表格行内编辑增删改查

2015-10-18 17:47:00 2437 次浏览 博主博客园看原文

前言 最近在做一个OA系统,需要将大量excel中的数据录入,并且希望以后新建数据时,也能像excel那样方便。并且这个后台系统有非常多这样的表单。因此需要做一个内敛的表单控件。 上网找到一款编辑起来非常方便的控件

前言

最近在做一个OA系统,需要将大量excel中的数据录入,并且希望以后新建数据时,也能像excel那样方便。并且这个后台系统有非常多这样的表单。因此需要做一个内敛的表单控件。

上网找到一款编辑起来非常方便的控件handsontable,这个表单控件可以支持excel中的多种操作,比如多行复制粘贴,ctrl+z撤销,ctr+r重做等等。但是这个插件对数据的提交和数据的校验做的并不好。于是自己对这个插件进一步封装成为一个控件。

写好后的控件演示地址

目标

控件希望至少做到的下几点

  1. 当某一行有单元格发生变化时,自动校验这一行的数据,如果校验成功,将数据post到一个save url,post能绑定固定参数。
  2. 当有多行发生变化时,依次校验各行,依次提交校验通过的数据。
  3. 当最后一行数据发生变化时,不管是否已经提交,都立即在后面追加一行。
  4. 当保存的行是新建的时候,要将响应的记录ID更新到table上。
  5. 绑定table.render刷新事件,当刷新时间被触发时,重新渲染列表。
  6. 绑定add.table_id事件,事件将绑定一行新的数据(由触发的时候传递过来),响应事件时,将这行新的记录追加到表格中。
  7. 选择多行快速删除,get一个请求传递参数ids(逗号分隔id)

使用

首先看看如何使用这个控件,只需要像下面这样

<?php
$data = array(
        'rows'   =>  array(
                array('id'=>1,'username'=>'lvyahui','email'=>'devlyh@163.com','phone'=>'9999','group'=>'组1','group_id'=>1),
                array('id'=>2,'username'=>'lvyahui','email'=>'devlyh@163.com','phone'=>'9999','group'=>'组2','group_id'=>2),
                array('id'=>3,'username'=>'lvyahui','email'=>'devlyh@163.com','phone'=>'9999','group'=>'组1','group_id'=>1),
                array('id'=>4,'username'=>'lvyahui','email'=>'devlyh@163.com','phone'=>'9999','group'=>'组3','group_id'=>3),
        ),
);

?>

<div id="dataTable">

</div>
{{HTML::script('js/handsontable.full.min.js')}}
{{HTML::style('css/handsontable.full.min.css')}}
{{HTML::script('js/editTable.js')}}
<script>
    $('#dataTable').initEdit({
        rows    :  JSON.parse('<?=json_encode($data['rows'])?>'),
        colHeaders :   ['ID','用户名','邮箱','电话号码'],
        columns :   {
            id  :   {
                readOnly    :   true
            },
            username : {
                label   :   '用户名',
                required    :   true,
                validator  :    /^\w+$/
            },
            email :{
                required    :   true,
                //validator  :    /^\w+$/
                editor: 'select',
                selectOptions : ['lvyahui8@126.com','devlyh@163.com']
            },
            phone :{
                validator  :    function(value,callback){
                    //return value.length > 1;
                    callback(true);
                    return true;
                },
                allowVaild  :   true
            },
            group  :    {
                required    :   true,
                editor: 'select',
                selectOptions : ['组1','组2','组3']
            }
        },

        bindData    :   {
            cus :   1,
            category_id :   2
        },

        beforeSave  :   function(data){
            var groups = [{name:'组1',id:1},{name:'组2',id:2},{name:'组3',id:3}];
            var has = groups.filter(function(item){
               if(item.name == data.group){
                   return true;
               }else{
                    return false;
               }
            });
            if(has.length > 0){
                data.group_id = has[0].id;
            }
            return data;
        },

        afterSave   :   function(resp){

        },

        url         :   {
            save    :   '<?=URL::to('test/table-row')?>',
            delete  :   '<?=URL::to('test/table-delete')?>'
        }
    });
    $('body').trigger('add.dataTable',{
        id  :   111,
        username    :   'devlyh',
        email   :   'lvyahui@126.com',
        phone   :   '100000',
        gourp_id    :   1
    });

</script>

