这次和大家分享的是自己写的一个table常用几种展示格式的js插件取名为(table-shenniu),样式使用的是bootstrap.min.css,还需要引用jquery.min.js包,这个插件由来的目的是项目中需要一个table格式的提交数据的页面,功能有合并单元格,只能提交选中行数据,全选功能,和一个下拉选功能;这几种功能感觉朋友们肯定都会遇到,所以干脆封装一个插件,发布出来说不定能帮到有些朋友快速完成任务哈哈,当然最主要的还是希望朋友们能相互交流里面的代码,逻辑,谢谢;中秋节就要到了,这里提前预祝大家节日快乐,吃月饼的时候不要忘记了点个赞。
以上是个人的看法,下面来正式分享今天的文章吧:
. 功能介绍与效果图(认可的点个赞)
. 需求分析
. 插件主要代码块的说明
. 获取table插件数据,并提交给后台
. 插件源码及帮助文档
下面一步一个脚印的来分享:
. 功能介绍与效果图(认可的点个赞)
1.普通效果
2.合并表格
3.汇总
4.合并+汇总
5.展示选中行明细
. 需求分析
首先,咋们来看张图:
如图所示,这里的乘客可能会选择不同的产品,同时选择的购买份数也可能改变,然后只能提交最后一列勾选上的数据这是上面的图展示出来的功能信息;为了更好的用户体验显然“乘客姓名”这一列需要吧相同名称的数据合并;价格一列通常在用户填写表单中不会有汇总的操作,但是数据统计报表中一般就需要;还有这个产品列只有名称,没有更详细描述信息,通常我们需要展示出来方便用户了解更多的信息,一般我们直接在产品名称对应的html元素使用title展示,这样不友好,所以又有了点击产品名称,查看明细的需求;下面我们来整合下需求:
1. table中每行需要checkbox选择框和对应的份数下拉框select及全选按钮
2. 重复数据合并单元格
3. 汇总金额数字列
4. 明细描述
5. 还需要一个能隐藏产品Id或者乘客Id的元素(插件这里定位一个隐藏列)
这个就是table列表需要满足的需求,也是大众化的需求吧
. 插件主要代码块的说明
首先,咋们来说下插件需要的固定格式的数据:列表头json数据,列表行json集合数据格式;
列表头json数据:
1 //测试用例-列表头 2 var header = [ 3 4 { 5 "title": "产品名称", 6 "name": "product", 7 "type": "label" 8 }, 9 { 10 "title": "销售价", 11 "name": "sale", 12 "type": "label", 13 }, 14 { 15 "title": "份数", 16 "name": "num", 17 "type": "select", //如果是下拉框,这里需要初始值val 18 "val": [ 19 { 20 "text": 1, 21 "val": 1 22 }, 23 { 24 "text": 2, 25 "val": 2 26 }, 27 { 28 "text": 3, 29 "val": 3 30 } 31 ] 32 }, 33 { 34 "title": "总额", 35 "name": "amount", 36 "type": "label" 37 }, 38 { 39 "title": "<input type='checkbox' name='cbAll' style='border: 0px; width: 20px; height: 20px;'/>", //如果是全选选择框,这里直接写html元素 40 "name": "cb", 41 "type": "checkbox" 42 }, 43 { 44 "title": "保险Id", 45 "name": "productid", 46 "type": "hidden" 47 } 48 ];
列表头数据注意点在于:
1. 如果"type": "select"类型,需要初始值val属性的值,这样每一行中才会出现一个下拉框
2. "type": "checkbox",如果列是复选框,那么头部单元格一般是全选的复选框,我们直接在头部json定义
<input type='checkbox' name='cbAll' style='border: 0px; width: 20px; height: 20px;'/>
就行了,如果不需要全选框,直接定义成文字就行
3. 插件列支持的type类型有type:有label(文本),select(下拉框,如果是下拉框需要初始化下拉数据“val”),checkbox(选择框,如果需要全选框直接写html元素,默认自带),hidden(隐藏域,保存每行唯一的值)
列表行json集合数据格式:
1 //测试用例-没行数据 2 var data = [ 3 { "product": "神雕侠侣电影票", "sale": 30.00, "num": 1, "amount": 30.00, "cb": false, "productid": 2 }, 4 { "product": "摇摇乐门票", "sale": 30.00, "num": 2, "amount": 60.00, "cb": false, "productid": 1 }, 5 { "product": "神雕侠侣电影票", "sale": 30.00, "num": 1, "amount": 30.00, "cb": true, "productid": 2 }, 6 { "product": "摇摇乐门票", "sale": 30.00, "num": 3, "amount": 90.00, "cb": false, "productid": 1 } 7 ]; 8 9 data = [ 10 { "product": { "产品名称": "神雕侠侣电影票", "描述": "神雕侠侣电影票,中秋节大放送,情侣们快来啊,只需一块钱", "销售价 ": 30.00 }, "sale": 30.00, "num": 1, "amount": 30.00, "cb": false, "productid": 1 }, 11 { "product": { "产品名称": "摇摇乐门票", "描述": "摇摇乐门票,中秋节大放送,情侣们快来啊,只需一块钱", "销售价 ": 30.00 }, "sale": 30.00, "num": 2, "amount": 60.00, "cb": false, "productid": 2 }, 12 { "product": { "产品名称": "杨过和小龙女电影票", "描述": "杨过和小龙女电影票,中秋节大放送,情侣们快来啊,只需一块钱", "销售价 ": 30.00 }, "sale": 30.00, "num": 1, "amount": 30.00, "cb": true, "productid": 3 }, 13 { "product": { "产品名称": "四川一日行旅游券", "描述": "小编忙,没时间维护" }, "sale": 30.00, "num": 3, "amount": 90.00, "cb": false, "productid": 4 }, 14 { "product": { "产品名称": "四川一日行旅游券", "描述": "小编忙,没时间维护" }, "sale": 30.00, "num": 1, "amount": 30.00, "cb": false, "productid": 5 } 15 ];
这里需要注意的是这两种数据结构,如果想要展示明细,那么没个单元格对应的值是一个{}对象,数据格式如:{"产品名称":"四川一日行旅游券","描述":"小编忙,没时间维护"}
,这里的“产品名称“将作为明细列名称;如果只需要展示名称不要明细,直接赋值成值就行了
其次,来看下可配置的参数说明,如下代码:
var defOption = { id: "", //要显示插件内容的div的Id (必需) header: header, //json格式列表头 (必需) data: data, //json格式数据 (必需) cbAllName: "cbAll", //全选框Name cbName: "cb", //每行选框Name tableId: "table" + new Date().getTime(), //tableId 默认时间格式 isTotal: false, //是否汇总 (默认不汇总) outTotalCols: "", //不汇总列 多个格式如:1,2 isCombine: false, //是否合并单元格 (默认不合并) outCombineCols: "", //不合并单元格列 多个格式如:1,2 back: function () { console.log("这里是回调"); } //回调函数 };
然后,代码逻辑步奏为:初始化列表头->初始化每行内容(每行加载的时候需要根据列表头部的type来判断应该加载什么html元素控件并且可以绑定初始化状态值)->全选绑定事件->绑定某个需要展示明细的单元格,动态加载明细内容数据->执行汇总->执行合并单元格(此处需要注意的是,不需要先汇总在合并单元格,如果顺序发生变化,汇总的格式将异常)->执行回调响应函数;这个就是主要的代码逻辑;
. 获取table插件数据,并提交给后台
首先,table元素展示出来后,如果作为用户提交的界面,还需要能获取出用户选择的数据,并提交给后台,我这里列举一个简单的获取列子,咋们可以直接在回调函数back中写提交表单的代码如:
1 var tbChoice = new tb_choice({ 2 id: "divShow", 3 data: data, 4 header: header, 5 outCols: "0,4,6", 6 isTotal: false, 7 back: function () { 8 9 $("#btnSave").click(function () { 10 11 var cbs = $("input[name='cb'][type='checkbox']:checked"); 12 if (cbs.length <= 0) { alert("请选择保险"); return; } 13 14 var param = []; 15 $.each(cbs, function (index, item) { 16 var tr = $(this).parent().parent(); 17 var id = $(tr).find("[name='id']").val(); 18 var num = $(tr).find("[name='num'] option:selected").val(); 19 var productId = $(tr).find("[name='productid']").val(); 20 21 param.push({ id: id, num: num, productId: productId }); 22 }); 23 24 //请求后台 25 $.post("Add.aspx", { op: orderId, t: "save", param: JSON.stringify(param) }, function (result01) { 26 27 var resultJson = JSON.parse(result01); 28 console.log(resultJson); 29 alert(resultJson.msg); 30 if (resultJson.status) { 31 32 location.reload(true); 33 } 34 }); 35 }); 36 } 37 });
我这里直接在回调back函数里面写ajax提交给后台数据,这里使用遍历table的每行需要的数据组合成json格式发送给后台,用起来还是挺简单的呢,因为获取的使用通过name属性来获取元素的值,而name对应的都是最开始列表头json数据的name,大家可以对比下
. 插件源码及帮助文档
首先,插件的源码待会在结尾全部发布,里面分别有汇总方法,合并列方法,和一个String的扩展方法大家可以分开使用,朋友们有兴趣的也可以看帮助文档,根据文档上面的页面列子下载代码也行;更多的展示效果可以来这里看文档table-shenniu;源码如下:
1 /// <reference path="../jquery-1.10.2.min.js" /> 2 3 4 var tb_total = function () { 5 6 return { 7 8 //汇总 9 //tableId:table元素的Id 10 //outCols:排除不合并的列,多个使用‘,’隔开 11 sumTab: function (tableId, outCols) { 12 13 if (!outCols) { outCols = ""; } 14 15 var rows = $("#" + tableId + " tr"); 16 17 var colLen = $(rows).eq(1).find("td[class!='hide']").length; 18 var totalVal = []; 19 for (var i = 0; i < colLen; i++) { 20 totalVal.push("0"); 21 } 22 $.each(rows, function (i, item) { 23 24 var tds = $(item).find("td[class!='hide']"); 25 var crossVal = ""; 26 $(tds).each(function (i_td, item_td) { 27 28 var hVal = $(item_td).html(); 29 30 if (isNaN(hVal)) { 31 //非数字 32 totalVal[i_td] = ""; 33 } else { 34 if (totalVal[i_td].length > 0) { 35 36 //数字 37 totalVal[i_td] = (parseFloat(totalVal[i_td]) + parseFloat(hVal)).toFixed(2); 38 } else { crossVal = hVal; } 39 } 40 }); 41 }); 42 43 var totalHtml = []; 44 totalHtml.push("<tr style='"); 45 for (var i in totalVal) { 46 47 totalHtml.push("<td align=\"center\">"); 48 49 if (i == 0) { 50 totalHtml.push("<font style='color:red'>合计</font>"); 51 totalHtml.push("</td>"); 52 continue; 53 } else if (outCols.length > 0) { 54 55 //表示存在排除列 56 if (("," + outCols + ",").indexOf("," + i + ",") > -1) { 57 totalHtml.push(""); 58 totalHtml.push("</td>"); 59 continue; 60 } 61 } 62 totalHtml.push(totalVal[i]); 63 totalHtml.push("</td>"); 64 } 65 totalHtml.push("</tr>"); 66 67 $("#" + tableId).append(totalHtml.join('')); 68 }, 69 70 //合并列 71 //tableId:table元素的Id 72 //outCols:排除不合并的列,多个使用‘,’隔开 73 uniteTab: function (tableId, col, outCols) { 74 75 if (!outCols) { outCols = ""; } 76 //col-- 需要合并单元格的列 1开始 77 var colLength = col; 78 var tb = document.getElementById(tableId); 79 if (!tb) { return; } 80 tb.style.display = ''; 81 var i = 0; 82 var j = 0; 83 var rowCount = tb.rows.length; // 行数 84 var colCount = tb.rows[0].cells.length; // 列数 85 var obj1 = null; 86 var obj2 = null; 87 //为每个单元格命名 88 for (i = 0; i < rowCount; i++) { 89 for (j = 0; j < colCount; j++) { 90 if (!tb.rows[i].cells[j]) { continue; } 91 tb.rows[i].cells[j].id = tableId + "tb__" + i.toString() + "_" + j.toString(); 92 } 93 } 94 //合并行 95 for (i = 0; i < colCount; i++) { 96 if (i == colLength) break; 97 //排除不合并列 98 if (("," + outCols + ",").indexOf("," + i + ",") > -1) { continue; } 99 100 obj1 = document.getElementById(tableId + "tb__0_" + i.toString()) 101 for (j = 1; j < rowCount; j++) { 102 obj2 = document.getElementById(tableId + "tb__" + j.toString() + "_" + i.toString()); 103 if (obj1.innerText == obj2.innerText) { 104 obj1.rowSpan++; 105 obj2.parentNode.removeChild(obj2); 106 } else { 107 obj1 = document.getElementById(tableId + "tb__" + j.toString() + "_" + i.toString()); 108 } 109 } 110 } 111 112 //合并列 113 for (i = 0; i < rowCount; i++) { 114 if (tb.rows[i] != null) { 115 colCount = tb.rows[i].cells.length; 116 obj1 = document.getElementById(tb.rows[i].cells[0].id); 117 if (obj1 != null) { 118 for (j = 1; j < colCount; j++) { 119 if (j >= colLength) break; 120 if (obj1.colSpan >= colLength) break; 121 122 if (tb.rows[i].cells[j]) { 123 obj2 = document.getElementById(tb.rows[i].cells[j].id); 124 125 if (obj1.innerText == obj2.innerText) { 126 obj1.colSpan++; 127 obj2.parentNode.removeChild(obj2); 128 j = j - 1; 129 } 130 else { 131 obj1 = obj2; 132 j = j + obj1.rowSpan; 133 } 134 } 135 } 136 } 137 } 138 } 139 140 } 141 } 142 } 143 144 //table 表插件,要求bootstrap.min.css样式 145 var tb_choice = function (option) { 146 147 //测试用例-列表头 148 var header = [ 149 150 { 151 "title": "产品名称", 152 "name": "product", 153 "type": "label" 154 }, 155 { 156 "title": "销售价", 157 "name": "sale", 158 "type": "label", 159 }, 160 { 161 "title": "份数", 162 "name": "num", 163 "type": "select", //如果是下拉框,这里需要初始值val 164 "val": [ 165 { 166 "text": 1, 167 "val": 1 168 }, 169 { 170 "text": 2, 171 "val": 2 172 }, 173 { 174 "text": 3, 175 "val": 3 176 } 177 ] 178 }, 179 { 180 "title": "总额", 181 "name": "amount", 182 "type": "label" 183 }, 184 { 185 "title": "<input type='checkbox' name='cbAll' style='border: 0px; width: 20px; height: 20px;'/>", //如果是全选选择框,这里直接写html元素 186 "name": "cb", 187 "type": "checkbox" 188 }, 189 { 190 "title": "保险Id", 191 "name": "productid", 192 "type": "hidden" 193 } 194 ]; 195 196 //测试用例-没行数据 197 var data = [ 198 { "product": "神雕侠侣电影票", "sale": 30.00, "num": 1, "amount": 30.00, "cb": false, "productid": 2 }, 199 { "product": "摇摇乐门票", "sale": 30.00, "num": 2, "amount": 60.00, "cb": false, "productid": 1 }, 200 { "product": "神雕侠侣电影票", "sale": 30.00, "num": 1, "amount": 30.00, "cb": true, "productid": 2 }, 201 { "product": "摇摇乐门票", "sale": 30.00, "num": 3, "amount": 90.00, "cb": false, "productid": 1 } 202 ]; 203 204 data = [ 205 { "product": { "产品名称": "神雕侠侣电影票", "描述": "神雕侠侣电影票,中秋节大放送,情侣们快来啊,只需一块钱", "销售价 ": 30.00 }, "sale": 30.00, "num": 1, "amount": 30.00, "cb": false, "productid": 1 }, 206 { "product": { "产品名称": "摇摇乐门票", "描述": "摇摇乐门票,中秋节大放送,情侣们快来啊,只需一块钱", "销售价 ": 30.00 }, "sale": 30.00, "num": 2, "amount": 60.00, "cb": false, "productid": 2 }, 207 { "product": { "产品名称": "杨过和小龙女电影票", "描述": "杨过和小龙女电影票,中秋节大放送,情侣们快来啊,只需一块钱", "销售价 ": 30.00 }, "sale": 30.00, "num": 1, "amount": 30.00, "cb": true, "productid": 3 }, 208 { "product": { "产品名称": "四川一日行旅游券", "描述": "小编忙,没时间维护" }, "sale": 30.00, "num": 3, "amount": 90.00, "cb": false, "productid": 4 }, 209 { "product": { "产品名称": "四川一日行旅游券", "描述": "小编忙,没时间维护" }, "sale": 30.00, "num": 1, "amount": 30.00, "cb": false, "productid": 5 } 210 ]; 211 212 var defOption = { 213 id: "", //要显示插件内容的div的Id (必需) 214 header: header, //json格式列表头 (必需) 215 data: data, //json格式数据 (必需) 216 cbAllName: "cbAll", //全选框Name 217 cbName: "cb", //每行选框Name 218 tableId: "table" + new Date().getTime(), //tableId 默认时间格式 219 220 isTotal: false, //是否汇总 (默认不汇总) 221 outTotalCols: "", //不汇总列 多个格式如:1,2 222 isCombine: false, //是否合并单元格 (默认不合并) 223 outCombineCols: "", //不合并单元格列 多个格式如:1,2 224 back: function () { 225 226 console.log("这里是回调"); 227 } //回调函数 228 }; 229 230 $.extend(defOption, option); 231 232 var tbArr = []; 233 tbArr.push('<table id="{0}" class="table table-hover table-bordered text-center ">'.format([defOption.tableId])); 234 235 //头部 236 tbArr.push('<thead><tr>'); 237 $.each(defOption.header, function (i, item) { 238 tbArr.push("<th {1}>{0}</th>".format([item.title, (item.type == "hidden" ? "class='text-center hide'" : "class='text-center'")])); 239 }); 240 tbArr.push('</tr></thead>'); 241 242 //内容 243 $.each(defOption.data, function (i, item) { 244 245 tbArr.push("<tr >"); 246 $.each(item, function (key, val) { 247 248 var htype = []; 249 $.each(defOption.header, function (_, headItem) { 250 251 if (headItem.name == key) { 252 253 switch (headItem.type) { 254 case "label": 255 if (typeof (val) == "object") { 256 257 //只循环一次 258 $.each(val, function (valKey, valItem) { 259 htype.push("<label data-item='{1}' style=' cursor:pointer'>{0}</label>".format([valItem, JSON.stringify(val)])); 260 return false; 261 }); 262 } else { 263 htype.push(val); 264 } 265 break; 266 case "select": 267 htype.push("<select name='{0}'>".format([key])); 268 $.each(headItem.val, function (s_i, s_item) { 269 htype.push("<option value='{1}' {2}>{0}</option>".format([s_item.text, s_item.val, (s_item.val == val ? "selected='selected'" : "")])); 270 }); 271 htype.push("</select>"); 272 break; 273 case "text": 274 htype.push("<input name='{1}' type='text' class='form-control' value='{0}'/>".format([val, key])); 275 break; 276 case "checkbox": 277 htype.push("<input type='checkbox' name='{1}' {2} value='{0}' style='border: 0px; width: 20px; height: 20px;'/>".format([val, key, val ? "checked='checked'" : ""])); 278 break; 279 case "hidden": 280 htype.push("<input type='hidden' name='{1}' value='{0}'/>".format([val, key])); 281 break; 282 } 283 tbArr.push("<td style='font-weight: bold;' {1}>{0}</td>".format([htype.join(''), (headItem.type == "hidden" ? "class='hide'" : "class='text-center-vertical'")])); 284 return; 285 } 286 }); 287 }); 288 tbArr.push("</tr>"); 289 }); 290 tbArr.push('</table>'); 291 292 $("#" + option.id).html(tbArr.join('')); 293 294 //全选绑定 295 $("input[type='checkbox'][name='" + defOption.cbAllName + "']").click(function () { 296 297 var status = $(this).prop("checked"); 298 if (status) { 299 $("input[type='checkbox'][name='" + defOption.cbName + "']").prop({ "checked": true }); 300 } else { 301 $("input[type='checkbox'][name='" + defOption.cbName + "']").prop({ "checked": false }); 302 } 303 }); 304 //label明细展示绑定 305 $("label[data-item]").click(function () { 306 var dataItem = $(this).attr("data-item"); 307 if (dataItem) { 308 var tr = $(this).parent().parent(); 309 var nextTr = tr.next(); 310 var nextName = nextTr.attr("data-item"); 311 if (!nextName) { 312 313 var data = JSON.parse(dataItem); 314 var childTab = []; 315 childTab.push('<table class="table table-bordered">'); 316 var childHeader = ""; 317 var childContent = ""; 318 $.each(data, function (key, item) { 319 childHeader += "<td>{0}</td>".format([key]); 320 childContent += "<td>{0}</td>".format([item]); 321 }); 322 childTab.push("<tr>{0}</tr>".format([childHeader])); 323 childTab.push("<tr>{0}</tr>".format([childContent])); 324 childTab.push("</table>"); 325 326 var colsLen = tr.find("td[class!='hide']").length; 327 var appendDes = $("<tr data-item='{2}'><td colspan='{1}' class='text-left'>{0}</td></tr>".format([childTab.join(''), colsLen, dataItem])); 328 tr.after(appendDes); 329 } else { 330 nextTr.remove(); 331 } 332 } 333 }); 334 335 //单元格操作 336 var tbTotal = new tb_total(); 337 //汇总 338 if (defOption.isTotal) { tbTotal.sumTab(defOption.tableId, defOption.outTotalCols); } 339 //合并单元格 340 if (defOption.isCombine) { tbTotal.uniteTab(defOption.tableId, 2, defOption.outCombineCols); } 341 342 //执行回调响应函数 343 defOption.back(); 344 } 345 346 String.prototype.format = function (arrVal) { 347 348 if (!Array.isArray(arrVal)) { return this; } 349 350 var str = this; 351 for (var i = 0; i < arrVal.length; i++) { 352 353 str = str.replace("{" + i + "}", arrVal[i]); 354 } 355 return str; 356 }
本博客Android APP 下载 |
支持我们就给我们点打赏 |
支付宝打赏 支付宝扫一扫二维码 |
微信打赏 微信扫一扫二维码 |
如果想下次快速找到我,记得点下面的关注哦!