2012 年 5 月 31 日星期四

内嵌编辑

更新:如果您正在为 DataTables 寻找一个完整的编辑解决方案,Editor 现已推出,可以在几分钟内为您的表格添加编辑功能。

此博客文章是在 DataTables 1.10 发布之前编写的,并未使用 1.10 中的新 API 和功能。保留此篇文章供参考,但请参阅手册以获取有关如何使用 DataTables 的说明。

在现代的 Web 应用程序中,表格的一个常见用途是显示一组可以由最终用户修改的信息 - 一个典型的CRUD(创建、读取、更新、删除)场景。其中一个示例是 Web 应用程序的用户列表,管理员可以在表格视图中实时更新用户详细信息。

有很多方法可以显示此表格的编辑界面,您希望使用哪种方法取决于您希望它如何与您的网站/应用程序的其余部分进行交互。您可能希望有一个浮动在表格顶部的编辑灯箱,或者您可以使用类似jEditable的库一次使用一个单元格。通常,我避开为 DataTables 提供编辑插件,因为应该如何实施它会根据不同的情况而有很大不同,而且一刀切在这里并不适用。

幸运的是,使用DataTables API可以轻松创建适合您特定设置的 CRUD 界面。在此示例中,我将展示如何创建提供整行内联编辑的界面。在本教程结束时我们将实现的效果如下图所示

表格

与往常一样,我们需要一个表格作为起点。为此,我将我要制作的可编辑表格放在我的Conditional-CSS 的浏览器支持标准表格的基础之上,再添加两列,一列用于编辑按钮,一列用于删除按钮。当然,您可以根据需要对这些内容进行样式设置或自定义(例如图像)。我还在表格上方添加了一个用于创建新行的链接。标记如下所示

<p><a id="new" href="">Add new row</a></p>
<table cellpadding="0" cellspacing="0" border="0" class="display" id="example">
    <thead>
        <tr>
            <th>Rendering engine</th>
            <th>Browser</th>
            <th>Platform(s)</th>
            <th>Engine version</th>
            <th>CSS grade</th>
            <th>Edit</th>
            <th>Delete</th>
        </tr>
    </thead>
    <tbody>
        <tr class="odd gradeX">
            <td>Trident</td>
            <td>Internet Explorer 4.0</td>
            <td>Win 95+</td>
            <td class="center">4</td>
            <td class="center">X</td>
            <td><a class="edit" href="">Edit</a></td>
            <td><a class="delete" href="">Delete</a></td>
        </tr>
        ...
    </tbody>
</table>

DataTables 的初始化很简单

    $(document).ready(function() {
        var oTable = $('#example').dataTable();
    } );

编辑模式

在三个编辑功能(创建、编辑和删除)中,编辑功能是我们这里将首先要解决的问题。我的做法是简单地用一个input标签替换要使它可编辑的行中每个单元格的内容,及将它的值设为单元格的内容。这不会对 DataTables 提供的功能(如排序和过滤)有任何影响,因为 DataTables 会维护来自单元格的数据的内部缓存(以便更快地访问数据)——因此我们可以对 DOM 元素执行任何我们需要做的事情。

这部分的功能非常简单

function editRow ( oTable, nRow )
{
    var aData = oTable.fnGetData(nRow);
    var jqTds = $('>td', nRow);
    jqTds[0].innerHTML = '<input type="text" value="'+aData[0]+'">';
    jqTds[1].innerHTML = '<input type="text" value="'+aData[1]+'">';
    jqTds[2].innerHTML = '<input type="text" value="'+aData[2]+'">';
    jqTds[3].innerHTML = '<input type="text" value="'+aData[3]+'">';
    jqTds[4].innerHTML = '<input type="text" value="'+aData[4]+'">';
    jqTds[5].innerHTML = '[Save]()';
}

正如您所看到的,editRow() 函数采用两个参数

    1. oTable - DataTables 实例
    1. nRow - 要编辑的行对于的 TR 节点

对于行中可编辑的每个单元格,我们插入 input 标记,并通过使用 fnGetData 提取数据来设置其值(获取行数据)。请注意,“编辑”链接也会更新,显示“保存”,告诉终端用户再次单击链接时执行的操作。很明显这是个非常简单的案例,可轻松扩展以包括 select 元素或您希望使用的任何其他类型的输入。

function saveRow ( oTable, nRow )
{
    var jqInputs = $('input', nRow);
    oTable.fnUpdate( jqInputs[0].value, nRow, 0, false );
    oTable.fnUpdate( jqInputs[1].value, nRow, 1, false );
    oTable.fnUpdate( jqInputs[2].value, nRow, 2, false );
    oTable.fnUpdate( jqInputs[3].value, nRow, 3, false );
    oTable.fnUpdate( jqInputs[4].value, nRow, 4, false );
    oTable.fnUpdate( '[Edit]()', nRow, 5, false );
    oTable.fnDraw();
}