上面的代码体现了设计思路,提交数据的时候,提交的是rows的某一行,显示的时候,只显示columns中有的属性。对于关系型数据,可以在提交数据之前将关系数据绑定提交。最下面触发的add.table_id(之所以事件跟一个table_id是为了保证一个页面有多个这个table的时候不冲突)事件,新增了一条数据。

你就可以看到生成了这样一个表格。

下面以一个实际的例子为例

 1 <div class="table" id="editTable"></div>
 2 {{HTML::script('js/handsontable.full.min.js')}}
 3 {{HTML::style('css/handsontable.full.min.css')}}
 4 {{HTML::script('js/editTable.js')}}
 5 
 6 <script>
 7     $('#editTable').initEdit({
 8         rows    :  [<?= implode(",",array_map(function($item){
 9             return "{id:$item->id,serial:'$item->serial',name:'$item->name',store:'$item->store',ship_time:'$item->ship_time',number:$item->number}";
10         },$model->items->all()));?>],
11         columns :   {
12             id  :   {
13                 label       :   'ID',
14                 readOnly    :   true
15             },
16             store : {
17                 label   :   'PO #',
18                 required:   true
19             },
20             serial : {
21                 label   :   '产品ID',
22                 required    :   true
23             },
24 
25             name :{
26                 label       :   '产品名称',
27                 required    :   true
28             },
29 
30             description: {
31                 label       : '产品描述',
32                 required    : false
33             },
34 
35             ship_time   :   {
36                 label       :   '发货截止时间',
37                 required    :   true
38             },
39             number  :    {
40                 label       :   '数量',
41                 required    :   true
42             }
43         },
44         bindData    :   {
45             customer_orders_id :   '<?=$model->id?>'
46         },
47         url         :   {
48             save    :   '<?=URL::to('customerOrderItem/edit')?>',
49             delete  :   '<?=URL::to('customerOrderItem/delete')?>'
50         }
51     });
52 </script>

效果

生成的表单就像这样

下面来批量新建,可以看到但出现两行符合要求是,向后台post了两个请求,请求响应了成功,将ID更新到单元格

修改单元格

?

触发add.table_id事件,向表格添加一行数据

 1 $("body").delegate(".select-item", "click", function (e) {
 2     var m = $("#add-product");
 3     $.get($(this).attr('href')+'&type='+$that.data('type'),function(resp){
 4         console.log(resp);
 5         resp.number = resp.number || 0;
 6         $('body').trigger('add.'+$that.data('table'),resp);
 7         m.modal('hide');
 8     },"json");
 9     return false;
10 });

触发刷新

$('.order-item').one('shown.bs.collapse',function(){
    $('div.edit-table').trigger('table.render');
});

多行删除,这里因为后台还没做,所以会报错,但是数据时请求到了delete url上的

代码

