Web 前端开发 让前端飞JavaScript 进阶营

基于DataTables写一个可编辑Table插件

2017-12-15  本文已影响76人  upcyoung

源代码地址:https://github.com/upcyoung/dtEditor

在Web项目中,用于显现数据时,一般都会选择Table。完全自己手写Table是一项特别繁杂的事情,所以一般会选择Table插件。在项目中,如果需要使用Table时,我一般会首选jquery datatables,datatables支持丰富的配置、API及扩展(Buttion, Selected, Fixedcolumn等)。

在有些项目中会要求数据可新增、编辑,在DataTables的官网中可以找到Editor扩展,该扩展提供了对数据的编辑功能,但是此扩展是收费的。由于我已经习惯了使用DataTables,并不想换到其他的Table插件,所以决定自己写一个支持编辑的插件。

在编写插件前,首先要确定一个实现插件的大体思路。在表单录入中,由哪种方式(输入、下拉、日期等)进行编辑及验证是多样化的,如果考虑把这部分也在插件中就太复杂了(如果有兴趣可查看x-editable)。所以,我希望这个插件要功能单一,仅提供一个基架,编辑的行为在外部实现,同时支持行内和弹出两种编辑方式。

在实现插件我想到的是在原有的row上为每个可编辑列生成相应的控件,还是clone当前row并添加到tbody中再为每个可编辑列生成控件。这里,我选择了后种方式。接下要解决的问题是如何为可编辑列生成控件并在结束时销毁各个控件。这里借鉴DataTalbe中columnDefs的定义方式,用target来声名列的位置,使用render方式返回控件;考虑到控件的数据可能是异步加载,增加renderAfter方法;考虑到控件的销毁和事件的移除,增加destroy方法;为值的获取增加get方法。这样编辑列的配置如下。

//可编辑列的配置
window.UpcEditorSetting = function (options) {
    var self = this;
    self.target = options.target;
    self.field = options.field;
    self.title = options.title;
   if (options.noField) {
       self.noField = true;
   }
   self.$dom = null;
   self.handlers = [];
   self.render = function () { return ''; };
   if (!self.noField) {
        self.get = function () {
            return $.trim(this.$dom.val());
        };
   }
   if (options.render) {
            this.render = options.render;
   }
   if (options.get) {
        this.get = options.get;
   }
   if (options.destroy) {
        this.destroy = options.destroy;
   }
   if (options.renderAfter) {
        this.renderAfter = options.renderAfter;
   }
   return this;
};

其中,noField用于标识此列为非编辑列,$dom用于保存想要在上下文中要使用的对象,handlers保存绑定的事件,用于在销毁时移除事件绑定,field和title用于弹出编辑的支持。如果不需要上下文this时,可仅声名具有以上属性的对象即可。
在定好编辑列配置后,就可以开始搭主要的架子了。将插件命名为dtEditor,插件主要实现编辑、保存、取消、新增、获取编辑数据功能。jquery插件的实现方式就是要在jquery原型上声名function就可以了,如下方式:

//jquery插件的声名方式
$.prototype.dtEditor=function(options){}
//or
$.fn.dtEditor=function(options){}
//or
$.fn.extend({
    dtEditor:function(options){}
})

以下贴出inline编辑模式下可编辑列的初始化和获取值相关的代码。

