layui-tree如何實現懶載入以及動態區域性渲染樹節點
眾所周知,layui.tree的樹形控制元件,在渲染樹節點時,需要後臺準備好整個組織樹的資料;如果組織樹的資料量特別大,頁面渲染就特別慢。最近在狐小E智慧辦公平臺中,展示企業部門樹時,就遇到這種問題;當時產品要求,部門樹渲染要特別快,同時某部門下新增/刪除了一個子部門時,要動態重新整理,實時呈現;然而layui.tree的原始機制是,必須重新載入整個樹的資料再渲染,這樣就會導致展開的節點全收縮回去,而不是剛好展開到該父部門位置;
這樣就有2個問題亟需優化:
1)部門樹資料量大時,渲染慢。
2)父部門如何動態重新整理子部門,並且區域性更新檢視;
為了解決這2個問題,我們只好修改layui.tree原始碼,實現這種高大上的功能;廢話不多說,直接上乾貨:
狐小E智慧辦公後臺使用的layui版本是v2.5.5,其他版本的layui原始碼可能有所不同,這點需注意。
解決方案
步驟1:原始碼修改
首先,在layui前端框架找到實現樹元件的模組原始碼tree.js,如圖所示:
1:開啟原始碼,在樹的主渲染方法 r.render前,新增上用於載入child節點的方法r.children,程式碼如下:
r.children = function (e, i, d) {
var a = l.that[e];
return a.children(i, d)
}
效果如下圖:
2:找到b.prototype.tree方法,在此方法前,新增兩個用於懶載入子節點的方法:
b.prototype.children = function (n1, n2) {
var e = this;
e.setchildrendata(e.config.data, n1, n2);
},
b.prototype.setchildrendata = function (n0, n1, n2) {
var e = this;
var c = i('#' + e.config.id);
layui.each(n0, function (a, r) {
var b = c.find('div[data-id=' + r.id + ']').hasClass(C);
r.spread = b;
if (e.config.accordion === !0) { //手風琴模式
r.spread = !1;
var cs = c.find('div[data-id=' + n1 + ']').parents('.layui-tree-set');
cs.each(function () {
if (r.id === i(this).attr('data-id')) {
r.spread = !0;
}
});
}
if (r.id === n1) {
r.spread = !0;
if(!r.children){
r.children=[];
}
if (n2.length === 0) {
delete r.children;
}else{
//i.extend(!0, r.children, n2);
r.children = n2;
}
e.reload(e.config.id, e.config.data);
}
if (r.children) {
e.setchildrendata(r.children, n1, n2); //遞迴子節點
}
});
}
3:修改b.prototype.tree方法的程式碼,修改後程式碼如下【備註://TODO 部分標識的是layui原始程式碼】
b.prototype.tree = function (e, a) {
var n = this, t = n.config, r = a || t.data;
layui.each(r, function (a, r) {
//TODO 此處調整原始碼
//var l = r.children && r.children.length > 0,
var l = r.children,
o = i('<div class="layui-tree-pack" ' + (r.spread ? 'style="display: block;"' : "") + '"></div>'),
//TODO 原始碼調整
//h = i(['<div data-id="' + r.id + '" class="layui-tree-set' + (r.spread ? " layui-tree-spread" : "") + (r.checked ? " layui-tree-checkedFirst" : "") + '">', '<div class="layui-tree-entry">', '<div class="layui-tree-main">', function () {
h = i(['<div data-id="' + r.id + '" data-parent-id="' + r.parentId + '" class="layui-tree-set' + (r.spread ? " layui-tree-spread" : "") + (r.checked ? " layui-tree-checkedFirst" : "") + '">', '<div class="layui-tree-entry ' + (l ? "taller" : "") + '" >', '<div class="layui-tree-main">', function () {
//TODO 原始碼調整
//return t.showLine ? l ? '<span class="layui-tree-iconClick layui-tree-icon"><i class="layui-icon ' + (r.spread ? "layui-icon-subtraction" : "layui-icon-addition") + '"></i></span>' : '<span class="layui-tree-iconClick"><i class="layui-icon layui-icon-file"></i></span>' : '<span class="layui-tree-iconClick"><i class="layui-tree-iconArrow ' + (l ? "" : c) + '"></i></span>'
return t.showLine ? l ? '<span class="layui-tree-iconClick layui-tree-icon"><i class="layui-icon ' + (r.spread ? "layui-icon-subtraction" : "layui-icon-addition") + '"></i></span>' : '<span class="layui-tree-iconClick layui-tree-icon"><i class="layui-icon layui-icon-file"></i></span>' : '<span class="layui-tree-iconClick"><i class="layui-icon ' + ( r.spread ? "layui-tree-active" : "layui-tree-iconArrow") + (l ? "" : c)+' "></i></span>'
}(), function () {
return t.showCheckbox ? '<input type="checkbox" name="' + (r.field || "layuiTreeCheck_" + r.id) + '" same="layuiTreeCheck" lay-skin="primary" ' + (r.disabled ? "disabled" : "") + ' value="' + r.id + '">' : ""
}(), function () {
//TODO 修改原始碼 顯示title
//return t.isJump && r.href ? '<a href="' + r.href + '" target="_blank" class="' + y + '">' + (r.title || r.label || t.text.defaultNodeName) + "</a>" : '<span class="' + y + (r.disabled ? " " + d : "") + '">' + (r.title || r.label || t.text.defaultNodeName) + "</span>"
return t.isJump && r.href ? '<a title="' + (r.title || r.label || t.text.defaultNodeName) + '" href="' + r.href + '" target="_blank" class="' + y + '">' + (r.title || r.label || t.text.defaultNodeName) + "</a>" : '<span title="' + (r.title || r.label || t.text.defaultNodeName) + '" class="' + y + (r.disabled ? " " + d : "") + '" >' + (r.title || r.label || t.text.defaultNodeName) + "</span>"
}(), "</div>", function () {
if (!t.edit) return "";
var e = {
add: '<i class="layui-icon layui-icon-add-1" data-type="add"></i>',
update: '<i class="layui-icon layui-icon-edit" data-type="update"></i>',
del: '<i class="layui-icon layui-icon-delete" data-type="del"></i>'
}, i = ['<div class="layui-btn-group layui-tree-btnGroup">'];
return t.edit === !0 && (t.edit = ["update", "del"]), "object" == typeof t.edit ? (layui.each(t.edit, function (a, n) {
i.push(e[n] || "")
}), i.join("") + "</div>") : void 0
}(), "</div></div>"].join(""));
l && (h.append(o), n.tree(o, r.children)), e.append(h), h.prev("." + s)[0] && h.prev().children(".layui-tree-pack").addClass("layui-tree-showLine"), l || h.parent(".layui-tree-pack").addClass("layui-tree-lineExtend"), n.spread(h, r), t.showCheckbox && (r.checked && n.checkids.push(r.id), n.checkClick(h, r)), t.edit && n.operate(h, r)
})
}
4:修改節點展開方法b.prototype.spread,修改後程式碼如下【備註://TODO 部分標識的是layui原始程式碼】
b.prototype.spread = function (e, a) {
var n = this, t = n.config, r = e.children("." + p), l = r.children("." + f), c = r.find("." + o),
k = r.find("." + y), m = t.onlyIconControl ? c : l, x = "";
m.on("click", function (i) {
var ax = e.children("." + v),//TODO a衝突 改成ax
//TODO 修改原始碼
//n = m.children(".layui-icon")[0] ? m.children(".layui-icon") : m.find(".layui-tree-icon").children(".layui-icon");
n = m.children(".layui-icon")[0] ? m.find(".layui-tree-iconClick").children(".layui-icon") : m.find(".layui-tree-icon").children(".layui-icon");
if (ax[0]) { //TODO a改為ax
//TODO 展開節點
if (!e.hasClass(c) && !e.hasClass(C)) {
t.spread && t.spread({
elem: e,
state: a.children.length > 0,
data: a
})
}
//TODO 註釋掉原始碼
/*if (e.hasClass(C)) e.removeClass(C), a.slideUp(200), n.removeClass(u).addClass(h); else if (e.addClass(C), a.slideDown(200), n.addClass(u).removeClass(h), t.accordion) {
var r = e.siblings("." + s);
r.removeClass(C), r.children("." + v).slideUp(200), r.find(".layui-tree-icon").children(".layui-icon").removeClass(u).addClass(h)
}*/
//TODO 節點展開效果調整 新增Begin
if (e.hasClass(C) && !$(i.target).hasClass("layui-tree-txt")) {
e.removeClass(C), ax.slideUp(200)
//修改原始碼,調整無實線狀態時三角圖示有動畫
if (!t.showLine) {
m.find('span .layui-icon').removeClass(aa).addClass(bb)
} else {
n.removeClass(u).addClass(h)
}
} else if (e.addClass(C), ax.slideDown(200), n.addClass(u).removeClass(h), t.accordion) {
var r = e.siblings("." + s);
r.removeClass(C), r.children("." + v).slideUp(200), r.find(".layui-tree-icon").children(".layui-icon").removeClass(u).addClass(h)
} else if (!t.showLine) {
m.find('span .layui-icon').removeClass(bb).addClass(aa)
} //TODO 節點展開效果調整 新增End
} else x = "normal"
})
5:b.prototype.spread 方法修改中,有兩處樣式新增 aa 、bb:如下所示
需要在tree.js中頂部樣式變數處新增。
c = "layui-hide", d = "layui-disabled", s = "layui-tree-set", o = "layui-tree-iconClick",
h = "layui-icon-addition", u = "layui-icon-subtraction", p = "layui-tree-entry", f = "layui-tree-main",
y = "layui-tree-txt", v = "layui-tree-pack", C = "layui-tree-spread", k = "layui-tree-setLineShort",
m = "layui-tree-showLine", x = "layui-tree-lineExtend" ,
aa = "layui-tree-active", bb = "layui-tree-iconArrow", //TODO 新增aa、bb兩個樣式
步驟2:方案落地
解決問題1:非同步載入子節點
下面是狐小E智慧辦公 (https://www.hixiaoe.com)後臺通訊錄模組中,部門樹渲染載入的業務邏輯,其核心邏輯就是通過父節點ID查詢子節點資料,繫結tree元件的spread函式,捕捉節點的展開事件,使用者點選樹節點,根據當前節點ID查詢下一級節點,如果children節點有資料,則呼叫tree的children函式動態地渲染子節點。
//獲取部門樹
function LoadDeptTree() {
$.ajax({
url: "${ctx}/dept/tree/one-level?parentId=0",
dataType: "json",
async: true,
type: "GET",
success: function (resp) {
if (resp && resp.msgcode == 0) {
//無連線線風格
deptTree =tree.render({
elem: '#leftTreeArea'
,id:'leftTreeArea'
, data: resp.data
, showLine: false //是否開啟連線線
, click: editDept
, spread: function (obj) {
if (!obj.state) {
// 懶載入子節點,非同步獲取data資料 這裡根據obj.data.id向後臺請求當前節點資料
$.ajax({
url: "${ctx}/dept/tree/one-level?parentId="+obj.data.id,
dataType: "json",
async: true,
type: "GET",
success: function (resp) {
//當前節點展開,如果下一層有children,則呼叫樹的children方法,動態渲染子節點
if (resp && resp.msgcode == 0) {
//第一個引數是樹繫結的頁面元素ID
//第二個引數是當前展開節點的ID
//第三個引數是當前節點子節點的資料(資料格式參照layui的tree元件資料格式)
tree.children(deptTree.config.id, obj.data.id, resp.data);
}
}
});
}
}
});
} else {
alert("載入部門樹失敗")
}
},
error:function(XMLHttpRequest, textStatus, error){
if (error.code == 19) {
window.location.reload();
}else{
alert("載入部門樹失敗");
}
}
});
}
解決問題2:父部門新增子部門,區域性動態更新檢視
選中父部門,動態地為父部門新增一個子部門,新增成功將新增的子部門區域性渲染到整體部門樹中。下面是新增子部門後,js呼叫一下我自己的refreshTreeNode函式,函式裡有操作layui.tree的區域性更新檢視的程式碼,如下:
//重新渲染樹節點
function refreshTreeNode(treeNodeId,treeNodeName) {
$.ajax({
url: "${ctx}/dept/tree/one-level?parentId="+treeNodeId,
dataType: "json",
async: true,
type: "GET",
success: function (resp) {
if (resp && resp.msgcode == 0) {
tree.children(deptTree.config.id, treeNodeId, resp.data);
if (treeNodeName) {
//修改節點名稱
$("div[data-id='"+treeNodeId+"']").find(".layui-tree-txt").eq(0).html(treeNodeName).attr("title",treeNodeName);
}
//游標重新定位到當前節點
$("div[data-id='"+treeNodeId+"']").find(".layui-tree-entry").eq(0).addClass("layui-table-click");
}
}
});
}
如果想下載我們修改後的程式碼,請點選:
https://oa.hixiaoe.com/static/layui-v2.5.x/src/lay/modules/tree.js
作者介紹:小文文,狐小E智慧辦公(https://www.hixiaoe.com)開發工程師,專注移動辦公軟體的SaaS平臺建設以及輕應用開發
相關文章
- EasyUI Jquery 動態載入樹,點選節點載入UIjQuery
- layui-tree實現Ajax非同步請求後動態新增節點UI非同步
- 滾動載入圖片(懶載入)實現原理
- React如何實現圖片懶載入React
- 圖片懶載入實現
- jquery如何實現動態載入CSS檔案jQueryCSS
- Tablayout+ViewPager動態新增fragment懶載入TabLayoutViewpagerFragment
- 圖片懶載入js實現JS
- Android:Fragment懶載入的實現以及自己的封裝思路AndroidFragment封裝
- python 實現類屬性的懶載入裝飾器Python
- 手把手實現圖片懶載入+封裝vue懶載入元件封裝Vue元件
- 一起來實現圖片滾動懶載入
- 實現圖片懶載入(throttle, debounce)
- [譯] 原生實現圖片懶載入
- 實現圖片懶載入(Lazyload)
- TabLayout+ViewPager+fragment實現懶載入TabLayoutViewpagerFragment
- TabLayout+ViewPager+Fragment懶載入實現TabLayoutViewpagerFragment
- 利用 clip-path 實現動態區域裁剪
- Flutter 區域性路由實現Flutter路由
- React從零實現-節點建立和渲染React
- SICP:賦值和區域性狀態(Python實現)賦值Python
- OpenGL/OpenGL ES入門: 影象渲染實現以及渲染問題
- js動態載入實現提高網頁載入速度JS網頁
- Spring Boot 如何熱載入 jar 實現動態外掛?Spring BootJAR
- Spring Boot 如何熱載入jar實現動態外掛?Spring BootJAR
- Fragment 懶載入實踐Fragment
- banq,能否把你動態實現樹已經隨意將樹節點移動的程式碼貼出來啊?
- Hibernate 基本操作、懶載入以及快取快取
- 深入理解React:懶載入(lazy)實現原理React
- 實現圖片懶載入的三種方式
- 在 Swift 中使用閉包實現懶載入Swift
- vue 動態載入路由,渲染左側選單欄Vue路由
- 懶載入
- OrchardCore 如何動態載入模組?
- 優雅的實現動態載入 css、jsCSSJS
- UNIX下C++實現動態載入物件C++物件
- 動態載入css方法實現和深入解析CSS
- Vue實現一個圖片懶載入外掛Vue