表格行内编辑增删改查

简介: 前言 最近在做一个OA系统,需要将大量excel中的数据录入,并且希望以后新建数据时,也能像excel那样方便。并且这个后台系统有非常多这样的表单。因此需要做一个内敛的表单控件。 上网找到一款编辑起来非常方便的控件handsontable,这个表单控件可以支持excel中的多种操作,比如多行复制粘贴,ctrl+z撤销,ctr+r重做等等。

前言

最近在做一个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);

 

目录
相关文章
|
20天前
|
SQL 关系型数据库 MySQL
学成在线笔记+踩坑(3)——【内容模块】课程分类查询、课程增改删、课程计划增删改查,统一异常处理+JSR303校验
课程分类查询、课程新增、统一异常处理、统一封装结果类、JSR303校验、修改课程、查询课程计划、新增/修改课程计划
学成在线笔记+踩坑(3)——【内容模块】课程分类查询、课程增改删、课程计划增删改查,统一异常处理+JSR303校验
|
7天前
|
SQL 关系型数据库 MySQL
ThinkPHP6 连接使用数据库,增删改查,find,select,save,insert,insertAll,insertGetId,delete,update方法的用法
本文介绍了在ThinkPHP6框架中如何连接和使用数据库进行增删改查操作。内容包括配置数据库连接信息、使用Db类进行原生MySQL查询、find方法查询单个数据、select方法查询数据集、save方法添加数据、insertAll方法批量添加数据、insertGetId方法添加数据并返回自增主键、delete方法删除数据和update方法更新数据。此外,还说明了如何通过数据库配置文件进行数据库连接信息的配置,并强调了在使用Db类时需要先将其引入。
ThinkPHP6 连接使用数据库,增删改查,find,select,save,insert,insertAll,insertGetId,delete,update方法的用法
|
2月前
|
SQL 数据库连接 API
ThinkPHP6实现增删改查接口
ThinkPHP6实现增删改查接口
33 1
|
2月前
|
XML 数据库 数据格式
Spring5入门到实战------14、完全注解开发形式 ----JdbcTemplate操作数据库(增删改查、批量增删改)。具体代码+讲解 【终结篇】
这篇文章是Spring5框架的实战教程的终结篇,介绍了如何使用注解而非XML配置文件来实现JdbcTemplate的数据库操作,包括增删改查和批量操作,通过创建配置类来注入数据库连接池和JdbcTemplate对象,并展示了完全注解开发形式的项目结构和代码实现。
Spring5入门到实战------14、完全注解开发形式 ----JdbcTemplate操作数据库(增删改查、批量增删改)。具体代码+讲解 【终结篇】
|
2月前
|
SQL XML Java
Spring5入门到实战------12、使用JdbcTemplate操作数据库(增删改查)。具体代码+讲解 【上篇】
这篇文章是Spring5框架的实战教程,详细讲解了如何使用JdbcTemplate进行数据库的增删改查操作,包括在项目中引入依赖、配置数据库连接池、创建实体类、定义DAO接口及其实现,并提供了具体的代码示例和测试结果,最后还提供了完整的XML配置文件和测试代码。
Spring5入门到实战------12、使用JdbcTemplate操作数据库(增删改查)。具体代码+讲解 【上篇】
|
2月前
|
开发工具 数据安全/隐私保护 索引
LDAP学习笔记之二:389-DS(RHDS) 增删改查基本操作
LDAP学习笔记之二:389-DS(RHDS) 增删改查基本操作
|
2月前
|
关系型数据库 MySQL 数据库
MySQL数据库的增删改查
MySQL数据库的增删改查
14 0
|
2月前
|
SQL 关系型数据库 MySQL
"Python与MySQL的浪漫邂逅:一键掌握增删改查,开启你的数据库编程之旅!"
【8月更文挑战第21天】Python因其简洁的语法和强大的库支持,成为连接数据库的首选工具。本文介绍如何使用Python连接MySQL数据库并执行基本操作。首先需安装`mysql-connector-python`库。通过配置连接信息建立数据库连接后,可利用`cursor.execute()`执行SQL语句进行数据的增删改查,并通过`commit()`提交更改。查询时使用`fetchall()`或`fetchone()`获取结果。记得处理异常及关闭连接以释放资源。掌握这些基础,有助于高效进行数据库编程。
32 0
|
2月前
|
druid Java 数据库连接
SpringBoot项目整合MybatisPlus持久层框架+Druid数据库连接池,以及实现增删改查功能
SpringBoot项目整合MybatisPlus和Druid数据库连接池,实现基本的增删改查功能。
179 0
|
2月前
|
前端开发 Java 关系型数据库
通过HTML网页对mysql数据库进行增删改查(CRUD实例)
通过HTML网页对mysql数据库进行增删改查(CRUD实例)
166 0
下一篇
无影云桌面