從零開始使用JavaScript編寫資料表格控制元件(二)
資料表格控制元件的渲染器
在第一部分中,我們講述瞭如何實現一個最簡單的資料表格控制元件,在後面的部分,我們會討論得深入一些,探討資料表格的渲染器、排序等功能。
5. 渲染器
什麼叫做渲染器呢?
之前我們的DataGrid非常簡單,每個單元格只是簡單的把文字資料渲染出來,如果想要對這些文字作格式化處理,比如說,把性別的標記0和1轉換成文字的“男”和“女”,要怎麼去考慮?
有個笨辦法,我們讓使用者傳入資料之前,就把原始資料修改一下,比如說,原先資料格式是這樣:
{
name: "Tom",
age: 16,
gender: 1
}
我們給他先轉換一遍,變成:
{
name: "Tom",
age: 16,
gender: 1,
genderName: "Male"
}
這樣,不顯示gender這個列,而是直接顯示genderName這一列,也可以做到想要的效果。這麼看上去簡單方便,有沒有什麼弊端呢?有三種。
- 破壞了原始資料
- 需要對資料作預處理,這個處理過程比較集中,我們在前端常見的優化策略是把計算儘可能均攤,避免某個短時間的密集計算。
- 把邏輯割裂了。為什麼這麼說呢,因為你不但要在批量載入資料之前做這個轉換,新增、修改行的時候,是不是也同時要?
另外也有個辦法,可以預定義一些規則,比如說定義了這個是性別的列,把格式化的過程內建在控制元件中,這種做法也不好,內建的東西總是有限的,面對不斷變更的需求,需要無休止地修改。
現在討論的只是單個列需要處理,假如有多個,那更麻煩了,有沒有什麼更好的辦法呢?
5.1 欄位格式化
我們可以把這個格式化功能提取到外面,然後注入進來。格式化的功能,至少應當是針對列的,所以可以附加到列的初始化資訊裡傳遞過來。考慮一下格式化函式的引數,至少需要原始值,但有些情況更加複雜,所以我們多給它一些資訊,比如說,本行的完整資料,還有當前列的key值。這麼一來,一個典型的格式化函式就有了:
function labelFunction(data, key) {
var value = data[key];
if (value == 0) {
return "Female";
}
else if (value == 1) {
return "Male";
}
else {
return "Unknown"
}
}
有了格式化,我們就可以很方便地進行一些顯示的轉換,比如對日需求,期、金額的實際值和顯示值進行轉換,或者,也可以顯示一些圖片和操作按鈕之類。
比如說:
function labelFunction(data, key) {
var value = data[key];
if (value > 18) {
return "<button>Click me, man</button>";
}
else {
return "Hi, boy, you can do nothing.";
}
}
上面這段程式碼是一個示例,我們可以指定當年齡大於18歲的時候出來一個按鈕可點,不足18的時候只出來一段文字。看上去,這段程式碼也滿足我們需要了,但它將會遇到問題。
什麼問題呢?我們來給這個按鈕加個事件,點選它的時候,顯示這個人的名字。考慮到我們輸出的是HTML字串,所以這個事件比較難加,除非也用字串拼到裡面,這麼做是有很多弊端的,我們來考慮用一些優雅的方式解決。
5.2 單元格渲染器
既然返回字串不好,那我們直接一點,返回DOM結構如何?
var itemRenderer = {
render: function (row, key, columnIndex) {
var data = row.data;
if (data[key] >= 18) {
var btn = document.createElement("button");
btn.innerHTML = data[key];
btn.onclick = function () {
alert("I am " + data[key] + " years old, I want a bottle of wine!");
};
return btn;
}
else {
var span = document.createElement("span");
span.innerHTML = data[key];
return span;
}
},
destroy: function () {
}
};
這樣就好多了。這個時候,我們需要考慮渲染器和格式化函式的優先順序,有人會問,有了渲染器,還要格式化函式幹什麼?這問題其實就像有了拖拉機,為什麼鋤頭還能賣得出去?我們把渲染器當作一個比較重量級的解決方案,格式化函式當作輕量級的,各有其使用場景。
我們來看看行的渲染方法應當怎麼寫:
render: function (cell, data, field, index) {
if (this.grid.columns[index].itemRenderer) {
cell.innerHTML = "";
cell.appendChild(this.grid.columns[index].itemRenderer.render(this, field, index));
}
else if (this.grid.columns[index].labelFunction) {
cell.innerHTML = "";
cell.innerHTML = this.grid.columns[index].labelFunction(data, field);
}
else if (this.grid.itemRenderer) {
cell.innerHTML = "";
cell.appendChild(this.grid.itemRenderer.render(this, field, index));
}
else {
cell.innerHTML = data[field];
}
}
這裡面有四種東西:
- 針對某列的渲染器
- 針對某列的格式化函式
- 針對所有單元格的全域性渲染器
- 直接賦值
我們讓它們的優先順序遞減。為什麼會同時需要全域性渲染器和列的渲染器呢?其實也可以在全域性渲染器裡面對行、列作判斷,然後分別為每種情況渲染,但如果很多列都需要渲染,這麼做不太好,需要分離成多個不同的列渲染器。
注意到我們使用的渲染器裡面帶有destroy方法,這個是為了減少記憶體洩露而設計的,使用者可以自行在這裡解除安裝事件處理函式,隔斷待回收的物件引用。
現在我們實現了單元格的渲染器機制,那麼,標題的列頭呢?這裡可能也會需要有定製的內容,所以也需要為它設計類似的擴充套件機制,在此不再贅述。
5.3 資料表格的複選功能
有了這些渲染器機制,我們可以來為資料表格新增更實用的功能。很多資料表格的使用場景需要複選,標題上有一個核取方塊,可以控制行的選中狀態,行的選中狀態也會反過來影響到標題核取方塊的選中狀態。
所以,我們需要兩個渲染器,一個是放在標題上的,一個是放在行上的。
var CheckboxRenderer = {
render: function(row, field, columnIndex) {
var grid = row.grid;
var data = row.data;
var div = document.createElement("div");
var checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.checked = data["checked"];
checkbox.onclick = function () {
data["checked"] = !data["checked"];
var checkedItems = 0;
var rowLength = grid.rows.length;
for (var i=0; i<rowLength; i++) {
if (grid.rows[i].get("checked")) {
checkedItems++;
}
}
if (checkedItems === 0) {
grid.set("checkState", "unchecked");
}
else if (checkedItems === rowLength) {
grid.set("checkState", "checked");
}
else {
grid.set("checkState", "indeterminate");
}
};
div.appendChild(checkbox);
var span = document.createElement("span");
span.innerHTML = data[field];
div.appendChild(span);
return div;
}
};
var HeaderRenderer = {
render: function (grid, field, columnIndex) {
var rows = grid.rows;
var div = document.createElement("div");
var checkbox = document.createElement("input");
checkbox.type = "checkbox";
switch (grid.get("checkState")) {
case "checked": {
checkbox.checked = true;
break;
}
case "unchecked": {
checkbox.checked = false;
break;
}
case "indeterminate": {
checkbox.indeterminate = true;
break;
}
}
div.appendChild(checkbox);
checkbox.onclick = function () {
var checked = this.checked;
for (var i = 0; i < rows.length; i++) {
rows[i].set("checked", checked);
}
};
var span = document.createElement("span");
span.innerHTML = field;
div.appendChild(span);
return div;
},
destroy: function() {
}
};
之前,我們並不能完全確定應當傳遞給渲染器哪些引數,可能會認為只需要當前資料和列的key值即可,在做這個例子的過程中,會發現可能還需要datagrid本身的例項,所以,直接把行的例項傳入即可,從它身上可以直接獲得datagrid的例項和行的資料。除此之外,列序號也可以傳遞進來,雖然說它跟key可以互相反查得到,但直接傳入會比較便利些。
5.4 小結
到目前為止,我們的資料表格控制元件裡可以展示一些複雜的東西了,比如說一些操作按鈕,圖片,甚至繪製一些圖形,更重要的是,它們可以跟控制元件本身產生互動,而又不需要修改控制元件自身的程式碼。
所以說,這個DataGrid控制元件還是比較靈活的,可以支援有一定複雜度的需求了,但是還是有缺陷。這種機制,如果想要渲染出跨行或者跨列的表格,就有一些難度了,我們不在這個方面多作文章,只針對90%的需求編寫程式碼。
本節提到的程式碼,示例在:
http://xufei.github.io/thin/demo/controls/datagrid.html
請讀者自行檢視。
相關文章
- 從零開始使用JavaScript編寫資料表格控制元件(一)JavaScript控制元件
- 從零開始編寫自己的JavaScript框架(二)JavaScript框架
- 從零開始寫JavaScript框架(二)JavaScript框架
- 從零開始編寫自己的JavaScript框架(一)JavaScript框架
- Re從零開始的UI庫編寫生活之表格元件UI元件
- 從零開始寫JavaScript框架(一)JavaScript框架
- 從零開始編寫指令碼引擎指令碼
- 從零開始寫一個Javascript解析器JavaScript
- 從零開始編寫一個babel外掛Babel
- 從零開始:用REACT寫一個格鬥遊戲(二)React遊戲
- 資料分析從零開始實戰 | 基礎篇(二)
- 從零開始寫一個ExporterExport
- 從零開始仿寫一個抖音App——開始APP
- Re從零開始的UI庫編寫生活之表單UI
- Re從零開始的UI庫編寫生活之按鈕UI
- 從零開始寫一個微前端框架-資料通訊篇前端框架
- 從零開始的堆疊卡片控制元件控制元件
- 從零開始寫一個網頁網頁
- Excel 開始支援使用 JavaScript 編寫自定義函式ExcelJavaScript函式
- Re從零開始的UI庫編寫生活之規範制定UI
- 從零開始編寫一個 Python 非同步 ASGI WEB 框架Python非同步Web框架
- 從零開始用golang編寫一個分散式測試工具Golang分散式
- 用PyTorch從零開始編寫DeepSeek-V2PyTorch
- 從零開始寫一個node爬蟲(上)—— 資料採集篇爬蟲
- 從零開始學Electron筆記(二)筆記
- 從零開始系列-Laravel編寫api服務介面:6.資料庫查詢(未完待續)LaravelAPI資料庫
- 從零開始再學 JavaScript 定時器JavaScript定時器
- 從零開始學typescript— 自動編譯TypeScript編譯
- 從零開始手寫Koa2框架框架
- 如何從零開始寫一個網站網站
- 從零開始怎麼寫androidnativeservice?Android
- 寫給大資料初學者,從零開始學習大資料開發的完整路線大資料
- Re從零開始的UI庫編寫生活之進度條元件UI元件
- 從零開始系列-Laravel編寫api服務介面:10.transformerLaravelAPIORM
- 從零開始系列-Laravel編寫api服務介面:7.資料庫新增和更新(未完待續)LaravelAPI資料庫
- 19. 從零開始編寫一個類nginx工具, 配置資料的熱更新原理及實現Nginx
- 從零開始的Java RASP實現(二)Java
- 從零開始React專案架構(二)React架構