IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> Layui的tree组件实现懒加载节点 -> 正文阅读

[JavaScript知识库]Layui的tree组件实现懒加载节点

为Layui的tree组件添加懒加载生成节点功能(支持其tree组件绑定的默认方法)

需求

  1. 工作中使用了Layui搭建页面,其中有一个功能是下拉框支持树形结构得到数据,且支持多选;
  2. 但是树形结构的数据不是一次性返回,而是每次掉接口返回一个节点数据(后端接口接收一个参数: 父节点的ID, 返回该父节点下直系的子节点数据),不仅需要递归多次,且一次性掉了很多次的接口,性能不好,而且这个接口每分钟还存在调用次数上限;
  3. 所以需要动态加载节点(或者说懒加载吧,我也不知道咋说🤣),反正就是数据中会添加一个字段:hasChildren,如果有这个字段的话文字左边就会可点击的 ? 图标,点击之后调接口根据返回的数据渲染节点
  4. 支持复选框多选

实现1(粗暴的直接操作DOM,模拟layui的css):

根据返回的数据动态生成DOM添加至节点中,其弊端就是没有通过Layui tree组件的渲染,所以不支持tree组件自带的回调来获取参数,需要自己给DOM添加绑定事件,也不支持上面的需求4(或者说实现极其复杂,还要动态生成复选框),这是一个试错的过程(我是先实现1,因为太复杂了才想到了实现2),有兴趣的可以看看,没时间的可以跳过看实现2😂

先实例化tree组件,获取初始数据

初始代码如下所示:
在这里插入图片描述

页面显示如下:
页面显示
可以看到因为初始数据 treeData 中没有children字段,其节点不可展开(连展开按钮都没有),这时候我想的是根据hasChildren 这个字段模拟一个假的children数据chilren: [{title: '',id: ''}]来让其可以展开来;这里我模拟一下调接口:
在这里插入图片描述
这时候我们再看页面就变成了:
在这里插入图片描述
初始效果有了,接下来要实现的就是点击展开图标,调接口获取真的children数据,将里面的假children数据替换掉,为什么上面的截图我将浏览器开发者工具打开且将Elements里面的节点全部展开呢?其实就是为了接下来的操作,当时我想的就是模拟它的dom结构和CSS类名来实现效果,后来发现还要重新绑定事件实现交互功能,****😭(原理就是照葫芦画瓢 生成DOM 绑定事件)

// 通过事件代理 实现点击树形组件展开或者缩回 回调 (事件捕获)
document.getElementById('tree_dep').addEventListener('click', function(e){
    if (e.target.classList.contains('layui-icon-addition')) {
        if (e.target.classList.contains('custom-addition')) {
            addition(e)
        } else {
            subtraction(e)
        }
    }
    if (e.target.classList.contains('layui-icon-subtraction')) {
        if (e.target.classList.contains('custom-addition')) {
            subtraction(e)
        } else {
            addition(e)
        }
    }
}, false)
// 添加/显示 树的子节点
function addition(e) {
    let p_dom = e.target.parentNode.parentNode.parentNode.nextSibling
    let b_dom = e.target.parentNode.parentNode.parentNode.parentNode
    let txt = e.target.parentNode.nextSibling.innerText
    let data_id = b_dom.getAttribute('data-id')
    if (tree_open[data_id]) {
        p_dom.style.display = 'none'
        b_dom.classList.remove('layui-tree-spread')
        e.target.classList.remove('layui-icon-subtraction')
        e.target.classList.add('layui-icon-addition')
        tree_open[data_id] = false
        return
    }
    tree_open[data_id] = true
    if (!lazy_loading[data_id]) {
        do_async(data_id).then(res => {
			b_dom.innerHTML = `<div class="layui-tree-entry">
				<div class="layui-tree-main">
					${res.length? '<span class="layui-tree-iconClick layui-tree-icon"><i class="layui-icon layui-icon-subtraction"></i></span>' : '<span class="layui-tree-iconClick"><i class="layui-icon layui-icon-file"></i></span>'}<span class="layui-tree-txt">${txt}</span>
				</div>
			</div>`
			recur_obj(treeData, data_id, res)
			if (res.length !== 0) {
				let pack_dom = document.createElement('div')
				pack_dom.classList.add('layui-tree-pack', 'layui-tree-lineExtend', 'layui-tree-showLine')
				pack_dom.style.display = 'block'
				for (let i = 0;i < res.length;i++) {
					let item = res[i]
					let div_dom = document.createElement('div')
					div_dom.setAttribute('data-id', item.id)
					div_dom.classList.add('layui-tree-set')
					div_dom.innerHTML = `<div class="layui-tree-entry">
						<div class="layui-tree-main">
							${item.hasChildren ? '<span class="layui-tree-iconClick layui-tree-icon"><i class="layui-icon layui-icon-addition custom-addition"></i></span>' : '<span class="layui-tree-iconClick"><i class="layui-icon layui-icon-file"></i></span>'}<span class="layui-tree-txt">${item.title}</span>
						</div>
					</div>`
					if (item.hasChildren) {
						lazy_loading[item.id] = false
					}
					pack_dom.appendChild(div_dom)
				}
				b_dom.appendChild(pack_dom)
				lazy_loading[data_id] = true
			}
        })
    }
}

