为了在 CardSimulate 项目中方便的显示技能和效果列表, 决定重构以前编写的一段 JavaScript 代码 --att 表格绘制库, 这段代码的作用是将特定的 JavaScript 数据对象转化为表格, 支持精细的样式设置和一些复杂报表功能并且提供了自由的扩展性. 可以用较新的 Chrome 浏览器访问 https://ljzc002.github.io/Att/HTML/TEST/AttSample.html 查看新版代码的例子, 旧版代码的介绍见: https://www.cnblogs.com/ljzc002/p/5511510.html.
1, 从表格类初始化表格对象
旧版的代码直接将表格对象作为一个全局变量, 新版代码则定义了一个表格类, 而每一个表格对象则是表格类的实例, 这样就可以方便的在一个页面里添加多个表格对象, 并有条理的管理多个表格的工作流程和属性. 表格对象的初始化代码如下:
- /**
- * Created by Administrator on 2015/5/11.
- */
- // 动态画表类, 尝试使用自包含结构
- //2016/8/31 在表格中加入更多的格式选择
- //2018/10/31 重构 att6 框架为 att7 版本
- Att7=function()
- {
- }
- Att7.prototype.init=function(param)// 只初始化对象的属性, 不实际绘制
- {
- try
- {
- this.base=param.base;// 表格的容器对象
- this.id=param.id;// 表格的 id
- //this.left=param.left?param.left:0;// 在容器对象内的左侧距离 -> 认为 tab_data 和 div_table 完全重合
- //this.top=param.top?param.top:0;// 上部距离
- this.rowsp=param.rowsp?param.rowsp:50;// 默认每页显示 50 条数据, 输入负值表示无限制
- //this.page_current=param.page_current?param.page_current:0;// 默认显示数据集的第一页, 初始索引为 0
- this.isStripe=param.isStripe?param.isStripe:1;// 这种三目运算不适用于布尔值!!!! 默认奇偶行使用不同颜色
- this.isThlocked=param.isThlocked?param.isThlocked:0;// 默认不锁定表头
- this.isCollocked=param.isCollocked?param.isCollocked:0;// 默认不锁定表列
- this.showIndex=param.showIndex?param.showIndex:1;// 默认在左侧显示行号
- this.baseColor=param.baseColor?param.baseColor:"#ffffff";// 默认背景色为白色, 间隔色为背景色亮度降低十六分之一
- this.stripeColor=param.stripeColor?param.stripeColor:"#eeeeee";// 有时要求奇数行和偶数行使用不同的颜色
- this.pickColor=param.pickColor?param.pickColor:"#97ceef";// 选择了某一行时要突出显示这一行
- this.div_temp1=document.createElement("div");// 这几个 div 用来对背景颜色进行比较, 因为不同的浏览器对背景颜色的保存方式不同
- this.div_temp1.style.backgroundColor=this.baseColor;// 有的用小写字母有的用大写字母, 有的用 rgb + 数字, 所以这里主动建立 div
- this.div_temp2=document.createElement("div");// 在同样的保存方式下对颜色进行比较
- this.div_temp2.style.backgroundColor=this.stripeColor;
- this.div_temp3=document.createElement("div");
- this.div_temp3.style.backgroundColor=this.pickColor;
- this.str_indexwid=param.str_indexwid?param.str_indexwid:"100px";// 索引列的宽度
- this.num_toolhei=param.num_toolhei?param.num_toolhei:80;// 表格上部的工具区的高度
- // 固有属性, 点击某些单元格时可以打开的小窗口
- this.html_onclick="<div class=\"div_inmod_lim\"style=\"width: 100%;height: 100%;margin: 0px;border: 1px solid;padding: 0px;" +
- "float: left;line-height: 20px\"> " +
- "<div class=\"div_inmod_head\"style=\"width: 100%;height: 20px;background-color: #E0ECFF;margin:0;border: 0;padding:0;border-bottom: 1px solid\">" +
- "<span style=\"float: left;margin-left: 2px\"> 详情 </span>" +
- "<BUTTON style=\'float:right;aposition:static; width: 14px;height: 14px; margin: 0;margin-top: 2px;margin-right:2px;padding: 0;" +
- "background: url(../../ASSETS/IMAGE/close.png) no-repeat;border: 0px;vertical-align:top\' onclick=\"delete_div(\'div_bz\');\" type=submit></BUTTON> " +
- "</div>" +
- "<textarea class=\"div_inmod_lim_content\"style=\"width: 100%;height: 98px;overflow-x: hidden;margin:0;border: 0;padding:0\"contenteditable=\"false\"></textarea> </div>";
- this.html_onmouseover=// 鼠标移入时弹出的小文本提示框
- "<div class=\"div_inmod_lim\" " +
- "style=\"width: 100%;height: 100%;margin: 0px;border: 1px solid;padding: 0px;float: left;line-height: 20px\">" +
- "<textarea class=\"div_inmod_lim_content\"style=\"width: 100%;height: 100%;overflow-x: hidden;margin:0;border: 0;padding:0\"contenteditable=\"false\">" +
- "</textarea>" +
- "</div>";
- }
- catch(e)
- {
- console.log("表格初始化异常!"+e);
- return false;
- }
- return "ok";
- }
这里设置了表格对象的各项属性, 第 28 到 33 行用不显示的 div 解决了 dom 标签颜色比较问题, 第 37 到 50 行定义了两个窗口小控件以备后续调用. init 方法的调用方式如下:
- var table1=new Att7();
- var objp={
- base:"div_tab",
- id:"table1",
- //left:50,
- //top:50,
- rowsp:999,
- isThlocked:1,
- isCollocked:2,// 不包括索引列?-》包括
- baseColor:"#00ff00",
- stripeColor:"#00aa00",
- pickColor:"#97ceef"
- }
- if(table1.init(objp)=="ok")
- {// 下面是数据显示
2, 表格容器的建立:
表格显示时 dom 结构如下:
其中 all_base 是所有表格相关元素的总容器, div_tool 是表格上面的工具区, 里面可以放置一些选择筛选条件的控件, div_tab 是表格主体所在的区域, table1 是根据数据生成的表格 dom, 三个 div_mask 是锁定表头或者锁定表列时使用的遮罩层 dom.
使用的样式表文件如下:
- /* 专用于表格框架的样式 */
- body{ margin: 0; padding: 0; border: 0; text-align: center; overflow: hidden;width: 100%;
- height: 100%;position: fixed; font-family: verdana,arial,sans-serif; touch-action: none;
- -ms-touch-action: none;font-size: 12px;min-width: 600px;}
- #all_base{min-height: 576px;min-width: 1024px;height: 100%;width:100%;position: relative;overflow-x:auto;overflow-y: hidden;}
- /* 表格的属性 */
- td input{ height: 100%; width: 100%; border:0; text-align: center; background-color: inherit;}
- .div_tab{float: left;position: relative;width:4000px;overflow-x: hidden;overflow-y: scroll}
- .div_tab td{ text-align: center; /*border: solid 1px #008000;*/ border-right:solid 1px #008000; border-bottom: solid 1px #008000;
- line-height: 16px; font-size: 13px; height: 24px; padding: 1px; background-color: inherit; Word-break: keep-all;
- /*display: inline-block*/}
- .div_tab th{ text-align: center; /*border: solid 1px #008000;*/ line-height: 16px; font-size: 13px; height: 36px;
- padding: 1px; text-align: center; border-right: solid 1px #008000; border-bottom: solid 1px #008000; Word-break: keep-all;
- white-space:nowrap; overflow: hidden; text-overflow: ellipsis;/*display: inline-block*/}
- .div_tab table{ float: left; width: auto; border-right-width:0px; border: solid 1px #008000; table-layout: fixed;}
- .div_tab tr{ width: auto; vertical-align: middle; /*border: solid 1px #008000;*/ padding: 1px;}
- td a{ cursor: pointer;}
- td button{ cursor: pointer;}
- .div_mask2{ display:block; left: 0px; top: 0px; /*filter: alpha(opacity=50); opacity: 0.50;*/ overflow: hidden;/* 锁定的表头表列 */
- position: absolute; float: left; overflow-x: hidden}
- table{ border-spacing:0;}
- .div_mask2 td{ text-align: center; /*border: solid 1px #008000;*/ border-right:solid 1px #008000; border-bottom: solid 1px #008000;
- line-height: 16px; font-size: 13px; height: 24px; padding: 1px; background-color: inherit; Word-break: keep-all;}
- .div_mask2 th{ text-align: center; /*border: solid 1px #008000;*/ line-height: 16px; font-size: 13px; height: 36px;
- padding: 1px; text-align: center; border-right: solid 1px #008000; border-bottom: solid 1px #008000; Word-break: keep-all;
- white-space:nowrap; overflow: hidden; text-overflow: ellipsis;}
- .div_mask2 table{ float: left; width: auto; border-right-width:0px; border: solid 1px #008000; table-layout: fixed;
- position: absolute;}
- .div_mask2 tr{ width: auto; vertical-align: middle; /*border: solid 1px #008000;*/ padding: 1px;}
- .combo-panel li{ float:none;}
- .btn_limlen{ /*float: left;*/ height: 20px; width: 20px; border: 1px solid; /*margin-top: 6px;*/ /*margin-left: 4px;*/
- background: url(../ASSETS/IMAGE/play.PNG) no-repeat; position: absolute; -moz-border-radius: 3px; /* Gecko browsers 圆角 */
- -webkit-border-radius: 3px; /* Webkit browsers */ border-radius:3px; /* W3C syntax */ position: absolute;
- top: 6px; right: 4px;}
遗憾的是, 因为上述 CSS 的调试过程太长, 以至于已经忘记了这样设置的原因, 如果您使用时出现莫名其妙的元素错位, 请自己调试.
3, 启动表格绘制
通过表格对象的 draw 方法启动表格绘制
调用 draw 方法的方式如下:
- if(table1.init(objp)=="ok")
- {
- var obj_datas=[
- "测试表格",
- ["测试表头","测试表头","测试表头","测试表头","测试表头","测试表头","测试表头","测试表头"],
- ["str"
- ,"limit"
- ,["switch",["value1","text1"],["value2","text2"]]
- ,["input",["class1"],["height","10px"]]
- ,["select","class2",[["value1","text1"],["value2","text2"],["value3","text3"]],"onChange()"]
- ,["check","class3"]
- ,["button","class4","按钮","80px",["height","10px"]]
- ,["a","class5",["height","10px"]]
- ],
- [100,200,300,400,500,600,700,800],
- ["value1","value2value2value2value2value2value2value2value2value2value2","value1","value2","value1","value2","value1","value2"],
- ["value1","value2","value1","value2","value1","value2","value1","value2"]
- ,["value1","value2","value1","value2","value1","value2","value1","value2"]
- ];
- table1.draw(obj_datas,0);// 显示数据 obj_datas 的第 0 行
- requestAnimFrame(function(){table1.AdjustWidth();});
- }
其中 obj_datas 是一个自定义的数据对象, 这个对象可能从后端程序发送过来也可能是在前台组装生成. requestAnimFrame 是截取自谷歌 WebGL 工具库的一个方法, 用来 "延时一会", 等待浏览器完成表格容器渲染后, 再调整表格尺寸从而使表格布局紧密.
延时代码如下:
- // Copyright 2010, Google Inc.
- Windows.requestAnimFrame = (function() {
- return Windows.requestAnimationFrame ||
- Windows.webkitRequestAnimationFrame ||
- Windows.mozRequestAnimationFrame ||
- Windows.oRequestAnimationFrame ||
- Windows.msRequestAnimationFrame ||
- function(/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) {
- Windows.setTimeout(callback, 1000/60);
- };
- })();
4, 表格绘制代码的介绍:
a, 首先做一些和表格翻页有关的准备工作:
- Att7.prototype.draw=function(data,page_current)// 实际绘制 dom 元素
- {
- this.totalpages=0;// 记录下一共有多少页
- if(this.rowsp>0)
- {
- this.totalpages=Math.ceil((data.length-4)/this.rowsp);
- }
- if(this.totalpages==0)
- {
- this.totalpages=1;
- }
- // 计算当前页数
- if(page_current<0)
- {
- alert("到达数据首页!");
- this.page_current=0;
- }
- else if(page_current>=this.totalpages)
- {
- alert("到达数据末尾");
- this.page_current=this.totalpages-1;
- }
- else
- {
- this.page_current=page_current;
- }
因为 att 将所有 dom 标签的生成工作放在浏览器端, 所以可以一次性将所有数据从后台读取到前端, 由前端 JavaScript 程序进行分页操作.(而传统表格绘制工具多把 dom 标签生成放在后台程序中, 为了降低后台压力, 分页操作多在数据库层面进行)
翻页方法代码如下:
- // 翻页处理
- Att7.prototype.ChangePage=function(flag)
- {
- document.body.style.cursor='wait';
- switch(flag)// 不同的翻页动作对应不同的页号处理
- {
- case "0":
- {
- this.page_current=0;
- break;
- }
- case "+":
- {
- this.page_current++;
- break;
- }
- case "-":
- {
- this.page_current--;
- break;
- }
- case "9999":
- {
- this.page_current=9999;
- break;
- }
- }
- this.draw(this.data,this.page_current);
- document.getElementById('t_page_span').innerHTML=this.totalpages;
- try {// 万一没有定义
- AdjustColor();
- }
- catch(e)
- {
- }
- document.getElementById('c_page_span').innerHTML=this.page_current+1;
- document.body.style.cursor='default';
- var _this=this;
- try
- {
- requestAnimFrame(function () {
- _this.AdjustWidth()
- });
- }
- catch(e)
- {
- }
- }
根据 ChangePage 方法的不同参数, 可以进行四种不同的翻页操作, 您可以再需要的地方建立四个按钮来对应这些操作, 而翻页操作实际上只是改变了参数的 draw 方法. t_page_span 和 c_page_span 是两个 span 标签, 用来显示总页数和当前页数. AdjustColor 是一个可选的方法, 在绘制表格后遍历单元格, 根据需求改变符合某种条件的单元格的颜色.(这里并未使用)
b, 在开始绘制之前清理以前可能绘制过的 id 相同的表格:
- // 接着上面的翻页准备
- this.data=data;// 表格的数据集
- var tab_data;//table 标签
- var tab_colmask;// 列锁定遮罩标签
- if (document.getElementById(this.id))// 如果已有该表
- {// 清理已有的 dom
- tab_data= document.getElementById(this.id);
- var parent = tab_data.parentNode;
- parent.removeChild(tab_data);
- if(document.getElementById("div_thmask"))// 删除锁定表头的遮罩层
- {
- var div =document.getElementById("div_thmask");// 看来这样的设定还不能支持一个页面中同时存在多个锁定表头表格
- div.parentNode.removeChild(div);
- }
- if(document.getElementById("tab_mask2"))// 删除锁定表列的遮罩层
- {
- var tab =document.getElementById("tab_mask2");
- tab.parentNode.removeChild(tab);
- }
- if(document.getElementById("div_thmask3"))//
- {
- var tab =document.getElementById("div_thmask3");
- tab.parentNode.removeChild(tab);
- }
- }
- tab_data = document.createElement("table");// 重新建立 table 标签
- tab_data.id = this.id;
- tab_data.cellPadding = "0";
- tab_data.cellSpacing = "0";
- tab_data.style.position = "absolute";
- //tab_data.style.top = this.top + "px";
- //tab_data.style.left = this.left + "px";
- var div_table;// 包含表格的容器元素
- var obj=this.base;// 这个属性可能是 id 字符串也可能是对象本身
- if((typeof obj)=="string"||(typeof obj)=="String")
- {
- div_table = document.getElementById(obj);
- }
- else
- {
- div_table=obj;
- }
- div_table.innerHTML="";
- div_table.appendChild(tab_data);// 将 table 标签放入容器里
- this.div_table=div_table;
- tab_data = document.getElementById(this.id);
c, 表格表头的绘制与遮罩原理
在一个简单的表格里绘制表头并不复杂:
- var tr1 = document.createElement("tr");// 填写表头 (接着清理代码)
- if(this.showIndex==1)// 如果显示索引列
- {
- this.InsertaTHStr(tr1, "第"+(this.page_current+1) + "页",this.str_indexwid);//IE8 中缺少参数会报错
- }
- for (var k = 0; k <data[1].length; k++)
- {
- this.InsertaTHStr(tr1, data[1][k],(data[3][k]+"px"));
- }
- tab_data.appendChild(tr1);// 将 tr 放入 table
- tr1.style.backgroundColor=this.baseColor;
如果选择显示索引列, 则在表头的最左侧多插入一个 th,InsertaTHStr 方法用来向指定 tr 中插入 th, 参数分别是 tr 对象, 列名, 列宽, 这里的 data 也就是之前构造的数据集.
InsertaTHStr 代码如下:
- // 一些工具方法
- /**
- * 向一个表行中添加字符型表头元素
- * @param tr 表行 ID
- * @param str 添加字符
- * @param wid 列宽 (字符型 px)
- * @constructor
- */
- Att7.prototype.InsertaTHStr=function(tr,str,wid)
- {
- var th=document.createElement("th");
- th.style.width=wid?wid:"200px";
- if(str==null)
- {
- str="";
- }
- th.appendChild(document.createTextNode(str));
- tr.appendChild(th);
- }
然而当需要锁定表头或者锁定表列时, 事情变得复杂, 接着绘制表头的代码:
- this.arr_lock=[];//all_base 左右滑动时需要调整位置的元素
- this.arr_locky=[];
- if(this.isThlocked==1)// 绘制锁定表头的遮罩层, 它的内容和原表格的表头是一样的
- {
- var div_thmask=document.createElement("div");
- div_thmask.className="div_mask2";
- div_thmask.id="div_thmask";
- div_thmask.style.zIndex="200";
- var div_parent=div_table.parentNode;
- this.div_parent=div_parent;
- div_thmask.style.top=(compPos2(div_table).top-parseInt(div_table.style.height.split("p")[0]))+this.top+"px";// 定位添加的遮罩层
- div_thmask.style.left=compPos2(div_table).left+this.left+"px";
- div_thmask.style.width="6000px";// 遮罩的最大宽度
- div_thmask.style.height="42px";
- div_thmask.style.top=this.num_toolhei+"px";
- //div_thmask.getElementsByTagName("table")[0].style.backgroundColor=this.baseColor;
- var tab_thmask= document.createElement("table");
- var tr_thmask=document.createElement("tr");
- if(this.showIndex==1)// 如果不禁止索引列
- {
- this.InsertaTHStr(tr_thmask, "第" + (this.page_current + 1) + "页", this.str_indexwid);//IE8 中缺少参数会报错
- }
- for (var k = 0; k < data[1].length; k++)
- {
- this.InsertaTHStr(tr_thmask, data[1][k],(data[3][k]+"px"));
- }
- tab_thmask.appendChild(tr_thmask);
- tab_thmask.style.backgroundColor=this.baseColor;
- div_thmask.appendChild(tab_thmask);
- div_parent.appendChild(div_thmask);
- }
- if(this.isCollocked>0)// 绘制锁定表列的遮罩层, 估计不需要外包装的 div, 可以和 data_table 共享 div_table(考虑到层数决定这样做)
- {
- this.arr_lock.push(["tab_mask2",1,0]);// 第一个参数是要锁定的标签的 id, 第二个是是否锁定, 第三个是标签的初始水平偏移量
- this.arr_lock.push(["div_bz",0,0]);
- tab_colmask= document.createElement("table");
- tab_colmask.cellPadding = "0";
- tab_colmask.cellSpacing = "0";
- tab_colmask.style.position = "absolute";
- tab_colmask.className="div_mask2";
- tab_colmask.id="tab_mask2";
- tab_colmask.style.zIndex="150";
- tab_colmask.style.top="0px";
- tab_colmask.style.backgroundColor=this.baseColor
- var tr_mask= document.createElement("tr");// 创造一个占位用的表头行
- if(this.showIndex==1)// 如果不禁止索引列
- {
- this.InsertaTHStr(tr_mask, "第" + (this.page_current + 1) + "页", this.str_indexwid);
- }
- for (var k = 0; k <this.isCollocked-1; k++)
- {
- this.InsertaTHStr(tr_mask, data[1][k],(data[3][k]+"px"));
- }
- tab_colmask.appendChild(tr_mask);
- }
- // 如果同时锁定了表头和左侧的表列
- if((this.isThlocked==1)&&(this.isCollocked>0))
- {
- this.arr_lock.push(["div_thmask3",1,0]);
- var div_thmask=document.createElement("div");
- div_thmask.className="div_mask2";
- div_thmask.id="div_thmask3";
- div_thmask.style.zIndex="250";
- var div_parent=div_table.parentNode;
- div_thmask.style.top=(compPos2(div_table).top-parseInt(div_table.style.height.split("p")))+"px";// 定位添加的遮罩层
- div_thmask.style.left=compPos2(div_table).left+"px";
- div_thmask.style.width="4000px";
- div_thmask.style.height="42px";
- div_thmask.style.top=this.num_toolhei+"px";
- var tab_thmask= document.createElement("table");
- tab_thmask.style.backgroundColor=this.baseColor;
- var tr_thmask=document.createElement("tr");
- if(this.showIndex==1)// 如果不禁止索引列
- {
- this.InsertaTHStr(tr_thmask, "第" + (this.page_current + 1) + "页", this.str_indexwid);//IE8 中缺少参数会报错
- }
- for (var k = 0; k <this.isCollocked-1; k++)
- {
- this.InsertaTHStr(tr_thmask, data[1][k],(data[3][k]+"px"));
- }
- tab_thmask.appendChild(tr_thmask);
- div_thmask.appendChild(tab_thmask);
- div_parent.appendChild(div_thmask);
- }
实现表头表列锁定的思路是这样的: 首先 all_base 的大小固定为 all_base 的容器的大小 (在这里等于窗口大小), 然后把 div_table 设置的足够宽 (默认 4000px), 而高度则设为 all_base 高度减 div_tools 高度的有限值, 这样当 table 的行数较多且 div_table 获得焦点时即可用鼠标滚轮控制 div_table 的内容的上下滚动, 而因为 div_table 的宽度超过 all_base,div_table 的上下滑动条被隐藏起来.
在 div_table 的内容上下滚动时, 因为 div_thmask 和 div_thmask3 在 div_table 外相对于 all_base 定位, 所以不会受 div_table 滚动的影响, 再将 z-index 设高一些, 看起来就是表格内容滚动而表头锁定不变.
至于表列锁定, 首先禁用 all_base 的上下滑动, 只保留左右滑动, 因为 div_table 比 all_base 宽, 所以 all_base 的左右滑动条一直存在, 监听 all_base 滑动条的滑动事件, 在每次滑动时调整 div_mask2 的水平位置, 即可达到看起来锁定了表列的效果.
在同时锁定了表头和表列时, div_thmask3 位于这几个遮罩的最上层, 表现二者共同起作用的效果.
all_base 滑动的响应方法如下:
- Att7.prototype.ScrollLock=function()// 拖动滑动条时, 弹出层随拖动一同移动
- {
- var mask2left=0;
- var mask2top=0;
- var scrollleft=document.getElementById("all_base").scrollLeft;//scrollLeft 指滑动条向右滑动的距离
- var scrolltop=document.getElementById("all_base").scrollTop;
- var arr_lock=this.arr_lock;
- var arr_locky=this.arr_locky;
- var leng=arr_lock.length;
- for(var i=0;i<leng;i++)
- {
- if(arr_lock[i][1]==1)
- {
- //$("#"+arr_lock[i][0]).CSS("left",mask2left+scrollleft+arr_lock[i][2]+"px");
- document.getElementById(arr_lock[i][0]).style.left=mask2left+scrollleft+arr_lock[i][2]+"px";
- }
- }
- var leng2=arr_locky.length;
- for(var i=0;i<leng2;i++)
- {
- if(arr_locky[i][1]==1)
- {
- //$("#"+arr_locky[i][0]).CSS("top",mask2top+scrolltop+arr_locky[i][2]+"px");
- document.getElementById(arr_locky[i][0]).style.top=mask2top+scrolltop+arr_locky[i][2]+"px";
- }
- }
- }
在实际使用中发现, 虽然锁定遮罩里的内容和原表格里的内容相同, 但实际渲染时总会出现尺寸偏差, 所以在完成渲染后执行 AdjustWidth 方法重新调整遮罩的宽度:
- // 不断修正让遮罩层的宽高和底层一致
- Att7.prototype.AdjustWidth=function()
- {
- if(document.getElementById("div_thmask"))
- {
- var ths_mask = document.getElementById("div_thmask").getElementsByTagName("th");
- var ths = document.getElementById(this.id).getElementsByTagName("th");
- if (ths[0].offsetWidth) {// 有宽度说明浏览器已经完成了渲染操作
- this.div_table.style.height=this.div_parent.offsetHeight-this.num_toolhei-12+"px";// 调整 div_table 高度
- var leng = ths.length;
- for (var i = 0; i < leng; i++) {
- try {
- ths_mask[i].style.width = (ths[i].offsetWidth - 3) + "px";
- }
- catch (e) {
- //i--;
- continue;
- }
- }
- if (document.getElementById("div_thmask3")) {
- var div_thmask3 = document.getElementById("div_thmask3").getElementsByTagName("th");
- var leng2 = div_thmask3.length;
- for (var i = 0; i < leng2; i++) {
- div_thmask3[i].style.width = (ths[i].offsetWidth - 3) + "px";
- }
- }
- if (document.getElementById("tab_mask2"))
- {
- var trs_mask = document.getElementById("tab_mask2").getElementsByTagName("tr");
- var trs = document.getElementById(this.id).getElementsByTagName("tr");
- var leng3 = trs.length;
- for (var i = 1; i < leng3; i++)
- {
- trs_mask[i].style.height =(trs[i].offsetHeight)+"px";
- }
- }
- }
- else {// 如果还没有完成渲染, 则再延时调用一次
- var _this=this;
- requestAnimFrame(function () {
- _this.AdjustWidth()// 需要注意的是延时操作或者事件触发时, 原来的 this 对象已经随着时间的推移被释放掉了, 所以用_this 保持这个对象
- });
- }
- }
- }
如果您想为表格添加动态调整列宽功能, 可以在列宽变化后调用这个方法; 或者如果您想在浏览器尺寸变化后保持 div_table 和 all_bas 的紧密贴合也可以将这个方法设为 resize 事件的响应.
d, 绘制最简单的表格内容:
- // 接着上面的表头绘制
- if (this.rowsp> 0)// 默认必须要分页, 数据集的第一行是表名, 第二行是列名, 第三行是列设定, 第四行是列宽, 第五行开始是数据
- {
- var rows=this.rowsp;// 每一页多少行
- var pages=this.page_current;// 当前页
- var collock=this.isCollocked;// 锁定几个表列
- var count=0;// 标记经过了几个没有数据源的列, 存在按钮等不填写源数据的列时, data[2] 会比 data[l] 长, 为了让后面的类型和数据对应上, 应该用 m 减去 count!
- var count_none=0;// 标记经过了几个使用数据源但不显示的列,
- for (var l = 4 + pages * rows; l <data.length && (l - pages * rows) < rows + 4; l++)
- {// 遍历当前页中的每一条数据
- //dataObj2.push(data[l]);
- count=0;// 绘制每一行时都把标记数设为 0, 其后每检测到一个标记就 + 1,data[l][m+count] 从数据源取数
- count_none=0;
- var tr2 = document.createElement("tr");// 填写一个表行
- var tr_mask = document.createElement("tr");// 准备给遮罩层用
- if (l % 2 == 0&&this.isStripe==1)// 偶数的数据行显示为间隔色
- {
- tr2.style.backgroundColor = this.stripeColor;
- tr_mask.style.backgroundColor = this.stripeColor;
- }
- else
- {
- tr2.style.backgroundColor = this.baseColor;
- tr_mask.style.backgroundColor = this.baseColor;
- }
- if(this.showIndex==1)// 如果不禁止索引列
- {
- this.InsertaTDPick(tr2, l - 3 + "");// 这个是序号
- this.InsertaTDPick2(tr_mask, l - 3 + "", this.id);// 遮罩层的序号
- }
- for (var m = 0; m < data[2].length; m++)// 一行中的一个单元格, 这里可能有多种变化, 在 length 范围外的数据列不会被考虑
- {// 根据数据源的第三个元素中存储的 DOM 信息, 为数据的每一列设置不同的控件类型!!!!
- try
- {
- if (data[2][m] == "str") // 简单的字符类型, 要限制下宽度!
- {
- this.InsertaTDStr(tr2, data[l][m - count],(data[3][m-count_none]+"px"));
- if(this.isCollocked>0&&(m+1)<this.isCollocked)
- {
- this.InsertaTDStr(tr_mask, data[l][m - count],(data[3][m-count_none]+"px"));
- }
- }
在实际使用中发现每一行数据集的元素数和表格每一行的列数并不总是能一一对应, 有时表格的列数比数据集元素多, 比如不包含数据集的控件, 有时表格宽度比数据集短, 比如某一列数据需要设定为 "不可见", 为此设置了 count 和 count_none 两个计数器对表格和数据集的索引进行调整.
接下来设置每一个数据 tr 的颜色, 并在需要时向 tr 推入显示行号的索引列单元格.
然后遍历数据集这一行的每个数据, 根据设置的单元格类型, 向 tr 中推入单元格, 对于最简单的 str 型单元格使用 InsertaTDStr 方法向 tr 中添加, 其代码如下:
- /**
- * 向一个表行中添加字符型单元格元素
- * @param tr 表行 ID
- * @param str 添加字符
- * @param wid 列宽
- * @constructor
- */
- Att7.prototype.InsertaTDStr=function(tr,str,wid)
- {
- var td=document.createElement("td");
- td.style.width=wid?wid:"200px";
- if(str==null)
- {
- str="";
- }
- td.appendChild(document.createTextNode(str));
- tr.appendChild(td);
- }
接着, 如果有锁定表列, 则也向表列锁定遮罩里推入这个 td.
e, 前面的代码中还出现了 InsertaTDPick 和 InsertaTDPick2 方法, 它们的作用是通过点击原表格或锁定表列遮罩上的行号突出显示某行数据:
- // 一个可以被选中的单元格, 选中后改变单元格所在表行的颜色以突出显示
- Att7.prototype.InsertaTDPick=function (tr,str)
- {
- var td=document.createElement("td");
- td.appendChild(document.createTextNode(str));
- td.style.cursor="crosshair";
- var _this=this;
- td.onclick=function()
- {// 考虑到浏览器可能擅自更改背景颜色样式的字符串表示格式, 使用一个不显示的 div 进行比对
- if(td.parentNode.style.backgroundColor!=_this.div_temp3.style.backgroundColor)
- {// 如果还没变色
- td.parentNode.style.backgroundColor=_this.pickColor;
- }
- else
- {
- if(_this.isStripe==1)
- {
- // 如果已经变色则恢复原本颜色
- if(parseInt(td.innerHTML)%2==0)
- {
- td.parentNode.style.backgroundColor = _this.baseColor;
- }
- else
- {
- td.parentNode.style.backgroundColor = _this.stripeColor;
- }
- }
- else
- {
- td.parentNode.style.backgroundColor = _this.baseColor;
- }
- }
- };
- tr.appendChild(td);
- }
- // 这个给遮罩层用, id 是表实体的 id
- Att7.prototype.InsertaTDPick2=function (tr,str,id)
- {
- var td=document.createElement("td");
- td.appendChild(document.createTextNode(str));
- td.style.cursor="crosshair";
- td.style.width="50px";
- var _this=this;
- td.onclick=function()
- {//526DA5
- if(td.parentNode.style.backgroundColor!=_this.div_temp3.style.backgroundColor)
- {
- td.parentNode.style.backgroundColor=_this.pickColor;// 修改遮罩层
- ChangeTable(td,_this.pickColor);
- }
- else
- {
- if(_this.isStripe==1)
- {
- if(parseInt(td.innerHTML)%2==0)
- {
- td.parentNode.style.backgroundColor = _this.baseColor;
- ChangeTable(td,_this.baseColor);
- }
- else
- {
- td.parentNode.style.backgroundColor = _this.stripeColor;
- ChangeTable(td,_this.stripeColor);
- }
- }
- else
- {
- }
- }
- };
- function ChangeTable(obj,color)// 遮罩层变化之后, 原表格也要变化
- {
- var trs=document.getElementById(id).getElementsByTagName("tr")// 找实体表然后去修改
- var leng=trs.length;
- for(var i=1;i<leng;i++)
- {
- if(obj.innerHTML==trs[i].getElementsByTagName("td")[0].innerHTML)
- {
- trs[i].getElementsByTagName("td")[0].parentNode.style.backgroundColor=color;
- }
- }
- }
- tr.appendChild(td);
- }
f, 自定义多样的单元格类型
att 定义了多种常用的复杂报表单元格, 也支持您添加自己的单元格类型, 时间有限, 这里只举两个例子:
limit 单元格: 数据长度正常时原样显示, 如果数据长度超过单元格宽度太多, 则显示缩略文本, 同时在单元格里插入一个按钮, 点击按钮弹出小对话框显示完整内容:
- else if(data[2][m] == "limit")// 限制字符长度不能过长
- {
- this.InsertaTDStr_lim(tr2, data[l][m - count],(data[3][m-count_none]+"px"));
- if(collock>0&&(m+1)<collock)
- {
- this.InsertaTDStr_lim(tr_mask, data[l][m - count],(data[3][m-count_none]+"px"));
- }
- }
- // 限制宽度
- Att7.prototype.InsertaTDStr_lim= function(tr,str,wid,charwid)
- {//
- var td=document.createElement("td");
- td.style.width=wid?wid:"200px";
- td.style.position="relative";
- if(str==null)
- {
- str="";
- }
- var num_wid=parseInt(wid.split("px")[0]);
- var input1 = document.createElement("input");
- input1.type="text";
- input1.style.border = 0;
- input1.style.width =num_wid+"px" ;// 控件宽度
- input1.style.textAlign = "center";
- input1.style.backgroundColor="transparent";
- input1.style.float="left";
- input1.value=str;
- input1.readOnly=true;
- /*input1.onfocus=function(evt){
- this.blur(); 这样就不能复制粘贴了!
- }*/
- if(!charwid)// 如果没有设置字宽
- {
- charwid=10;
- }
- if((str.length*charwid)>(num_wid*2))// 如果文字的长度超过了单元格宽度的两倍
- {
- //td.title=str;
- //td.overflow="hidden";
- //str=(str.substr(0,(num_wid*2/10).toFixed()) +"...");
- // 尝试在右侧加一个弹出小按钮?
- //str=(str.substr(0,(num_wid*2/10).toFixed()) );
- input1.style.width =(num_wid-30)+"px" ;
- td.appendChild(input1);
- var btn =document.createElement("button");
- btn.className="btn_limlen";
- btn.title=str;
- var _this=this;
- btn.onclick=function()// 通过点击打开的弹出框需要一个关闭按钮, 通过鼠标移入打开的弹出框则随移出自动关闭
- {
- /*if(clipboardData) {
- clipboardData.clearData();
- clipboardData.setData("text", str);
- }
- else */
- /*
- if(event.clipboardData)
- {// 火狐?
- event.clipboardData.clearData();
- event.clipboardData.setData("text/plain", str);
- alert("内容已复制到剪贴板");
- }
- else if(Windows.clipboardData)
- {//IE
- Windows.clipboardData.clearData();
- Windows.clipboardData.setData("text", str);
- alert("内容已复制到剪贴板");
- }
- */
- //clipboardData.getData("text");
- var evt=evt||Windows.event||arguments[0];
- cancelPropagation(evt);
- var obj=evt.currentTarget?evt.currentTarget:evt.srcElement;
- if(delete_div("div_bz")>0)// 清空可能已经显示的其他小窗口
- {
- //return;
- }
- Open_div("","div_bz", 240, 120, 0, 0, obj,"div_tab");// 自编的一个弹出小窗口方法 (旧版)
- //var target={top:,left:}//lim 保持不变, 尝试添加 lim2
- //Open_div2("div_bz", "div_bz", 240, 120, 0, 0, obj, "div_tab");
- document.querySelectorAll("#div_bz")[0].innerHTML = _this.html_onclick;// 向弹出项里写入结构 (之前初始化阶段定义的小控件)
- document.querySelectorAll("#div_bz .div_inmod_lim_content")[0].innerHTML = str;
- }
- td.appendChild(btn);
- }
- else
- {
- td.appendChild(input1);
- }
- tr.appendChild(td);
- }
select: 单元格里是一个下拉框, 用户改变选项后触发某些事件
- else if (data[2][m][0] == "select")// 单元格是一个下拉框
- {
- var td2 = document.createElement("td");
- //td2.style.width = "100px";
- td2.style.width=(data[3][m-count_none]+"px");
- var select = document.createElement("select");
- select.className = data[2][m][1];
- select.style.width = "100px";
- select.selectedIndex=0;
- var temp_i=0;// 用来暂存下面的 i
- for (var i = 0; i <data[2][m][2].length; i++)
- {
- var option = document.createElement("option");
- option.innerHTML = data[2][m][2][i][0];
- if(data[2][m][2][i][1]) {// 如果有的话也不介意设置一个 value
- option.value = data[2][m][2][i][1];
- }
- select.appendChild(option);
- if(data[2][m][2][i][1]==data[l][m - count]||data[2][m][2][i][0]==data[l][m - count])// 后台传过来的可能是 value 也可能是 text!!
- {// 如果这个选项和数据集里的数据相符, 则默认把这个选项选中
- option.selected="selected";
- select.selectedIndex=i;
- temp_i=i;
- }
- }
- listenEvent(select,"change",select_onchange);// 监听选项变化
- select.datachange=data[2][m][3];
- function select_onchange()
- {
- var evt = evt || Windows.event||arguments[0];
- cancelPropagation(evt);// 发现如果不阻断事件, 会引发 button1 的 click 相应!!??
- var obj=evt.currentTarget?evt.currentTarget:evt.srcElement;
- //dataObj2[parseInt(this.parentNode.parentNode.firstChild.innerHTML)%150-1][parseInt(this.className.split("*")[1])]=this.value;
- eval((obj.getAttribute("datachange")?obj.getAttribute("datachange"):obj.datachange));
- }
- /*select.onchange=function()
- {
- var evt = evt || Windows.event;
- cancelPropagation(evt);// 发现如果不阻断事件, 会引发 button1 的 click 相应!!??
- //dataObj2[parseInt(this.parentNode.parentNode.firstChild.innerHTML)%150-1][parseInt(this.className.split("*")[1])]=this.value;
- eval(data[2][m][3]);
- }*/
- td2.appendChild(select);
- tr2.appendChild(td2);
- if(collock>0&&(m+1)<collock)// 对于遮罩层
- {
- /*var td2a = document.createElement("td");
- td2a.style.width=(data[3][m-count_none]+"px");
- var selecta=select.cloneNode(true);
- selecta.datachange=data[2][m][3];
- td2a.appendChild(selecta);*/
- var td2a=td2.cloneNode(true);// 克隆 dom 元素
- var selecta=td2a.childNodes[0];
- selecta.datachange=data[2][m][3];
- selecta.selectedIndex=temp_i;
- tr_mask.appendChild(td2a);
- listenEvent(selecta,"change",select_onchange);
- }
- }
5, 总结
基本完成了 att 的重构工作, 旧版中使用 jQuery 的地方都替换成了原生的 JavaScript 方法, 虽然原生方法的兼容性不如 jQuery, 但考虑到要配合兼容性更窄的 WebGL 使用, 这些兼容性损失可以忽略. 重构的代码没有经过充分测试, 可能存在各种问题, 您可以访问 https://ljzc002.github.io/Att/HTML/TEST/AttSample.html 测试部分单元格类型.
来源: https://www.cnblogs.com/ljzc002/p/9921998.html