2019 年 1 月 11 日,星期五

父/子行編輯在子行中

各位新年快樂。去年我們將重心放在 DataTables 的其他方面、擴充功能和支援,因此部落格文章略有減少,但在 2019 年我們會更頻繁地發布部落格文章。首先,我們要重新探討Editor 的父/子文章。當您有一對多的資料庫結構時,父/子編輯是一個相當熱門的主題,它可讓終端使用者在單一頁面上編輯兩個表格的資料。

關於先前文章最常見的問題是「我們如何用子行而不是總是顯示子表格來完成此操作?」這就是我們在此要探討的內容。快速入門,這就是我們的目標結果

名稱 使用者

父表格

我們將從第一原則建立此處的編輯表格,而不是嘗試修改先前的父/子編輯文章,這樣更容易理解。該過程的第一步是建立父表格,這是一個非常簡單的 Editor 和 DataTable 組合,您可以在Editor 範例中找到,我們也會將其與row details DataTables 範例結合。

Editor Javascript

Editor Javascript 非常簡單,只有一個欄位(網站名稱),它會將資料提交到伺服器端腳本

var siteEditor = new $.fn.dataTable.Editor( {
    ajax: '../php/sites.php',
    table: '#sites',
    fields: [ {
        label: 'Site name:',
        name: 'name'
    } ]
} );

DataTables Javascript

對於 DataTables 初始化,我們需要定義三個欄位

  • 子行的顯示/隱藏控制項
  • 網站名稱
  • 分配給該網站的使用者人數。為此,使用一個columns.render函式,它只會從資料陣列中返回使用者人數。
var siteTable = $('#sites').DataTable( {
    order: [ 1, 'asc' ],
    ajax: '../php/sites.php',
    columns: [
        {
            className: 'details-control',
            orderable: false,
            data: null,
            defaultContent: '',
            width: '10%'
        },
        { data: 'name' },
        { data: 'users', render: function ( data ) {
            return data.length;
        } }
    ],
    select: {
        style:    'os',
        selector: 'td:not(:first-child)'
    },
    layout: {
        topStart: {
            buttons: [
                { extend: 'create', editor: siteEditor },
                { extend: 'edit',   editor: siteEditor },
                { extend: 'remove', editor: siteEditor }
            ]
        }
    }
} );

另請注意,select.selector選項用於禁止選取子行顯示/隱藏控制項欄,您不希望使用者每次顯示或隱藏子行時都變更列選取!

伺服器端 (PHP)

對於父表格,PHP 腳本會從網站表格讀取 idname 欄。當選取列時,需要 id 欄才能將資訊提交到子表格伺服器端腳本,id 實際上不會顯示在表格中,也不在 Editor 表單中(因此為了安全起見,會使用 set(false))。

這裡值得注意的一點是,會使用 Mjoin 執行個體來取得使用每個網站的項目數的相關資訊 (Mjoin 是「多重聯結」的縮寫)。有關如何使用 Mjoin 的詳細說明,請參閱Editor 手冊。如果您不需要或不想在父表格中顯示計數欄,則不需要 Mjoin

Editor::inst( $db, 'sites' )
    ->fields(
        Field::inst( 'id' )->set( false ),
        Field::inst( 'name' )->validator( 'Validate::notEmpty' )
    )
    ->join(
        Mjoin::inst( 'users' )
            ->link( 'sites.id', 'users.site' )
            ->fields(
                Field::inst( 'id' )
            )
    )
    ->process( $_POST )
    ->json();

子表格

現在,讓我們撰寫將顯示和隱藏子行的事件處理常式,因為這會定義顯示和隱藏每個子行的 DataTable 所需的函式。這是對row details 範例的小幅修改。我們不會僅將列資料傳遞到建立子表格的方法,而是傳遞整個列執行個體,讓使用者可以存取父表格的完整 DataTables API。此外,在這種情況下,我們將使用銷毀函式來清理關閉時的子表格,以確保沒有記憶體洩漏

$('#sites tbody').on('click', 'td.details-control', function () {
    var tr = $(this).closest('tr');
    var row = siteTable.row( tr );

    if ( row.child.isShown() ) {
        // This row is already open - close it
        destroyChild(row);
        tr.removeClass('shown');
    }
    else {
        // Open this row
        createChild(row);
        tr.addClass('shown');
    }
} );

由此,我們需要建立兩個函式

  • 用於子表格的 DataTable 和 Editor 功能的 createChild
  • 用於清理的 destroyChild

建立 DataTable

row details 範例中,會將字串提供給 child() 方法,以便在子行中顯示。但是,也可以傳遞 DOM 元素,因此我們只需建立表格元素、將其插入到文件中(使用child().show()),然後將其初始化為常規的 DataTable,即可建立 DataTable

function createChild ( row ) {
    // This is the table we'll convert into a DataTable
    var table = $('<table class="display" width="100%"/>');

    // Display it the child row
    row.child( table ).show();

    // Initialise as a DataTable
    var usersTable = table.DataTable( {
        // ...
    } );
}

您將可從此處看到,每當使用者要求顯示子行時,我們就可以動態建立任何 DataTable。每次呼叫 createChild() 函式時,都會建立新的唯一表格,因此也無需為每個表格建立 ID。

銷毀 DataTable

