树形操作-树形拖拽选择
标签:
JavaScript
树形操作数据,做个整理总结。本篇是关于树节点拖拽选择,重新生成一棵新树,并支持删除节点。demo 是基于 jquery 及 easy-ui 库实现的。
前言:
实现功能点:
树形展示、筛选
左侧节点支持拖拽到右侧,并且重组为树形展示
右侧已选树形节点支持删除,并统计选择的子节点个数
支持默认有子节点数据
截图:
具体实现-loading:
利用 css3 实现。主要运用了 :before
,:after
选择器‘画’了两个圆,然后利用border
进行圆的调整,最后利用animation
及transform:rotate()
实现旋转动画。
html:
<div class="loading"></div>
css:
.loading { height: 100%; line-height: 100%; position: relative; } .loading:before { position: absolute; top: calc(50% - 30px); left: calc(50% - 30px); content: ""; width: 60px; height: 60px; border-radius: 100%; border: 5px solid skyblue; border-left-color: transparent; border-right-color: transparent; animation: loading 1s linear infinite; } .loading:after { position: absolute; top: calc(50% - 30px); left: calc(50% - 30px); content: ""; width: 60px; height: 60px; border-radius: 100%; border: 5px solid yellow; border-top-color: transparent; border-bottom-color: transparent; animation: loading 2s linear infinite; } @keyframes loading { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
具体实现-树形选择:
主要 html 结构如下,css 就不展示,比较简单。
<div class="tree-select"> <div class="tree-left"> <div class="search tree-header" id="search"> <input class="search-input" placeholder="搜索标签" value="" /> </div> <ul class="tree-con loading" id="tree_select"></ul> </div> <div class='tree-right'> <div class="tip-wrap tree-header"> <span class="tip-num">已选标签(<span class="num" id="tree_selected_num">0</span>)</span> <span class="clear-all" id="tree_selected_clear">清空</span> </div> <ul class="tree-con" id="tree_selected"> </ul> </div></div>
js 代码比较多,先放个 js 代码组织结构图:
其中 event 中实现的是筛选和清空功能。
1. 数据获取,并渲染左侧树:renderSelectTree
treeSelectMod.$treeTarget.addClass('loading'); setTimeout(function () { // 实际中,通过 ajax 获取数据,这儿用 延时 模拟 treeSelectMod.$treeTarget.removeClass('loading'); if (result && result.flag && result.data && result.data.length) { //实例化树形菜单 treeSelectMod.$treeTarget.tree({ data: result.data, dnd: true, // 允许拖拽 formatter: function(node) { // 统计子节点个数 var text = '<span class="node-name">' + node.text + '</span>'; if (node.children && node.children.length > 0) { text += '<i class="tip">(' + node.children.length + ')</i>'; } return text; }, onLoadSuccess: function(e, node) { // 折叠树 treeSelectMod.$treeTarget.tree('collapseAll'); // 节点上禁止放置 $.each($('#tree_select .tree-node'), function(i, item) { $(item).droppable("disable"); }); } }); treeSelectMod.setDragAndDrop(); // 编辑回填:需要有已选数据 if (isEdit) { treeSelectMod.editInit(result.data); } } else { treeSelectMod.$treeTarget.html(treeSelectMod.noDataHtml); } }, 1000);
2. 拖拽操作:setDragAndDrop
// 设置被拖元素$("#tree_select .tree-node").draggable({ proxy: 'clone', revert: true, // 拖动结束后节点将返回它的开始位置 cursor: 'pointer', onStartDrag: function() { $(this).draggable('proxy').css({ 'background': '#fff', 'opacity': '0.5' }); }, onStopDrag: function(e) { // 拖拽置放位置,不是目标不进行操作 if (e.target.id != 'tree_selected') { return true } var node = treeSelectMod.$treeTarget.tree('getNode', e.data.target); // 获取被拖动的节点数据 // 过滤 var selectedData = treeSelectMod.selectedData; if (node.children && node.children.length) { // 被拖拽节点是父节点:判断选中数据中是否有当前节点,没有,就加入,有就替换(保证子节点都正确) var parentNode = { id: node.id, text: node.text, children: [], state: node.state || 'closed' }; node.children.forEach(function(item) { parentNode['children'].push({ id: item.id, text: item.text }); }) var hasSameParentNode = false; for (var i = 0; i < selectedData.length; i++) { if (selectedData[i].id == node.id) { hasSameParentNode = true; selectedData[i] = parentNode; break; } } if (!hasSameParentNode) { selectedData.push(parentNode); } } else { // 被拖拽节点为子节点:需要子节点带着其父节点,便于数据正确 // 通过被拖拽节点找其父节点,在选中的数据中进行同级比较,先找到相等的父节点,再在选中的子节点中判断是否有当前拖拽节点,没有就加入子节点,有不进行操作。如果连父节点都没有相等的,就连父节点一起加入 var parent = treeSelectMod.$treeTarget.tree('getParent', e.data.target); var childNode = { id: node.id, text: node.text }; if (parent) { var parentNode = { id: parent.id, text: parent.text, state: parent.state || 'closed' }; parentNode['children'] = []; parentNode['children'].push(childNode); var hasSameParentNode = false; for (var i = 0; i < selectedData.length; i++) { if (selectedData[i].id == parent.id) { hasSameParentNode = true; var children = selectedData[i]['children'] || []; var arr = children.filter(function(item) { return item.id == node.id; }); if (!arr.length) { // 不存在,加入子节点 children.push(childNode); } break; } } if (!hasSameParentNode) { // 没有相等父节点,连父节点一起加入 selectedData.push(parentNode); } } } // 最后,渲染 DOM treeSelectMod.renderSelectedTree(treeSelectMod.selectedData); } });//设置目标对象允许放置被拖元素$("#tree_selected").droppable();
3. 渲染已选择树,及树节点删除:renderSelectedTree
$('#tree_selected').tree({ data: treeData, formatter: function(node) { var text = '<span class="node-name">' + node.text + '</span>'; if (node.children && node.children.length > 0) { text += '<i class="tip">(' + node.children.length + ')</i>'; } text += '<span class="tree-node-del">x</span>'; return text; }, onClick: function(node) { // 删除节点 var selectedData = treeSelectMod.selectedData; if (node.children && node.children.length) { // 删除节点是父节点:从已选择的中找到删除节点,进行删除 for (var i = 0; i < selectedData.length; i++) { if (selectedData[i].id == node.id) { selectedData.splice(i, 1); } } } else { // 删除节点是子节点:从已选择的子级中找到删除子节点,进行删除,并展开其父节点,方便查看是否删除 for (var i = 0; i < selectedData.length; i++) { for (var j = 0; j < selectedData[i].children.length; j++) { if (selectedData[i].children[j].id == node.id) { selectedData[i].children.splice(j, 1); selectedData[i].state = 'open'; } } if (!selectedData[i].children.length) { // 最后判断,如果父节点下没有子节点了,就把父节点删除 selectedData.splice(i, 1); } } } // 重新根据过滤后的数据进行渲染 DOM treeSelectMod.renderSelectedTree(treeSelectMod.selectedData); }, onLoadSuccess: function() { // 统计选择的标签:子节点参与统计 $('#tree_selected_num').html(treeSelectMod.getTreeChildNum(treeSelectMod.selectedData)); } });
其他操作比较简单,就不赘述了,具体实现可看demo。
实现结果,只要每次右侧节点拖拽放置了以及右侧节点进行了节点删除挫折,左侧 DOM 就要进行一遍渲染。可能会存在性能问题,但目前没更好的实现方案。(欢迎有更好方案的提供建议,~~)
最后就是,目前实现的方案,只支持二级树结构,多级是不支持的。
点击查看更多内容
为 TA 点赞
评论
共同学习,写下你的评论
评论加载中...
作者其他优质文章
正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