// 隐藏树的子节点 
function subtraction(e) {
    let p_dom = e.target.parentNode.parentNode.parentNode.nextSibling
    let b_dom = e.target.parentNode.parentNode.parentNode.parentNode
    let txt = e.target.parentNode.nextSibling.innerText
    let data_id = b_dom.getAttribute('data-id')
    if (!tree_open[data_id]) {
        p_dom.style.display = 'block'
        b_dom.classList.add('layui-tree-spread')
        e.target.classList.remove('layui-icon-addition')
        e.target.classList.add('layui-icon-subtraction')
        return
    }
    tree_open[data_id] = false
}

// 递归修改 treeData
function recur_obj(data, id, arr) {
    for (let i = 0;i < data.length;i++) {
        if (data[i].id == id) {
            data[i].children = arr
            return
        }
        if (data[i].children && data[i].children.length) {
            recur_obj(data[i].children, id, arr)
        }
    }
}

实现的效果:
在这里插入图片描述
按照这个思维,接下来我们就是需要重新给DOM绑定事件,并且根据需求还要生成复选框元素,反正就是很复杂,头都秃了,Layui的文档里面也没有懒加载动态生成节点,最多只有操作节点。。。。
在这里插入图片描述
等等,操作节点中有增加节点的功能,那岂不是源码中有新增节点的功能代码,因此就有了 实现2

实现2(在源码中添加懒加载功能代码)

首先我使用的是从Layui官网下载的自动化构建后的代码,没办法看更别说改了,所以第一步我们从git仓库下载完整开发包 Layui官网链接
在这里插入图片描述
GitHub里面我们下载zip,下载之后解压,解压之后是这样的目录结构:
在这里插入图片描述
用vscode打开,进入src里面看了一下layui.js,这应该是入口文件吧,然后找到modules文件夹里面的tree.js 文件,我们就在这里面给我们的tree 组件添加新功能;首先我们要找到自带的新增节点的功能代码:在这里插入图片描述
如上图所示,我们找到了这块代码,看一下点击新增节点图标tree.js到底做了那些;
然后定义一个我们期望传入的数据结构,以及如何在tree.render()里面增加懒加载的基础参数:

let data = [ // 接收的数据源
	{
		title: '****', // 必需
		id: '****', // 必需
		hasChildren: true, // true/false 必需
		children: [], // 可有可无, 如果hasChildren为false,肯定不需要这个字段
		// 其他自定义数据随便
	}
]
layui.use('tree', function(){
	var tree = layui.tree;
	tree.render({
		// ......基础参数参考Layui官网的树组件文档
		lazyLoad: function(){}, // 我们自定义的懒加载参数lazyLoad,记住它是一个异步方法
	})	
})

