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

源代码地址: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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容