本文更新不及時,建議到原文地址瀏覽:解決layui的table組件更新數據後每行toolbar工具欄不更新的問題.
layui是一套非常不錯的後臺管理系統UI框架,其簡約並具有系統感的設計贏得了許多開發者的青睞。不過我在使用layui的動態表格組件時遇到了一個問題,更新一條數據時,對應的每行的toolbar(就是右邊編輯/查看/刪除這類按鈕區)沒有根據數據進行重新動態更新。我通過源碼閱讀,瞭解到了爲什麼沒有更新了,並完美修復這個問題。下面將進行詳細介紹。
一、問題重現
通常我們的表格右邊都會有幾個按鈕,用來對某一條數據進行操作。而很大一種情況就是右邊的操作按鈕是根據數據狀態顯示對應操作功能的。那麼這個時候,就可以使用其提供的 toolbar
字段來完成我們的需求。通過官方文檔我們瞭解到,toolbar
字段可以是一個模板id,而模板是支持動態渲染的。可在表格裏刷新時,toolbar又不更新,這就很樸素迷離了啊。下面先看一個不會更新的示列:
上面的第二列按鈕是根據用戶身份顯示不同的按鈕的,工具條的模板定義如下:
<script id="data-tool-bar" type="text/html">
<button class="layui-btn layui-btn-xs layui-btn-normal" lay-event="changeStatus">修改身份</button>
{{# if(d.status === "vip"){ }}
<button class="layui-btn layui-btn-xs">贈送禮包</button>
{{# } else if (d.status === "poor") { }}
<button class="layui-btn layui-btn-xs layui-btn-warm">催促充值</button>
{{# } else if (d.status === "normal") { }}
<button class="layui-btn layui-btn-xs layui-btn-primary">推送廣告</button>
{{# } }}
</script>
可以看到,這個第二個按鈕是根據用戶的不同身份,顯示不同的按鈕的。但是在更新數據修改用戶身份時,後面的按鈕並沒有根據修改的狀態顯示對應的按鈕,只是前面數據部分修改生效了。
二、追溯問題
爲了修復這個問題,我們必須要進入源碼一探究竟。我們知道,要監聽這部分工具按鈕的點擊事件,可以通過給按鈕指定一個 lay-event 來達到監聽對應事件的效果。通過此事件可以獲取到對應行的數據,並更具此數據進行進一步的業務操作。下面展示了上一節的【修改身份】按鈕事件的代碼:
// 監聽按鈕。這個 table 就是 layui.table 得到的全局table對象。
table.on('tool(test-table)', function (obj) {
var event = obj.event;
if ("changeStatus" === event) {
var item = obj.data;
// 根據當前狀態確定下一個狀態,這樣來模擬依次變化身份。
if (item.status === "vip") {
item.status = "normal";
} else if (item.status === "normal") {
item.status = "poor";
} else {
item.status = "vip"
}
// 更新改變用戶身份。
obj.update(item);
}
});
上述代碼中,通過事件event
可以拿到點擊的這一行的data數據,然後我們將其status(身份)信息進行修改,並且通過其提供的update
方法進行更新來完成界面上的更新。但發現這樣做了之後,只有數據部分在界面上更新了,如果右邊的toolbar是模板動態渲染的,就不會跟着新狀態數據一起更新。
1、閱讀源碼找到位置
爲了解決這個問題,詢遍大街小巷,發現滿到處都是與之相關的問題,而解決方案大多都是使用表格全局的reload
方法將就用着。我可不是一個將就的人,必須死磕到底。
我們更新數據是通過update
方法進行更新的,二話不說,找到layui的源代碼打開目錄./src/lay/modules/
,找到其中的table.js
組件文件。直接Ctrl+F
進行搜索update方法名。還不算艱辛,直接可以定位到這塊代碼周圍(下面的代碼截取自layui源碼提交版本號[5904e5b1344efa661b53f1cbd2a5d0e5b12ea4ef]的內容):
//數據行中的事件監聽返回的公共對象成員
var commonMember = function(sets){
var othis = $(this)
,index = othis.parents('tr').eq(0).data('index')
,tr = that.layBody.find('tr[data-index="'+ index +'"]')
,data = table.cache[that.key] || [];
data = data[index] || {};
return $.extend({
tr: tr //行元素
,data: table.clearCacheKey(data) //當前行數據
,del: function(){ //刪除行數據
table.cache[that.key][index] = [];
tr.remove();
that.scrollPatch();
}
// 這裏就是我們實際調用更新的代碼位置。
,update: function(fields){ //修改行數據
fields = fields || {};
// 遍歷傳進來的每一個鍵值對(實際上就是我們的每一條數據)。
layui.each(fields, function(key, value){
// data 是通過服務器返回的實際的每一行數據
if(key in data){
// 這邊定義一個templet用於確定下方是使用模板來更新數據還是
// 直接使用字段的值來進行填充。
var templet,
td = tr.children('td[data-field="'+ key +'"]');
data[key] = value;
// 這個 eachCols 就是一個循環,i 是下標, item2 就是我們render函數
// 傳遞進來的每一個col的配置項。
that.eachCols(function(i, item2){
// 判斷指定字段是否使用模板進行數據展示。
// 是的話就直接賦值模板。
if(item2.field == key && item2.templet){
templet = item2.templet;
}
});
// 然後這裏就進行數據的模板渲染,然後更新到表格界面
td.children(ELEM_CELL).html(parseTempData({
templet: templet
}, value, data));
td.data('content', value);
}
});
}
}, sets);
經過上述代碼分析後。我表示很驚訝。因爲整個update
函數裏面根本就沒有一點要去更新toolbar裏面的視圖的意思。 嘶~~。看到這裏,我不禁倒吸一口冷氣。要說layui開發團隊不會寫更新toolbar的方法是不可能的。可爲什麼不完成這個功能呢…難道是因爲…😯我好像發現了什麼。🤫別出聲!
現在,既然找到了入口。不如就自己來實現這個功能吧!
2、實現更新
既然源碼裏沒有更新toolbar的代碼實現,那隻好我們自己來實現這個功能了。我們的代碼只需要在update方法後面繼續追加實現就好了。所以在上述代碼第52行下面繼續追加在實現代碼。實現之前,必須要先理清思路。
- 第一步:先獲取到配置的toolbar的模板id。
- 第二步:使用模板id+數據來生成新的視圖。
- 第三步:找到toolbar應該放置的位置將新的視圖放入。
第一步:獲取到配置的toolbar的模板id
我們要爲多次情況考慮,可能我們會爲一行數據配置多個toolbar操作區域。所以我們需要使用一個數組來保存我們要更新的toolbar區域。然後通過已出現在更數據區域的eachCols
方法獲取到我們配置的toolbar的模板id。如下:
// 兼容一行中有好幾個列使用toolbar的情況,所以使用數組。
var toolbarTemps = [];
// 在下面繼續更新toolbar的內容。
that.eachCols(function (index, colOption) {
// 篩選出配置了toolbar的條目。
if (colOption.toolbar) {
// 拿到了之後,除了需要模板id外,還需要一個key。這個key在初始化時內部已經生成
// 要通過它來定位這個toolbar放在哪一個位置表格td下的位置,所以也獲取下來。
toolbarTemps.push({
temp: colOption.toolbar,
key: colOption.key
});
}
});
第二步:使用數據加模板生成新視圖
現在我們拿到了模板id,已經保存在了toolbarTemps
裏面,只需要再拿到這一行的數據就可以進行生成了。通過原本更新數據的代碼可以不難發現,update
方法裏面的data
就時對應的行的數據:
// 直接循環已經獲取到的模板id。
layui.each(toolbarTemps, function (index, value) {
var temp = value.temp; // 模板id。
var key = value.key; // 用於定位的key。
// 用來保存新的渲染結果。
var tempStr;
if (temp.indexOf("#") > -1) {
// 說明這是模板id。
tempStr = $(temp).html();
} else {
// 否則認爲這個字符串本身就是一個模板字符串。
tempStr = temp;
}
// 使用數據重新渲染
if (tempStr) {
var result = laytpl(tempStr).render(data);
}
});
這裏的代碼中使用了laytpl
組件,不用擔心,可以直接放心使用,table組件是引入了這個組件,並且table組件作用範圍類時可以使用的。
第三步:將生成的結果放在對應的位置
通過閱讀更新數據時的代碼(第二章第一小節示例的代碼),不難發現第4行,index = othis.parents('tr').eq(0).data('index')
獲取的是數據的下表,而且是通過預先設置的dom屬性來獲取的。而這一行代碼中前部分我們需要用到,因此需要對這行代碼進行小修改,將其拆分:
// 原來的代碼
var commonMember = function(sets){
var othis = $(this)
,index = othis.parents('tr').eq(0).data('index')
,tr = that.layBody.find('tr[data-index="'+ index +'"]')
,data = table.cache[that.key] || [];
// 後面的省略...
// 將其修改爲
var commonMember = function(sets){
var othis = $(this)
,rowTr = othis.parents('tr').eq(0)
,index = rowTr.data('index')
,tr = that.layBody.find('tr[data-index="'+ index +'"]')
,data = table.cache[that.key] || [];
// 後面的省略...
拆分完成後,繼續完成上一步的代碼,在獲取到的result下面添加更新界面的邏輯。代碼如下:
// 直接循環已經獲取到的模板id。
layui.each(toolbarTemps, function (index, value) {
var temp = value.temp; // 模板id。
var key = value.key; // 用於定位的key。
// 用來保存新的渲染結果。
var tempStr;
if (temp.indexOf("#") > -1) {
// 說明這是模板id。
tempStr = $(temp).html();
} else {
// 否則認爲這個字符串本身就是一個模板字符串。
tempStr = temp;
}
// 使用數據重新渲染
if (tempStr) {
var result = laytpl(tempStr).render(data);
// 要使用key來確定具體的選擇器,才能正確的找到位置。然後將結果設置進去。
rowTr.find("td[data-off='true'] div.laytable-cell-1-" + key).html(result);
}
});
3、完整修復代碼
現在,所有的工作都已經完成了。來看看最終我們現在把代碼改成了什麼樣:
var commonMember = function(sets){
var othis = $(this)
,rowTr = othis.parents('tr').eq(0)
,index = rowTr.data('index')
,tr = that.layBody.find('tr[data-index="'+ index +'"]')
,data = table.cache[that.key] || [];
data = data[index] || {};
return $.extend({
tr: tr //行元素
,data: table.clearCacheKey(data) //當前行數據
,del: function(){ //刪除行數據
table.cache[that.key][index] = [];
tr.remove();
that.scrollPatch();
}
// 這裏就是我們實際調用更新的代碼位置。
,update: function(fields){ //修改行數據
fields = fields || {};
// 遍歷傳進來的每一個鍵值對(實際上就是我們的每一條數據)。
layui.each(fields, function(key, value){
// data 是通過服務器返回的實際的每一行數據
if(key in data){
// 這邊定義一個templet用於確定下方是使用模板來更新數據還是
// 直接使用字段的值來進行填充。
var templet,
td = tr.children('td[data-field="'+ key +'"]');
data[key] = value;
// 這個 eachCols 就是一個循環,i 是下標, item2 就是我們render函數
// 傳遞進來的每一個col的配置項。
that.eachCols(function(i, item2){
// 判斷指定字段是否使用模板進行數據展示。
// 是的話就直接賦值模板。
if(item2.field == key && item2.templet){
templet = item2.templet;
}
});
// 然後這裏就進行數據的模板渲染,然後更新到表格界面
td.children(ELEM_CELL).html(parseTempData({
templet: templet
}, value, data));
td.data('content', value);
}
});
// 兼容一行中有好幾個列使用toolbar的情況,所以使用數組。
var toolbarTemps = [];
// 在下面繼續更新toolbar的內容。
that.eachCols(function (index, colOption) {
// 篩選出配置了toolbar的條目。
if (colOption.toolbar) {
// 拿到了之後,除了需要模板id外,還需要一個key。這個key在初始化時內部已經生成
// 要通過它來定位這個toolbar放在哪一個位置表格td下的位置,所以也獲取下來。
toolbarTemps.push({
temp: colOption.toolbar,
key: colOption.key
});
}
});
// 直接循環已經獲取到的模板id。
layui.each(toolbarTemps, function (index, value) {
var temp = value.temp; // 模板id。
var key = value.key; // 用於定位的key。
// 用來保存新的渲染結果。
var tempStr;
if (temp.indexOf("#") > -1) {
// 說明這是模板id。
tempStr = $(temp).html();
} else {
// 否則認爲這個字符串本身就是一個模板字符串。
tempStr = temp;
}
// 使用數據重新渲染
if (tempStr) {
var result = laytpl(tempStr).render(data);
// 要使用key來確定具體的選擇器,才能正確的找到位置。然後將結果設置進去。
rowTr.find("td[data-off='true'] div.laytable-cell-1-" + key).html(result);
}
});
}
}, sets);
其實我們只是修改了一份源代碼。而我們項目上用的代碼肯定是經過了混淆之後的代碼。其實可以把對應修改後的table.js
組件文件覆蓋下載的layui目錄裏面的經過混淆的table.js文件,這樣,當我們再次刷新界面,也是可以達到效果的。
4、修復後效果
現在,讓我們看看修復後的效果如何:
現在,可以愉快的調用obj的update方法來完成刷新了,同時右側的工具欄也會隨之刷新。
三、修復版table組件
如果你沒有閱讀以上內容,而是直接來到這一章節,建議你儘量不要使用這個修復版,畢竟它只是針對此問題進行功能性增強修復。使用了此組件你將失去此組件的原版更新支持。在你再三衡量了其重要性時,你可以下載此組件覆蓋你之前使用的table組件。下載地址:
點擊下載。
如果你是使用的layui.all.js這樣的全局一次性引入的話,此組件可能並不適用於你,因爲此修復版組件只是單個table組件。不過如果你閱讀了以上內容,相信即使是經過混淆了之後的代碼、還是使用一份包含所有組件的代碼,都可以很快修復並加入這個功能。
最後,如果對你有幫助。不妨點贊以資對我的鼓勵。