然后我们开始在tree.js里面动工,首先如果数据源里面没有children字段但是hasChildren为true时,我们希望有可展开的图标
在这里插入图片描述
有可展开图标了,但是没有数据(即hasChildren为true,children没有或者为空数组)我们就判定这是个懒加载节点,这时候我们给 +图标 加个事件,在如下代码里面加
在这里插入图片描述
圈的是我们自定义的懒加载事件,源代码中没有这个if判断,我们加一个提前判断,接收我们传入的lazyLoad异步函数,如果没有传就会报错

// 懒加载 
      if (item.hasChildren && !item.children) {
        var async_tree = options.lazyLoad || function () {throw new Error('The ASYNC_TREE option is nota found in the ' + MOD_NAME + ' instance')};
        //点击产生的回调
        async_tree(item.id).then(function(res){
          //节点添加子节点容器
          elem.append('<div class="layui-tree-pack"></div>');
          if(options.showLine){
            //节点本身无子节点
            if(!packCont[0]){
              //遍历兄弟节点,判断兄弟节点是否有子节点
              var siblings = elem.siblings('.'+ELEM_SET), num = 1
              ,parentPack = elem.parent('.'+ELEM_PACK);
              layui.each(siblings, function(index, i){
                if(!$(i).children('.'+ELEM_PACK)[0]){
                  num = 0;
                };
              });
    
              //若兄弟节点都有子节点
              if(num == 1){
                //兄弟节点添加连接线
                siblings.children('.'+ELEM_PACK).addClass(ELEM_SHOW);
                siblings.children('.'+ELEM_PACK).children('.'+ELEM_SET).removeClass(ELEM_LINE_SHORT);
                elem.children('.'+ELEM_PACK).addClass(ELEM_SHOW);
                //父级移除延伸线
                parentPack.removeClass(ELEM_EXTEND);
                //同层节点最后一个更改线的状态
                parentPack.children('.'+ELEM_SET).last().children('.'+ELEM_PACK).children('.'+ELEM_SET).last().addClass(ELEM_LINE_SHORT);
              }else{
                elem.children('.'+ELEM_PACK).children('.'+ELEM_SET).addClass(ELEM_LINE_SHORT);
              };
            }
            elemMain.find('.'+ICON_CLICK).addClass('layui-tree-icon');
            elemMain.find('.'+ICON_CLICK).children('.layui-icon').addClass(ICON_ADD).removeClass('layui-icon-file');
          //若未开启连接线,显示箭头
          }else{
            elemMain.find('.layui-tree-iconArrow').removeClass(HIDE);
          };
          // 添加数据
          item.children = res
          // 渲染节点
          that.tree(elem.children('.'+ELEM_PACK), res);
          if(options.showCheckbox) {
            //若开启复选框,同步新增节点状态
            if(elemMain.find('input[same="layuiTreeCheck"]')[0].checked){
              var packLast = elem.children('.'+ELEM_PACK).children('.'+ELEM_SET).last();
              packLast.find('input[same="layuiTreeCheck"]')[0].checked = true;
            };
            that.renderForm('checkbox');
          };
          // 默认展开
          elem.addClass(ELEM_SPREAD);
          elem.children('.'+ELEM_PACK).slideDown(200);
          // 修改图标状态
          var iconClick = touchOpen.children('.layui-icon')[0] ? touchOpen.children('.layui-icon') : touchOpen.find('.layui-tree-icon').children('.layui-icon');
          iconClick.addClass(ICON_SUB).removeClass(ICON_ADD);
        }).catch(function(rej){
          console.log(rej)
        })
      }

最终我们定义数据源和基础参数如下所示

let treeData = null,
	lazy_loading = {}, // 节点是否需要懒加载
    tree_open = {}; // 节点处于展开还是收缩状态