當需要關閉子行時,我們不希望只是關閉它並將 DataTable 留在其中佔用記憶體,如果終端使用者開啟和關閉足夠多的列,這將會導致記憶體洩漏,最終導致瀏覽器記憶體不足。相反,我們需要使用 destroy() 方法來銷毀表格及其所有事件處理常式。我們也會使用一小段 jQuery 將其從 DOM 中移除

function destroyChild(row) {
    var table = $("table", row.child());
    table.detach();
    table.DataTable().destroy();

    // And then hide the row
    row.child.hide();
}

Editor 設定

Editor 子行的設定與任何其他基本 Editor幾乎完全相同,它定義了資料的 Ajax URL、要編輯的表格和要編輯的欄位。但是,在這種情況下,我們需要使用 ajax.data 選項來將父系的 ID(在此情況下為網站 ID)傳送到伺服器,以便在 WHERE 條件中使用。我們也可以透過使用設定為父列 ID 的 field.def 選項(在此情況下為 rowData.id)來預選子表格所在的網站,使使用者體驗更輕鬆

var rowData = row.data();
var usersEditor = new $.fn.dataTable.Editor( {
    ajax: {
        url: '/media/blog/2016-03-25/users.php',
        data: function ( d ) {
            d.site = rowData.id;
        }
    },
    table: table,
    fields: [ {
            label: "First name:",
            name: "users.first_name"
        }, {
            label: "Last name:",
            name: "users.last_name"
        }, {
            label: "Phone #:",
            name: "users.phone"
        }, {
            label: "Site:",
            name: "users.site",
            type: "select",
            placeholder: "Select a location",
            def: rowData.id
        }
    ]
} );

DataTable 設定

DataTable 設定也與其他基本 DataTables 幾乎相同,再次修改為使用 ajax.data 來傳送父列的 ID,以確保只載入屬於該父系的列

var usersTable = table.DataTable( {
    pageLength: 5,
    ajax: {
        url: '/media/blog/2016-03-25/users.php',
        type: 'post',
        data: function ( d ) {
            d.site = rowData.id;
        }
    },
    columns: [
        { title: 'First name', data: 'users.first_name' },
        { title: 'Last name', data: 'users.last_name' },
        { title: 'Phone #', data: 'users.phone' },
        { title: 'Location', data: 'sites.name' }
    ],
    select: true,
    layout: {
        topStart: {
            buttons: [
                { extend: 'create', editor: usersEditor },
                { extend: 'edit',   editor: usersEditor },
                { extend: 'remove', editor: usersEditor }
            ]
        }
    }
} );

我們還使用pageLength來保持子行中的頁面大小較小,但可以根據需要進行設定。事實上,這突顯了子行中的 Editor 和 DataTable 只是常規元件,可以使用每個元件的任何選項、事件和 API 進行修改,就像任何其他 Editor 和 DataTable 一樣。

更新父表格

當子表格被修改時,父表格的使用者計數可能需要在一個或多個列中更新。為此,我們使用 ajax.reload() 方法(透過我們傳遞到函式中的 row 變數存取 - 回想一下,DataTables 的鏈結 API 允許在所有層級存取最上層方法)

usersEditor.on( 'submitSuccess', function (e, json, data, action) {
    row.ajax.reload(function () {
        $(row.cell( row.id(true), 0 ).node()).click();
    });
} );

上面的第三行使用合成的 click 事件來觸發子行的「顯示」動作,因為 Ajax 重新載入會導致它自動關閉(它實際上是新增的列)。可以從伺服器取得新的計數,並使用 row().data() 更新每個列的資料,以提高效率,但這不在本文的範圍內。

伺服器端 (PHP)

儘管在用戶端上,我們可以同時初始化並顯示多個子 Editor,但在伺服器端,我們只需要一個腳本,它會透過父 ID(即網站)來區分 Editor。這是使用提交的 site 參數和 WHERE 條件來完成。

if ( ! isset($_POST['site']) || ! is_numeric($_POST['site']) ) {
    echo json_encode( [ "data" => [] ] );
}
else {
    Editor::inst( $db, 'users' )
        ->field( 
            Field::inst( 'users.first_name' ),
            Field::inst( 'users.last_name' ),
            Field::inst( 'users.phone' ),
            Field::inst( 'users.site' )
                ->options( 'sites', 'id', 'name' )
                ->validator( 'Validate::dbValues' ),
            Field::inst( 'sites.name' )
        )
        ->leftJoin( 'sites', 'sites.id', '=', 'users.site' )
        ->where( 'site', $_POST['site'] )
        ->process($_POST)
        ->json();
}

將其組合在一起

只剩下一個步驟 - 當父表格中的網站標籤更新時,我們需要在子表格中反映這一點。我們可以使用子表格的 ajax.reload() 函式來完成此操作

function updateChild ( row ) {
    $('table', row.child()).DataTable().ajax.reload();
}

可以使用以下程式碼呼叫該函式

siteEditor.on('submitSuccess', function () {
    siteTable.rows().every(function () {
        if (this.child.isShown()) {
            updateChild(this);
        }
    });
} );

如果您想查看本文中使用的完整組合 Javascript,則請在此處取得。也可以取得所用的 CSS,雖然這只是用於列詳細資料按鈕和強調子行。

從這裡開始

我希望您能從這篇文章中了解,子行的顯示不應僅限於基本範例中的靜態資料。您可以在子行中新增任何您想要的互動,包括全面的可編輯 DataTable!