在子行中编辑父元素/子元素
祝大家新年快乐。去年,在专注于 DataTables 的其他方面(扩展和支持)时,我们有点儿忽略了博客,但在 2019 年,我们将更加定期地发布博客文章。为了振奋精神,我们将再次访问 Editor 父元素/子元素文章。当您有一个一对多的数据库结构时,父元素/子元素编辑是一个非常热门的话题,它可以让最终用户在单个页面上编辑两个表中的数据。
关于这篇文章的最常见问题是“当子行显示时,我们如何执行这项操作,而不是始终显示子表?” 这就是我们将在本文中探讨的内容。作为一个快速开始,这是我们想要实现的结果
名称 | 用户 |
---|
父元素表
不是尝试修改前一个父元素/子元素编辑文章,而是我们将从第一原理开始,创建编辑表,这样更容易遵循。在这个过程中的第一步是创建父元素表,这就像在 Editor 示例 中找到的一样,是一个非常简单的 Editor 和 DataTable 组合,我们还将其与 行详细信息 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 脚本从 `Site` 表中读取 `id` 和 `name` 列。`id` 列是必需的,以便在选择行时向子表服务器端脚本提交信息 - `id` 其实并没有在表中显示,同样也没有在编辑器表单中显示,因此使用 `set(false)` 作为一种安全措施。
这里需要注意的一点是使用 `Mjoin` 实例来获取关于每个站点使用的项目数的信息(“`Mjoin`”的意思是“多连接”)。在 编辑器手册 中可获得有关如何使用 `Mjoin` 的详细说明。如果你不需要或不希望在你的父表中显示计数列,则无需 `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();
子表
现在我们编写事件处理程序,它将显示和隐藏子行,因为它将定义显示和隐藏每个子行的数据表所需的功能。这是对 行详情示例 的一个小的修改。我们不会只将行数据传递给创建子表的方法,而是传递整个行实例,从而访问父表的完整数据表 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');
}
} );
基于此,我们需要创建两个函数
- 针对子表的数据表和编辑器功能的 `createChild`
- 针对清理的 `destroyChild`
创建数据表
在 行详情示例 中,一个字符串被给定到 `child()`方法中以在子行中显示该字符串。但是,也可以传递 DOM 元素,因此我们可以通过简单地创建一个表元素、将其插入文档(使用
child().show()
)并将其初始化为常规数据表,以此创建数据表。
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( {
// ...
} );
}
你可以从此处看到,当用户请求显示一个子行时,我们可以动态构建任何数据表。每次调用 `createChild()` 函数时都会创建一个新的唯一表,因此也无需为每个表创建 id。
销毁数据表
当关闭子行时,我们不想简单关闭它并保留其中的数据表占据内存 - 这将成为内存泄漏,如果最终用户打开和关闭足够多的行,最终会导致浏览器内存耗尽。相反,我们需要使用 `destroy()`方法来销毁表及其所有事件处理程序。我们还使用一小段 jQuery 从 DOM 中将其删除。
function destroyChild(row) {
var table = $("table", row.child());
table.detach();
table.DataTable().destroy();
// And then hide the row
row.child.hide();
}
编辑器的配置
编辑器子行的配置与任何其他 基本编辑器 的配置几乎完全相同,因为它定义了数据、要编辑的表和可编辑字段的 Ajax URL。但是,在本例中,我们需要使用 `ajax.data`选项来向服务器发送父元素的 id(在本例中为站点 id),以便在 `WHERE` 条件中使用。我们还可以通过使用 `field.def`
选项(已设置为父行的 id(在本例中为 `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 相同,同样修改为使用 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 仅仅是常规组件,并且可以使用针对每种组件(如任何其他 Editor 和 DataTable)的任何选项、事件和 API 进行修改。
更新父表
修改子表后,可能需要在一个或多个行中更新父表的Users 计数。为此,我们使用 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();
});
} );
上面的第 3 行使用一个合成的 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!