2019 年 4 月 15 日星期一
Allan Jardine 撰写

在编辑前刷新数据

基于网络的 CRUD 系统(如 Editor)本质上是并发运行的 - 例如,多位用户可同时更新数据。这些用户可能同处于一间办公室或分处世界各地,但您肯定不希望出现以下情况:某位用户针对某行做出更新,然后另一位用户在未意识到该行数据已更改的情况下提交信息而撤销了前者的更改。

最终,针对这一情况的最佳解决方案是实时更新表中的数据,这是一个复杂的话题,我们会在未来进行探讨(敬请期待!),但在本文中,我想介绍一种可在现有代码库中使用且代码占用量最少且不产生破坏的机制:当用户对某行触发编辑操作时刷新要编辑的行的数据。

为此,我们将在一个可重用组件中开发一个新的 按钮,该按钮可在触发编辑操作之前执行此操作。结果如下所示(请注意,这看起来与 Editor 中的标准编辑操作非常相似,但单击编辑时会使用 Ajax 请求刷新数据。可以另开一个浏览器窗口以显示此页面来创建自己的并发形式,从一个窗口更新另一个窗口中的数据

姓名 职位 办公室 工资
姓名 职位 办公室 工资

可重用按钮

Buttons 库 提供了许多内置按钮类型(例如,用于导出数据),而诸如 EditorSelect 等库可以扩展其功能。但 Buttons 真正强大之处在于其可定义 自定义按钮 的能力,以便创建您自己的按钮并扩展内置按钮。

可重用自定义按钮应附加到 $.fn.dataTable.ext.buttons 对象,其中属性名称为按钮的名称(在 DataTables buttons 配置中使用)。在此情况下,我们希望按钮的行为与 edit 按钮类型相似,因此我们可以进行扩展以构建在它上面。因此,我们的初始代码结构如下

$.fn.dataTable.ext.buttons.editRefresh = {
    extend: 'edit',
    text: 'Edit',
    action: function (e, dt, node, config) {
        // 1. Get currently selected row ids
        // ...

        // 2. Ajax request to refresh the data for those ids
        // ...

        // 3. On success update rows with data
        // ...

        // 4. And finally trigger editing on those rows
        // ...
    }
};

细分

现在我们的基本设计已设计完毕,我们只需要充实各个组件即可

  1. 为了获取将要编辑的行(以便我们可以向服务器识别我们感兴趣的行)的 ID,我们可以结合使用 DataTables 的 rows().ids() 方法和 {selected:true} 选择器修饰符 来筛选仅选定的行。
  2. 由于页面上已经使用了 jQuery,因此我们将使用 $.ajax() 发出简单的 Ajax 请求,将行 ID 发送到服务器。请注意使用 ajax() 获取已配置的编辑器 Ajax URL。
  3. 仅使用 row().data() 更新数据行,根据循环中 ID 选择行。请注意,我们应调用 draw() 以刷新表状态,但前提是已更新所有选定行。
  4. 我们可以利用由编辑器定义的 edit 按钮,方法是调用它,传入我们自己的 action 方法的参数,使用 Function.prototype.call 以确保匹配范围。

将它们放在一起,完善我们拥有的代码

$.fn.dataTable.ext.buttons.editRefresh = {
    extend: 'edit',
    text: 'Edit',
    action: function (e, dt, node, config) {
        this.processing( true );

        // Get currently selected row ids
        var selectedRows = dt.rows({selected:true}).ids();
        var that = this;

        // Ajax request to refresh the data for those ids
        $.ajax( {
            url: config.editor.ajax(),
            type: 'post',
            dataType: 'json',
            data: {
                refresh: 'rows',
                ids: selectedRows.toArray().join(',')
            },
            success: function ( json ) {
                // On success update rows with data
                for ( var i=0 ; i<json.data.length ; i++ ) {
                    dt.row( '#'+json.data[i].DT_RowId ).data( json.data[i] );
                }
                dt.draw(false);

                // And finally trigger editing on those rows
                $.fn.dataTable.ext.buttons.edit.action.call(that, e, dt, node, config);
            }
        } );
    }
};

使用按钮

现在我们只需创建编辑器和数据表实例 如常所做的一样,但不要在 buttons 数组中使用编辑器的 edit 按钮,而是使用 editRefresh - 例如

buttons: [
    { extend: 'create', editor: editor },
    { extend: 'editRefresh', editor: editor },
    { extend: 'remove', editor: editor }
]

客户端操作到此结束!

服务器端

在服务器端,我们需要一种方法来获取客户端请求的行数据。可在适用于编辑器的 PHP、.NET 和 NodeJS 库中完成此操作,通过相当简单的 WHERE 条件即可。

拆解链

在许多编辑器示例中,我们使用单个链来定义编辑器实例、处理数据并将其返回到客户端 - 例如在 PHP 中,我们可能使用

$editor = Editor::inst( $db, 'table' )
    ->fields( ... )
    ->process( $_POST )
    ->json();

但我们想要有条件地添加 WHERE 语句 - 要做到这一点,您必须记住,上面可以改写为

$editor = Editor::inst( $db, 'table' );
$editor->fields( ... );
$editor->process( $_POST );
$editor->json();

有了这个,很容易理解我们如何使用 if 语句来检查是否仅应检索某些行。

PHP

在 PHP 中,我们可以使用匿名函数来添加一行 ID 为基础的 WHERE ... OR ... 条件列表

$editor = Editor::inst( $db, 'staff' );
$editor->fields(
    ...
);

// Check if we are getting data for specific rows
if ( isset( $_POST['refresh'] ) ) {
    $editor->where( function ($q) use ($editor) {
        // Split the CSV ids
        $ids = explode( ',', $_POST['ids'] );

        for ( $i=0 ; $i<count($ids) ; $i++ ) {
            // Remove the row prefix
            $id = str_replace( $editor->idPrefix(), '', $ids[$i] );
            $q->or_where( 'id', $id );
        }
    } );
}

// Process and fire back the result
$editor
    ->process( $_POST )
    ->json();

.NET

在 .NET 中,类似于上面的 PHP 库,我们可以使用包含匿名函数的 Editor->Where() 方法,以在客户端要求时构建 WHERE ... OR ... 列表

var editor = new Editor(db, "staff")
    .Model<StaffModel>();

if (Request.HasFormContentType && Request.Form.ContainsKey("refresh")) {
    editor.Where( q => {
        var ids = (Request.Form["ids"].ToString()).Split(',');

        for (var i=0 ; i<ids.Length ; i++) {
            var id = ids[i].Replace(editor.IdPrefix(), "");

            q.OrWhere( "id", id );
        }
    });
}

var response = editor
    .Process(Request)
    .Data();

return Json(response);

NodeJS

编辑器的 NodeJS 库使用 Knex 库 进行数据库连接和抽象,并为通过 Editor.where() 方法得到条件 提供了它。然后,我们可以使用 Knex 的 orWhere() 方法 来限制 SELECT 语句

let editor = new Editor(db, 'staff').fields(
    ...
);

if (req.body.refresh) {
    editor.where(q => {
        let ids = req.body.ids.split(',');

        for (let i=0 ; i<ids.length ; i++) {
            let id = ids[i].replace(editor.idPrefix(), '');
            q.orWhere('id', id);
        }
    })
}

await editor.process(req.body);
res.json(editor.data());

结论

在这篇文章中,我演示了如何使用一个简单的自定义按钮来提升编辑器对高并发系统适用,方法是在终端用户触发编辑时刷新要编辑的行数据。和编辑器一样,此方法充分支持多行编辑。

期待未来看到该功能内置到 Editor 及其库中!

如需了解完整版本,可获取此示例的完整 Javascript。同样,此博文开发中所用的PHP.NET CoreNodeJS脚本也可获取。每个都对 demo 包中的“员工”示例进行了小的修改。