var tools = {
   getRowData:function() {
       if (this.oTrIndex >= 0) {
           var dt = this.oDataTable;
           var rd = dt.row(this.oTrIndex).data();
           var node = dt.row(this.oTrIndex).node();
           var tr = $(node).next()[0];
           var aoColumns = dt.settings()[0].aoColumns;
           var columns = this.columns;
           for (var i = columns.length - 1; i >= 0; i--) {
               if (columns[i].get) {
                   var td = $(tr).find("td")[[columns[i].target]];
                   var d = columns[i].get($(td), $(tr));
                   rd[aoColumns[columns[i].target].data] = d; 
               }

           }
           return rd;
       }
       return null;
   },
   inlineAccessor: function (index, tr, row, cache) {
       var dt = cache.oDataTable;
       var rowClone = $(row.node()).clone(); //clone当前行的node
       var columns = cache.columns;
       $(row.node()).hide();
       $(row.node()).after(rowClone);
       var tds = rowClone.find("td");
       var rd = row.data();
       //对每一列,激活编辑模式
       for (var i = cache.columns.length - 1; i >= 0; i--) {
           var coordinate = { row: index, column: columns[i].target };
           var cell = dt.cell(coordinate); //取得cell
           var td = tds[columns[i].target];
           var cd = null;
           if (!columns[i].noField) {
               cd = cell.data();
           }
           var html = columns[i].render(cd, rd, $(tr)); //调用render方法,获取填充td的html
           if (typeof html == "string") {
               td.innerHTML = html; //填充td,编辑状态
           } else {
               $(td).empty().append(html); //jquery对象,直接append
           }
           if (columns[i].renderAfter) { //html渲染完之后,执行回调
               columns[i].renderAfter(cd, rd, $(tr));
           }
       }
   }
};

插件使用方式如下。

var option = {
    pop: $('#pop-form'), //弹出编辑列的父容器
    columns: [
        new UpcEditorSetting({
            target: 0,
            noField: true
        }),
        new UpcEditorSetting({
            target: 2,
            field: 'text',
            title: '简短描述',
            render: function(cd) {
                this.$dom = $('<input type="text" class="form-control"/>').val(cd);
                return this.$dom;
            },
            renderAfter: function() {
                this.$dom.focus();
            }
        }),
        new UpcEditorSetting({
            target: 3,
            field: 'amount',
            title: '数量',
            render: function(cd) {
                this.$dom = $('<input type="text" class="form-control"/>').val(cd);
                return this.$dom;
            }
        }),
        new UpcEditorSetting({
            target: 4,
            field: 'meins',
            title: '计量单位',
            destroy: function() {
                this.$dom.chosen('destroy');
            },
            get: selectGetter,
            render: function(cd, full) {
                this.$dom = $('<select type="text" class="form-control"></select>').val(cd);
                return this.$dom;
            },
            renderAfter: function(cd) {
                var self = this;
                //使用jquery chosen
                self.$dom.chosen(chosenOp);
                //异步加载数据
                setTimeout(() => {
                    var options = [],data=[{"name":"百分比","code":"%"},{"name":"每千","code":"%O"}];
                    for (var i = 0, l = data.length; i < l; i++) {
                        options.push('<option data-t="' + data[i].name + '" value="' + data[i].code + '">' + data[i].code + '-' + data[i].name + '</option>');
                    }
                    self.$dom.empty().append(options.join('')).val();
                    if (v) {
                        self.$dom.val(v);
                    }
                    self.$dom.trigger("chosen:updated");
                }, 0);
            }
        }),
        new UpcEditorSetting({
            target: 5,
            field: 'date',
            title: '交货日期',
            render: function(cd) {
                this.$dom = $('<input type="text" class="form-control"/>');
                if (cd) {
                    this.$dom.val(cd);
                } else {
                    this.$dom.val((new Date()).format());
                }
                var options = {
                    autoclose: true,
                    language: 'zh-CN',
                    minView: 2,
                    pickerPosition: 'bottom-left',
                    format: "yyyy-mm-dd"
                };
                if (editMode == 0) {
                    options.pickerPosition = 'top-right';
                }
                //使用bootstrap datetimepicker
                this.$dom.datetimepicker(options);
                return this.$dom;

            },
            destroy: function($td) {
                this.$dom.datetimepicker('remove');
            }
        }),
        new UpcEditorSetting({ target: 11, noField: true })
    ],
    defaultValue: { text: '', amount: 1, meins: '',date:'' },
    message: messageService.warning
};
$('table').dtEditor(option);  //初始化dtEditor
$('table').dtEditor().triggerAdd(); //新增
$('table').dtEditor().triggerEdit(row); //编辑
$('table').dtEditor().triggerCancel(); //取消
$('table').dtEditor().getRowData(); //获取行数据
$('table').dtEditor().triggerSave(); //保存数据

效果图如下。


inline.jpg
pop.jpg
上一篇下一篇

猜你喜欢

热点阅读