引言(祝看官們新年萬事大吉)
前段時間項目需要實現網頁版的excel表格功能,瞬間就想到了handsontable,為什么呢?理由如下:該UI組件功能齊全多樣,展示效果也更貼近bootstrap風格,兼容所有現代瀏覽器和IE9+,然后開源,api相當給力。
唯一美中不足的是沒有中文版的api,也有些分享中文api的文章,也不完整(有就不錯了,不滿意自己去看官網api啊,啦啦啦~),閑言少敘,進入正文:
功能需求
我們先看一下功能操作欄(紅框部分為部分我們需要實現的功能):
上圖中的 input 會同步響應我們所選單元格的數據,其他的方法如圖所示。
首先我們既然是表格,那么我們的操作對象基本都是單元格,但同時我們肯定也希望能夠滿足范圍性操作,比如說批量修改樣式。當然我們不僅需要考慮到展示效果,還需要考慮到儲存和數據渲染,
我們需要的做到以下幾點:所選范圍、所選范圍里的單元格、如何儲存數據、如何通過loadData一次性加載數據(包括樣式及自定義屬性),帶着這些問題我們去看api,我們需要找到我們需要方法以及了解如何使用它們。
這里附上官方api鏈接:https://docs.handsontable.com/pro/1.16.0/tutorial-introduction.html
閱讀過程小生這里就不做贅述了(唔...一把辛酸淚),首先我們需要清楚的是,我們需要獲取到被選擇的單元格對象,而要獲取單元格對象我們要知道所選范圍(單個單元格也是一個范圍,這里不需要另作判斷),而要知道所選范圍我們要用到 getSelected() 方法,而此方法應該在我們選擇范圍后觸發,所以我們需要用到鈎子函數:afterOnCellMouseDown,具體用法可以到api里自行查閱
根據我們的需求,結合api得到如下圖:
萬事俱備,接下來該做什么呢?當然是做我們最愛做的事:擼代碼!~
實現功能
我們這里用本地數據做展示:
var data = [ [, , , , , , , , , , , , , , , , , , , , ], ["2001", 10, 11, 12, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,6,3,5,6,3,5,3,87,6,8,5,6,6,35,6,3,6,3,8,38,3], ["2002", 10, 11, 12, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,6,3,5,6,3,5,3,87,6,8,5,6,6,35,6,3,6,3,8,38,3], ["2003", 10, 11, 12, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,6,3,5,6,3,5,3,87,6,8,5,6,6,35,6,3,6,3,8,38,3], ["2004", 10, 11, 12, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,6,3,5,6,3,5,3,87,6,8,5,6,6,35,6,3,6,3,8,38,3], ["2005", 10, 11, 12, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,6,3,5,6,3,5,3,87,6,8,5,6,6,35,6,3,6,3,8,38,3], ["2006", 10, 11, 12, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,6,3,5,6,3,5,3,87,6,8,5,6,6,35,6,3,6,3,8,38,3], ["2007", 10, 11, 12, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,6,3,5,6,3,5,3,87,6,8,5,6,6,35,6,3,6,3,8,38,3], ["2008", 10, 11, 12, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,6,3,5,6,3,5,3,87,6,8,5,6,6,35,6,3,6,3,8,38,3], ["2009", 20, 11, 14, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,6,3,5,6,3,5,3,8,6,78,5,6,6,35,6,3,6,3,8,38,3], ["2010", 30, 15, 12, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,6,3,52,6,3,5,3,8,6,8,5,6,56,355,6,3,66,23,8,38,3], ["2011", 20, 11, 14, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,26,3,5,26,3,5,3,8,6,8,5,56,6,35,6,3,6,23,8,38,3], ["2012", 20, 11, 14, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,666,5,8,6,3,9,6,3,5,56,3,5,3,78,6,58,55,6,6,35,6,23,6,3,8,38,3], ["2013", 20, 11, 14, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,68,6,3,9,6,3,5,26,3,5,3,8,6,8,5,6,6,35,6,3,6,3,8,38,3], ["2014", 20, 11, 14, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,6,3,5,26,3,5,3,8,6,58,5,6,6,35,6,3,6,3,8,38,3], ["2015", 20, 11, 14, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,29,26,3,5,6,3,5,3,78,76,8,5,6,6,35,6,3,6,3,8,38,3], ["2016", 20, 11, 14, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,6,3,55,6,3,5,3,8,6,28,5,6,6,35,6,3,6,3,8,38,3], ["2017", 20, 11, 14, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,43,9,6,3,5,6,3,5,3,8,6,8,5,6,6,35,6,3,6,3,8,38,3], ["2018", 20, 11, 14, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,62,3,29,6,3,5,6,3,5,3,8,6,8,5,6,6,35,6,3,6,3,8,38,3], ["2019", 20, 11, 14, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,48,6,3,9,6,3,5,6,3,5,3,8,6,8,5,6,6,35,6,3,6,3,8,38,3], ["2020", 20, 11, 14, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,68,6,3,9,6,3,5,6,3,5,3,8,6,8,5,6,6,345,6,3,64,3,8,38,3], ["2021", 20, 11, 14, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,6,3,5,6,3,5,3,8,6,8,5,6,6,35,6,3,6,3,8,38,3], ["2022", 20, 11, 14, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,66,3,5,6,3,5,3,8,6,8,5,6,6,35,6,3,6,3,48,38,23] ];
前端界面如圖:
漢化右鍵菜單:如果不需要右鍵菜單可以設置為false,contextMenu : false
contextMenu: { items: { 'mergeCells':{ name: '合並單元格' , }, 'row_above': { name: '上方添加一行', }, 'row_below': { name: '下方添加一行', }, 'col_left': { name: '左側添加一列', }, 'col_right': { name: '右側添加一列', }, 'remove_row': { name: '移除此行', }, 'remove_col': { name: '移除此列', }, 'copy': { name: '復制', }, 'cut': { name: '剪切', }, 'make_read_only': { name: '禁止編輯選中項', }, 'alignment': { }, 'undo': { name: '還原上次操作', }, 'redo': { name: '重復上次動作', }, 'setAlias':{ name:'設置別名', callback:function(){ if( $(Ccell) != undefined ){ addAliasDialog(); }else{ alert("請先選擇單元格..."); } } } } }
看效果圖:
自定義菜單及回調函數:自定義菜單也是在 contextMenu里面,格式如下(如上圖中的 setAlias 設置別名,設置屬性方法我們在后面再說明):
contextMenu: { items: { '參數名':{ name:'菜單名', callback:function(){ // doSomething } } } }
列出全局變量:
// 列出全局變量 var Crow,Ccol,Ccell,valT,selectRange,selectRangeArr = [];
Crow:所選單元格的行,Ccol:所選單元格的列,valT:所選單元格的值,selectRange:所選范圍,selectRangeArr:所選單元格數組
獲取所選區域單元格數組,當前單元格高亮:這里需要補充的一點是,handsontable本身在表格失去焦點時會移除所有當前高亮類,而我們在點擊按鈕修改樣式時又需要所選高亮來滿足我們的“心中有數”,所以這里我們需要自定義一個高亮的類,看代碼:
// 獲取所選區域單元格數組 當前高亮 hot.addHook('afterOnCellMouseDown', function (event, cellCoords) { Crow = cellCoords.row, Ccol = cellCoords.col; selectRangeArr = []; // 所選區域所有單元格數組 Ccell = hot.getCell(Crow, Ccol) selectRange = hot.getSelected(); // 獲取所選區域范圍 console.log(selectRange); var txt = hot.getDataAtCell(selectRange[0],selectRange[1]); // 獲取所選區域第一個單元格值 // 單擊任意單元格取消編輯狀態 $(".handsontableInputHolder").css({ "display":"none" }); $("#templateCellInput").val(txt); var rangeRowArr = []; // 所選區域行數組 var rangeColArr = []; // 所選區域列數組 for( var i=selectRange[0];i<selectRange[2]+1;i++ ){ rangeRowArr.push(i); } for( var i=selectRange[1];i<selectRange[3]+1;i++ ){ rangeColArr.push(i); } for( var i=0;i<rangeRowArr.length;i++ ){ for( var n=0;n<rangeColArr.length;n++ ){ var selectRangeCell = { row:rangeRowArr[i],col:rangeColArr[n] }; selectRangeArr.push(selectRangeCell); } } // 添加表格失去焦點時的當前單元格類 $("td").removeClass("currentTd"); for( var i=0;i<selectRangeArr.length;i++ ){ var rangeCell = hot.getCell(selectRangeArr[i].row, selectRangeArr[i].col); $(rangeCell).addClass("currentTd"); } });
我們可以在控制台查看一下令我們心動的 selectRange 和 selectRangeArr :
所選單元格的值和input同步:templateCellInput 為 input的id
$("#templateCellInput").keyup(function(){ var val = $(this).val(); if(selectRangeArr.length>0){ for( var i=0;i<selectRangeArr.length;i++ ){ hot.setDataAtCell(selectRangeArr[i].row, selectRangeArr[i].col,val) } } });
修改單元格樣式(字體樣式和對齊方式):switch條件語句在這種事件觸發對象判斷中用起來是相當地令人愉快..
// 修改單元格樣式 $(".btn-group label.btn").click(function(e){ console.log(e.target); var _index = $(this).index(); var styleType = $(this).parent(); var StyleClassName = ''; // 修改單元格文本樣式 var toggleSwitch = true; if( styleType.hasClass("fontStyle") ){ var fontClass = ""; switch(_index){ case 0 : fontClass = "htBold"; // 加粗 break; case 1 : fontClass = "htItalic"; // 斜體 break; case 2 : fontClass = "htUnderline"; // 下划線 break; } StyleClassName = fontClass; } // 修改單元格對齊方式 if( styleType.hasClass("alignStyle") ){ var alignClass = ""; switch(_index){ case 0 : alignClass = "htLeft"; // 左對齊 break; case 1 : alignClass = "htCenter"; // 居中對齊 break; case 2 : alignClass = "htRight"; // 右對齊 break; case 3 : alignClass = "htJustify"; // 兩端對齊 break; } StyleClassName = alignClass; } // 修改所選區域所有單元格樣式並賦予屬性 for( var i=0;i<selectRangeArr.length;i++ ){ var rangeCell = hot.getCell(selectRangeArr[i].row, selectRangeArr[i].col); var checkMergeCell = $(rangeCell).attr("rowspan"); $(rangeCell).removeClass("htLeft htCenter htRight htJustify"); // 定義修改類名 創建對應屬性方法 var setRangeCellClass = function(){ $(rangeCell).toggleClass(StyleClassName); var cellClass = $(rangeCell)[0].className; hot.setCellMeta(selectRangeArr[i].row, selectRangeArr[i].col,"cellClass",cellClass); } if( checkMergeCell != undefined ){ if( toggleSwitch ){ setRangeCellClass(); toggleSwitch = false; }else{ continue; } }else{ setRangeCellClass(); } } });
這里需要注意的兩點是:
1、如果對象是合並單元格,那么在賦值和修改樣式上需要區別對待,看代碼(具體區別請調試代碼自己體會o(∩_∩)o );
2、在方法里我們可以看到運用了 setCellMeta() 方法,單純的前端效果我們不需要用到此方法,這里是為了便於儲存和渲染數據,如此在初始化表格渲染數據的時候我們能將每一個單元格所對應的樣式類名也添加進去,簡而言之:每一次初始化表格我們只需要渲染一次,開心~
自定義背景色、字體顏色、邊框色:這里我們用到了插件 bootstrap-colorpicker.js :
$(".ColorStyle input").each(function(){ $(this).colorpicker(); }) $(".ColorStyle input").blur(function(){ var val = $(this).val(); var _index = $(this).parent().index(); $(this).css("cssText","background:"+val+"!important;color:"+val+"!important;"); // 定義改變樣式方法 var changeCellStyle = function(){ if( _index == 0 ){ $(rangeCell).css({"background":val}); hot.setCellMeta(selectRangeArr[i].row, selectRangeArr[i].col,"bkColor",val); } if( _index == 1 ){ $(rangeCell).css({"color":val}); hot.setCellMeta(selectRangeArr[i].row, selectRangeArr[i].col,"ftColor",val); } if( _index == 2 ){ $(rangeCell).css({"border":"solid 1px "+val}); hot.setCellMeta(selectRangeArr[i].row, selectRangeArr[i].col,"bdColor",val); } }; for( var i=0;i<selectRangeArr.length;i++ ){ var rangeCell = hot.getCell(selectRangeArr[i].row, selectRangeArr[i].col); var checkMergeCell = $(rangeCell).attr("rowspan"); if( checkMergeCell != undefined ){ if( toggleSwitch ){ changeCellStyle(); toggleSwitch = false; }else{ continue; } }else{ changeCellStyle(); } } });
看效果圖:
自定義屬性:假設我們現在需要給某些單元格添加任意屬性,這里我們以設置別名為例,並且希望能在右鍵菜單中可以直接操作:
'setAlias':{ name:'設置別名', callback:function(){ if( $(Ccell) != undefined ){ addAliasDialog(); }else{ alert("請先選擇單元格..."); } } }
對的,此處應該有彈窗,這里小生強烈推薦用 layer.js,快准狠還高大上...這里附上 layer.js的官網地址,具體用法有空親們可以自行琢磨:http://www.layui.com/doc/modules/layer.html,下面看設置別名回調函數:
function addAliasDialog(){ var html = '<div class="alias" style="text-align:center;margin-top:20px;"><label>請輸入別名:<input type="text" id="aliasVal" /></label></div>'; layer.open({ type: 1, btn: ['確認', '取消'], shadeClose:true, title:"設置別名", area: ['420px', '240px'], //寬高 content: html, yes:function(index,layero){ var val = $("#aliasVal").val(); var cellMeta = hot.getCellMeta(Crow, Ccol); if( val != "" ){ hot.setCellMeta(Crow, Ccol, "alias", val); layer.msg('設置成功', { icon: 1, time: 1000 //1秒關閉(如果不配置,默認是3秒) }, function(){ console.log(cellMeta); layer.close(index); }); }else{ alert("別名不能為空!"); } }, cancel:function(index,layero){ layer.close(index); },btn2: function(index, layero){ layer.confirm('確認取消設置別名嗎?', {icon: 3, title:'提示'}, function(index){ layer.close(index); },function(index){ addAliasDialog(); }); } }); }
這里需要注意的一點是,setCellMeta添加的屬性是單元格的屬性,而不是單元格的DOM屬性,所以我們用attr或者prop方法是獲取不到的(小生不可能告訴親們當初經歷了什么~),我們可以在設置完成以后將所選單元格的屬性打印出來:
看打印結果
:
結語
這次分享到這里就結束了,希望對大家有所幫助,有疑問或者建議都可以留言交流,新年快樂,摸摸踹~