为了将用户编辑的信息保存回表中(以便可以像往常一样对其进行排序和筛选),我们使用 fnUpdate API 方法。fnUpdate 将写入给定的 TD 单元格节点,有效地使用新值替换在 editRow 函数中插入的 input 元素。请注意,传递给 fnUpdate 的第四个参数是 false,用于告诉 DataTables 不应重绘表,否则我们将在六次执行总体重绘,而实际上只需在所有更新完成后执行一次。

现在既然有了编辑和保存功能,我们需要向文档添加合适的事件处理程序来调用它们。为此,我们将 live 事件处理程序附加到编辑单元格中的 a 标记,它决定要执行的操作。单击“编辑”单元格时可能有三个状态

  • 当前没有正在编辑的行
  • 此行正在编辑,应保存
  • 另一个正在编辑 - 应取消编辑并编辑此行

为了跟踪当前编辑的行,我们将一个名为 nEditing 的变量用于存储该行的引用 - 由此我们可以决定要处于哪种状态。结果,我们可以为初始化代码创建以下内容

$(document).ready(function() {
    var oTable = $('#example').dataTable();
    var nEditing = null;

    $('#example a.edit').live('click', function (e) {
        e.preventDefault();

        /* Get the row as a parent of the link that was clicked on */
        var nRow = $(this).parents('tr')[0];

        if ( nEditing !== null &amp;&amp; nEditing != nRow ) {
            /* A different row is being edited - the edit should be cancelled and this row edited */
            restoreRow( oTable, nEditing );
            editRow( oTable, nRow );
            nEditing = nRow;
        }
        else if ( nEditing == nRow &amp;&amp; this.innerHTML == "Save" ) {
            /* This row is being edited and should be saved */
            saveRow( oTable, nEditing );
            nEditing = null;
        }
        else {
            /* No row currently being edited */
            editRow( oTable, nRow );
            nEditing = nRow;
        }
    } );
} );

添加行

复杂的部分已经解决 - 从这里开始全都是下坡路了!为了向表中添加新行,DataTables 提供了 fnAddData API 方法。我们可以将它与上述的 editRow 函数结合使用,创建新行并立即将其置于编辑模式中。这里我们将事件处理程序附加到表顶部的特殊链接上,位于文档准备函数中

$('#new').click( function (e) {
    e.preventDefault();

    var aiNew = oTable.fnAddData( [ '', '', '', '', '', 
        '[Edit]()', '[Delete]()' ] );
    var nRow = oTable.fnGetNodes( aiNew[0] );
    editRow( oTable, nRow );
    nEditing = nRow;
} );

这里有两点需要考虑:首先,fnAddData 返回一个索引数组,每个索引都指向 DataTables 中为新行存储的行信息(它是一个数组,因为 fnAddData 一次可以添加多行)。由此索引,我们可以使用 fnGetNodes 获取 tr 元素以便进行编辑。其次,我们将 nEditing 变量设置为新行,以便编辑/保存处理程序知道在单击时应将其保存。

删除行

为了封装三个编辑函数,我们现在只需添加从表中删除行的选项,这可通过使用 DataTables 提供的 fnDeleteRow API 方法轻松实现 - 只需传入要删除的行引用

$('#example a.delete').live('click', function (e) {
    e.preventDefault();

    var nRow = $(this).parents('tr')[0];
    oTable.fnDeleteRow( nRow );
} );

结论

本文介绍了一种可能的方法,用于构建 DataTables 中的 CRUD 界面。这里展示的实现故意保持简单,以此来展示 DataTables API 的用法,并显示如何快速构建和自定义这种界面来满足您的网站/应用程序需求。

值得注意的是,该实现有一些限制,还有一些领域有待改进

  • 目前数据未保存到服务器,只在本地 DataTables 实例中保存 - 因此重新加载后,表将恢复到其最初状态。需要向服务器发出 XHR 调用,才能将用户输入信息保存到数据库中(在saveRow 中)。
  • 列数以及哪些列可以编辑进行了硬编码 - 最好能够指定一个列索引数组,指出哪些列应该可编辑。
  • 输入类型目前仅限于文本输入 - 最好有select 输入等选项。
  • 可以实现自动聚焦和在“返回”时保存之类的 UI 改进,让最终用户操作起来更轻松。

我相信这是一个插件的基础 - 有人愿意接受挑战,将其构建成适用于 DataTables 的完整插件吗? :-)

在论坛中针对此帖子发表评论并展开讨论