2019 年 4 月 15 日 星期一
作者:Allan Jardine

編輯前重新整理資料

基於 Web 的 CRUD 系統,例如 Editor,本質上是並行的,也就是說,多個使用者可能會同時更新資料。這些使用者可能在同一個辦公室,也可能在世界的兩端,但您不希望發生的情況是,一個使用者對某一行進行更新,然後另一個使用者在沒有意識到資料已變更的情況下提交資訊,從而撤銷了這些變更。

最終,最佳的解決方案是即時更新表格中的資料,這是一個複雜的主題,我們將在未來進行探討(敬請期待!),但在本文中,我想介紹一種機制,該機制可以在最少的程式碼工作量下使用,並且不會中斷現有的程式碼庫:當使用者觸發編輯行時,重新整理要編輯的行(們)的資料。

為此,我們將開發一個新的 按鈕,它可以在觸發編輯之前執行此操作,這是一個可重複使用的元件。結果如下所示(請注意,這看起來與 Editor 中的標準編輯非常相似,但在點擊「編輯」時會使用 Ajax 請求來重新整理資料。顯示此頁面的第二個瀏覽器視窗可用於建立您自己的並行形式,從一個視窗更新另一個視窗中的資料)

姓名 職位 辦公室 薪資
姓名 職位 辦公室 薪資

可重複使用的按鈕

按鈕函式庫提供了許多內建的按鈕類型(例如用於匯出資料),而 EditorSelect 等函式庫可以擴充這些功能。但是按鈕的真正力量在於其定義自訂按鈕的能力,因此您可以建立自己的按鈕並擴充內建按鈕。

可重複使用的自訂按鈕應附加到 $.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() 來取得設定的 Editor Ajax URL。
  3. 更新行只需使用 row().data() 即可完成,並在迴圈中根據其 ID 選取行。請注意,我們應該呼叫 draw() 來重新整理表格狀態,但僅當所有選定的行都已更新時才應呼叫。
  4. 我們可以透過呼叫 edit 按鈕,並使用 Function.prototype.call 以確保範圍匹配,從而傳遞我們自己的 action 方法的參數,來使用 Editor 定義的 edit 按鈕。

將其組合在一起並充實程式碼,我們有

$.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);
            }
        } );
    }
};

使用按鈕

現在我們只需 照常建立 Editor 和 DataTable 實例,但不是在 buttons 陣列中使用 Editor 的 edit 按鈕,而是使用 editRefresh - 例如

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

這就是客戶端的所有內容!

伺服器端

在伺服器端,我們需要一種能夠從客戶端取得所請求行的資料的方法。這可以在 PHP、.NET 和 NodeJS 的 Editor 函式庫中使用一個相當簡單的 WHERE 條件來完成。

打破鏈條

在許多 Editor 範例中,我們使用單個鏈條來定義 Editor 實例、處理資料並將其傳回客戶端,例如,在 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

Editor 的 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 對高並行系統的適用性。與 Editor 的其餘部分一樣,此方法完全支援多行編輯。

期待未來在 Editor 及其函式庫中看到此功能內建!

如果您想查看完整版本,可取得此範例的完整 Javascript。同樣,也提供了用於開發此文章的 PHP.NET CoreNodeJS 腳本。每個腳本都是演示套件中提供的「staff」範例的小修改。