treeData = [{ // 原始数据 也是通过接口获取
		description: "集团总部",
		hasChildren: true,
		id: "000001",
		title: "集团总部",
	}]
let asyncData = {
	"000001": [ // 根据id:000001 调接口获取而来
		{description: "北京分部", hasChildren: true, id: "000002", title: "北京分部"},
		{description: "南京分部", hasChildren: true, id: "000003", title: "南京分部"},
		{description: "济南分部", hasChildren: true, id: "000004", title: "济南分部"},
		{description: "广州分部", hasChildren: true, id: "000005", title: "广州分部"},
		{description: "天津分部", hasChildren: true, id: "000006", title: "天津分部"},
		{description: "合肥分部", hasChildren: true, id: "000007", title: "合肥分部"},
		{description: "深圳分部", hasChildren: true, id: "000008", title: "深圳分部"},
		{description: "武汉分部", hasChildren: true, id: "000009", title: "武汉分部"},
		{description: "上海分部", hasChildren: true, id: "000010", title: "上海分部"},
		{description: "杭州分部", hasChildren: true, id: "000011", title: "杭州分部"},
		{description: "西安分部", hasChildren: true, id: "000012", title: "西安分部"}
	],
	"000002": [ // // 根据id:000002 调接口获取而来
		{"description":"北京分部-职能部门", "hasChildren":false, "id":"000002-001", "title":"职能团队"},
		{"description":"北京分部-项目团队", "hasChildren":false, "id":"000002-002", "title":"项目团队"},
		{"description":"北京分部-管理部门", "hasChildren":false, "id":"000002-003", "title":"管理部门"},
		{"description":"北京分部-后勤部门", "hasChildren":true, "id":"000002-004", "title":"后勤部门"},
		{"description":"北京分部-销售团队", "hasChildren":false, "id":"000002-005", "title":"销售团队" },
		{"description":"北京分部-财政部门", "hasChildren":false, "id":"000002-006", "title":"财政部门"},
		{"description":"北京分部-渠道团队", "hasChildren":false, "id":"000002-007", "title":"渠道团队"},
		{"description":"北京分部-福利员工部", "hasChildren":false, "id":"000002-008", "title":"福利员工部"}
	]
}
layui.use('tree', function(){
    tree = layui.tree;
    tree.render({
        elem: '#tree_dep',
        id: 'tree_dep',
        data: treeData,
        accordion: true,
        onlyIconControl: true,
        showCheckbox: true,
        lazyLoad: function (id) {
        	// console.log([1,2,3])
        	return new Promise(function(res, rej){
				if (!asyncData[id]) {
					alert('获取失败')
					rej('errorMsg')
				} else {
					setTimeout(function () {
						res(asyncData[id])
					}, 20)
				}
        	})
        },
        click: function(obj){
        	console.log(obj.data)
        }
    })
})

实际效果:
在这里插入图片描述
这样就改好了,其他配置按照Layui官网里面的配置就行,事件的话也是完美兼容的(起码我测试是这样的😉,如果有测试出来问题的话,评论区可以讨论交流🧐);

代码的话我也贴一下(就和官网开始使用的方法一是一样的目录结构,向它介绍的那样引用就行了):代码压缩包
(备注:没有积分的小伙伴想要的话给我发消息就好了😗),大家想要实现的话也可以按照这个思路动手试一试

说到这里就要讲讲我用它默认的gulp自动化构建的时候出现的两个问题:

  1. 首先是node版本过高,报错:primordials is not defined,后来我用nvm将node 版本降为v8.7.0就不报错了,但是出现了问题2
  2. 原因竟然是因为箭头函数写顺手了,在tree.js里面写了两个箭头函数,它这个源码里用的都是es5的语法,我用了es6的箭头函数导致它编译出错,后来引用了gulp-babel等插件还是解决不了,我就干脆把箭头函数改为function(){}了,实在是找不出来解决办法😭在这里插入图片描述
  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-07-16 11:11:21  更:2021-07-16 11:13:53 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/3 1:51:38-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码