今天用自己寫的庫完成了一個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.onmousedown=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.onmousedown=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