今天用自己写的库完成了一个40列填报报表的前后台调试,所花费的时间超过预期很多。遇到的坑有:ajax回调函数写错导致循环调用,没有考虑到java的request.getParameter()方法读入数据的长度限制,对json中的引号的转义处理理解不透彻,对同一数据项在JavaScript、Java、SQL三种语言中的数据类型转换失误,没有注意到前台数据集和数据库的几处结构差异。虽然最终完成了工作计划,但反复的调试与核对花费了太多的时间和精力。
又回到了那个“是否自己造轮子”的老问题。两年前刚参加工作时,我使用同事提供的Flex*Java*Oracle框架来快速上手,依靠这套框架我在较短的时间内完成了大量的简单报表编写工作,并积累了一些web编程的基础知识。
后来,用户对表格的功能提出了更细致的要求(多为根据不同的数据对表格的样式进行修改,比如数值超过100就设为红色之类),这些细化的要求并不是Flex和ActionScript所擅长的,在没有完善文档和例程的情况下,对每一个超出框架的小功能的修改都要花费极大的精力。再后来,随着Adobe放弃支持Flex转向Html5,Flex报表框架更是前途黯淡。
放弃Flex后,我又尝试了Ext.net控件、帆软报表设计器、润乾报表设计器等框架,但都不尽如人意。总结起来,这些框架的限制如下:
1、当用户提出的功能要求超过框架设计范围时,框架本身将无能为力,并且因为框架的复杂性应用开发者往往要花费更多的时间来实现功能;
2、应用开发者很难掌控框架的性能;
3、框架使用者在编程过程中积累的只是框架使用的经验,而不是真正的前端知识。
4、一旦框架背后的公司放弃对框架的支持,该框架的价值将急速降低。
综上,我开始尝试使用原生的JavaScript来编写报表绘制库,经过一段时间的努力,我自己的框架显出了雏形(JavaScript*Java*Oralcle)。在前台的报表绘制库里我实现了前台表格的样式控制、表元素点击相应、翻页、单元格嵌入复杂模块、锁定表头表列、动态列选择、点击表元素弹出窗口(div)、前台导入导出文本文件、前台Excel导出(IE only)等功能,初步摆脱了功能设计上的限制。
但随着客户需求的进一步升级,我的框架在处理“超大型填报表”时显得力不从心。根据经验,当报表的列数达到20列以上时,对单元格对象的管理、对前后台数据的同步(特别是对数据类型的同步)将超过实现复杂功能成为报表应用编写的主要矛盾。此时成熟框架的“设计器式”对象管理和前后台数据绑定技术就变得无比实用了。
现有的大部分成熟前端报表库都因为历史原因使用table、tr、td标签来进行表格绘制(我的也是),而浏览器会“自作聪明的”对这些标签进行超出开发者控制的样式调整,这不但使得报表绘制库的样式控制更加复杂,也使得单元格的精确控制和模块插入变得更难。在我看来,一个完美的报表绘制框架应该使用div标签绘制,用原生语言控制,并提供报表设计器和前后台数据绑定。
我并不准备自己投入这种框架的开发,一是时间和精力不足;二是我所在的单位在有多种选择时习惯通过投票来决定使用何种技术,在同事门都在用润乾画表的情况下,编写一个和润乾无关的框架必定不受欢迎。
现在看来,在一个成熟框架的基础上进行定制化修改,是一个兼顾了灵活性、工作效率和单位团结的好办法。
以下是表格绘制库att5的基本工程结构:
其中:
easyui的引入是为了方便的使用日历控件,如果不需要可以去掉;
testtab.css是主要的样式表文件;
test_01_base.html和test_01_base.js是基本示例;
DatePicker.js是网上找到的一个日历控件,功能比easyui弱,但更灵活;
webgl-utils.js是一个Google的3D动画库,主要用到其中的requestAnimFrame方法;
Events.js是事件处理库,主要来自Shelley Powers的《JavaScript经典实例》和网上搜集整理;
FileText.js是文件处理库是我参考网络教程编写的;
Table2.js是核心的表格绘制库(封装为att5),完全使用原生JavaScript编写;
View.js是样式处理库,根据书籍和网络搜集整理。
在示例js中少量使用了jQuery选择器简化编程,js库文件均为原生JavaScript。
下面是表格绘制库att5的基本使用示例(简单显示测试数据并实现数据集的文本保存):
1 /** 2 * Created by Administrator on 2016/5/6. 3 */ 4 //生成测试数据 5 function CreateTestData() 6 { 7 var count_row=200;//两百行数据 8 var count_col=10;//每行数据有十列 9 var arr_row=[]; 10 11 for(var i=0;i<count_row;i++) 12 { 13 arr_row=[]; 14 for(var j=0;j<count_col;j++) 15 { 16 arr_row.push("第"+(i+1)+"行第"+(j+1)+"列"); 17 } 18 arr_user.push(arr_row); 19 } 20 DrawTable(); 21 } 22 //在前台生成数据集并按数据集绘制表格 23 function DrawTable() 24 { 25 arr_user.unshift([100,100,100,100,100,100,100,100,100,100]);//10列,规定表列的最小宽度 26 var arr_DOM = []; 27 arr_DOM.push("str"); 28 arr_DOM.push("str"); 29 arr_DOM.push("str"); 30 arr_DOM.push("str"); 31 arr_DOM.push("str"); 32 arr_DOM.push("str"); 33 arr_DOM.push("str"); 34 arr_DOM.push("str"); 35 arr_DOM.push("str"); 36 arr_DOM.push("str"); 37 arr_user.unshift(arr_DOM);//向数据集中压入每一列的数据结构,这里全是简单字符类型 38 arr_user.unshift(["表头","表头","表头","表头","表头","表头","表头","表头","表头", 39 "表头"]);//压入表头 40 arr_user.unshift(dwmc+"最小功能测试表");//压入表名 41 //绘制表格并返回表格总页数 42 $('#t_page_span')[0].innerHTML=att5.ArrayToTable5("div_tab","tab_data",0,0,arr_user,30,pages); 43 $('#c_page_span')[0].innerHTML=pages+1;//当前所在页数 44 AdjustColor();//表格绘制完毕后根据需求对部分单元格样式进行微调 45 } 46 function AdjustColor() 47 { 48 49 } 50 51 //用文本方式把数据集导出,兼容ie8-ie11,兼容chrome 52 function TextExport() 53 { 54 var str_data=JSON.stringify(arr_user);//为保持数据结构,压成json格式 55 DownloadText(MakeDateStr()+"数据集",str_data); 56 } 57 58 //导入数据集,弹出一个用来选择文件的对话框 59 function ImportCol() 60 { 61 delete_div('div_ImportCol');//删除可能已经存在的对话框 62 Open_div("div_ssbup", "div_ImportCol", 640, 120, 80, 80, "", "", 0);//在指定位置打开一个div 63 $("#div_ImportCol")[0].innerHTML = $("#div_mod1")[0].innerHTML;//设置div的内容 64 65 //使得弹出框可以被拖拽 66 var div_ImportCol=$("#div_ImportCol")[0]; 67 drag(div_ImportCol); 68 //把对弹出框内元素的操作与弹出框拖拽分离 69 var div_inmod_content=$("#div_ImportCol #btn_ImportCol")[0]; 70 div_inmod_content.οnmοusedοwn=function() 71 { 72 var evt=evt||window.event; 73 cancelPropagation(evt);//阻断事件传播 74 } 75 var btn_close=$("#div_ImportCol .div_inmod_head button")[0]; 76 btn_close.οnmοusedοwn=function() 77 { 78 var evt=evt||window.event; 79 cancelPropagation(evt); 80 } 81 } 82 //导入文本文件 83 function ImportCol2() 84 { 85 var evt=evt||window.event; 86 cancelPropagation(evt); 87 var obj=evt.currentTarget?evt.currentTarget:evt.srcElement; 88 UploadText(obj.value,evt,"ImportCol3(s)"); 89 } 90 //根据导入的数据集绘制表格 91 function ImportCol3(str_json) 92 { 93 arr_user=JSON.parse(str_json); 94 delete_div('div_ImportCol'); 95 $('#t_page_span')[0].innerHTML=att5.ArrayToTable5("div_tab","tab_data",0,0,arr_user,30,pages); 96 $('#c_page_span')[0].innerHTML=pages+1; 97 }
完整的基本示例和库文件下载:
https://github.com/ljzc002/att5