下面是这个控件的核心代码。

  1 /*
  2 * editTable.js
  3 */
  4 ;(function($,global){
  5 
  6     var objToArray = function(obj){
  7         var arr = [];
  8         for(var x in obj){
  9             arr.push(obj[x]);
 10         }
 11         return arr;
 12     },
 13         requiredRender = function(hot, td, row, col, prop, value, cellProperties){
 14             Handsontable.renderers.TextRenderer.apply(this, arguments);
 15             td.className += 'required';
 16             //td.style.backgroundColor = 'yellow';
 17         };
 18 
 19     var ExcelTable = function(element,options){
 20         var that = this;
 21 
 22         this.element = element;
 23         this.hot = null;
 24         this.edit = null;
 25         this.defaults = {
 26             bindData    :   {},
 27             rows        :   [],
 28             url         :   {
 29                 save    :   '',
 30                 delete  :   ''
 31             },
 32             columns     :   {},
 33             tableClassName : '',
 34             afterSave   :   function(resp){},
 35             beforeSave  :   function(data){}
 36         };
 37 
 38         this.options = $.extend({},this.defaults,options);
 39         this.rows = this.options.rows;
 40         //this.cols = objToArray(that.options.columns);
 41         var colHeaders = [],
 42             columns = [];
 43         for(var x in this.options.columns){
 44             if(this.options.columns[x].hasOwnProperty('label')){
 45                 colHeaders.push(this.options.columns[x].label);
 46             }else{
 47                 colHeaders.push(x);
 48             }
 49             columns.push($.extend({data:x},this.options.columns[x]));
 50         }
 51         this.cols = columns;
 52         var hotOptions = {
 53             data        :   options.rows,
 54             colHeaders  :   colHeaders,
 55             afterChange :   function(changes,source){
 56                 if(source !== 'loadData'){
 57                     that.save(changes);
 58                 }
 59             },
 60             beforeRemoveRow:function(index,amount){
 61                 that.delete(index,amount);
 62             },
 63             columns     :  columns ,
 64             minSpareRows:   1,
 65             contextMenu: ['remove_row'],
 66             cells: function (row, col, prop) {
 67                 if(col < that.cols.length && that.cols[col].required){
 68                     this.renderer = requiredRender;
 69                 }
 70             },
 71             tableClassName  :   this.options.tableClassName,
 72             width   :   '100%',
 73             stretchH: "all",
 74             colWidths   :   this.options.colWidths
 75         };
 76 
 77         if(typeof Handsontable === "function"){
 78             this.hot = new Handsontable(this.element,hotOptions);
 79         }
 80 
 81         $('body').bind('add.'+$(element).attr('id'),function(e,row){
 82             that.rows.splice(that.rows.length-1,0,row);
 83             that.hot.render();
 84         });
 85         $(element).bind('table.render',function(){
 86             that.hot.render();
 87         });
 88     };
 89 
 90     ExcelTable.prototype = {
 91         constructor :   ExcelTable,
 92         post    :   function(data){
 93             var that = this;
 94             var ret = this.options.beforeSave(data);
 95             if(typeof ret === "object"){
 96                 data = ret;
 97             }
 98             if(data.id){
 99                 data.action = 'edit';
100             }else{
101                 data.id = '';
102                 data.action = 'edit';
103             }
104             if(this.options.url.save){
105                 $.post(this.options.url.save,data,function(resp){
106                     if(!data.id && resp.id !== null){
107                         // 新建了记录,重新渲染
108                         that.options.rows[resp.index].id = resp.id;
109                         that.hot.render();
110                     }
111                     that.options.afterSave(resp);
112                 },'json');
113             }
114         },
115         getDelete   :   function(ids){
116             if(this.options.url.delete){
117                 $.get(this.options.url.delete+'?id='+ids,function(resp){
118 
119                 });
120             }
121         },
122         save    :   function(cells){
123             var that = this,
124                 rows = [];
125             cells.forEach(function(cell){
126                 if(cell[2] !== cell[3]){
127                     rows[cell[0]] = cell[0];
128                 }
129             });
130             rows.forEach(function(rowIndex){
131                 var row = that.rows[rowIndex],
132                     data = $.extend(row,that.options.bindData);
133                 data.index = rowIndex;
134                 if(that.validate(row)){
135                     console.log(data);
136                     that.post(data);
137                 }
138             });
139         },
140         delete  :   function(start,amount){
141             var ids = [];
142             for(var x = start;x < start + amount;x++){
143                 ids.push(this.rows[x].id);
144             }
145             this.getDelete(ids.join(','));
146         },
147         validate   :   function(row){
148             var that = this;
149             return that.cols.filter(function(col,index){
150                     if(row.hasOwnProperty(col.data)){
151                         var valitator = that.hot.getCellValidator(0,index);
152                         if(col.required){
153                             if(!row[col.data]) return true;
154                             if(valitator){
155                                 return !that.execValidator(valitator,row[col.data]);
156                             }
157                             return false;
158                         }else if(row[col.data] && valitator){
159                             return !that.execValidator(valitator,row[col.data]);
160                         }else{
161                             return false;
162                         }
163                     }
164                     else{
165                         return false;
166                     }
167                 }).length == 0;
168         },
169         execValidator:function(validator,value){
170             if(validator instanceof RegExp === true){
171                 return validator.test(value);
172             }else if(typeof  validator === "function"){
173                 return validator(value,function(){});
174             }else{
175                 return false;
176             }
177         },
178 
179         isEmptyRow  :   function(rowIndex){
180             var rowData = this.hot.getData()[rowIndex];
181 
182             for (var i = 0, ilen = rowData.length; i < ilen; i++) {
183                 if (rowData[i] !== null) {
184                     return false;
185                 }
186             }
187             return true;
188         }
189     };
190 
191     $.fn.initEdit = function(options){
192         return this.each(function(){
193             var excelTable = new ExcelTable(this,options);
194         });
195     }
196 
197 })($ || jQuery,window);

?

评论