ztree高级实例(原创)

最近项目中需要设计一个类似收藏夹的功能保存用户常用的东西,需要客户体验好,所以想到了以前用过的ztree。

在用ztree的过程中遇到一些问题,在此记录,提醒自己,也帮助遇到相同情况的同行们。

1.至少在版本3.5.14中exedit和exhide两个js还是有冲突的。表现为拖拽节点到新建的节点下时无法获取目标节点tId,但是专门用方法获取tId却能成功获取。所以在demo中只好用禁止拖拽和字体颜色变黑代替节点隐藏。

2.变成异步读取保存数据后,跟提供的demo有较大不同,因为我需要实时更新数据,即每步增删改都需要异步处理,而不是退出时才保存数据。


新建节点异步保存问题:

一开始的设计是,新建节点后立即进入编辑状态,完成编辑前保存数据,但发现存在2个问题:

一是用户新建节点后可以按ESC退出编辑状态,二是新建节点较快时偶尔不能进入编辑状态的情况。

于是改成新建节点的时候是先增加节点,成功后再异步保存,但这样有时节点增加了,数据却没成功保存,

之后,又改成了先保存数据,成功后增加节点。

但又出现另一个问题,有时新建的节点变成了双份。加一次竟然会出来2个。

分析原因,感觉是父级节点加载或新建后还没展开过,增加子级节点时会异步加载数据,刚好这时已经插入了一条,而前端也会生成一个。

这个问题还没有完美解决,暂时通过首次加载时全部展开,新建父节点时,成功添加子级节点后父级节点和子级节点都展开,这样不会出现2个节点的情况。

基于父节点第一次展开时才加载数据的推测,拖拽出现2个节点的情况也是这样处理的。


拖拽节点异步保存问题:(以下提到的加载指父节点加载子节点)

经过测试,发现在树加载完成后全部展开节点,只会对有子节点的父节点进行加载。即有子节点的父节点的zAsync属性为true,无子节点的父节点的zAsync==false,因此在拖拽节点到无子节点的父节点时会进行加载。

在我测试实例时,拖拽节点有时会出现重复,节点变2份,这是因为我把保存节点的代码放到了BeforeDrop中。如果拖到未展开的目标节点马上放下,就会出现双份,节点加载会有一点延时,如果很快将节点放下,那么数据已经保存到数据库,那么加载的和ztree创建的就是双份。如果拖到未展开的目标节点停一会在放下就不会出现双份,当拖拽节点到目标节点停住的时候会展开该节点同时触发目标节点的加载,在加载时还未保存数据,因此不会出现重复的情况。而如果拖到已展开的无子节点的父节点时,总是会出现双份。

但是我们不可能要求用户,“拖拽的时候慢点”,所以这个问题需要解决。

目前找到了2种解决办法:

1.用reAsyncChildNodes强制加载。在在树加载完成并全部展开父节点后,判断所有父节点的zAsync属性,为false则强制加载该节点。这样拖拽时都不会再加载了。

2.把保存节点的代码放到了onDrop中。也就是拖拽节点成功后,保存数据,如果保存不成功还可以调用remove方法删除该节点。


  1 <!DOCTYPE html>
  2 <HTML>
  3 <HEAD>
  4     <TITLE> ZTREE DEMO - edit</TITLE>
  5     <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  6     <link rel="stylesheet" href="../../../css/demo.css" type="text/css">
  7     <link rel="stylesheet" href="../../../css/zTreeStyle/zTreeStyle.css" type="text/css">
  8     <script type="text/javascript" src="../../../js/jquery-1.4.4.min.js"></script>
  9     <script type="text/javascript" src="../../../js/jquery.ztree.all-3.5.min.js"></script>
 10     <script type="text/javascript" src="../../../js/jquery.ztree.exhide-3.5.min.js"></script>
 11     <SCRIPT type="text/javascript">
 12         <!--
 13         var setting = {
 14             
 15             view : {
 16                 fontCss: {},
 17                 addHoverDom: addHoverDom,
 18                 removeHoverDom: removeHoverDom,
 19                 selectedMulti: true
 20             },
 21             edit: {
 22                 enable: true,
 23                 editNameSelectAll: true,
 24                 removeTitle : "删除",
 25                 renameTitle : "编辑",
 26                 showRemoveBtn: true,
 27                 showRenameBtn: showRenameBtn,
 28                 drag: {
 29                     isCopy: false,
 30                     isMove: true,
 31                     prev: canPrevAndNext,
 32                     next: canPrevAndNext,
 33                     inner: true
 34                 }
 35             },
 36             data: {
 37                 keep: {
 38                     leaf: true,
 39                     parent: true
 40                 },
 41                 simpleData: {
 42                     enable: true
 43                 }
 44             },
 45             callback: {
 46                 //beforeDrag: beforeDrag
 47                 beforeRemove: zTreeBeforeRemove,
 48                 beforeRename: zTreeBeforeRename,
 49                 beforeDrop: zTreeBeforeDrop
 50             }
 51         };
 52         var setting2 = {
 53             
 54             view : {
 55                 fontCss: setFontCss,
 56                 dblClickExpand : true,
 57                 selectedMulti: true,
 58                 expandSpeed: "fast"
 59             },
 60             
 61             edit: {
 62                 enable: true,
 63                 drag: {
 64                     isCopy: true,
 65                     isMove: false,
 66                     prev: false,
 67                     next: false,
 68                     inner: false
 69                 },
 70                 showRemoveBtn: false,
 71                 showRenameBtn: false
 72             },
 73             data : {
 74                 keep: {
 75                     leaf: true,
 76                     parent: true
 77                 },
 78                 simpleData : {
 79                     enable : true
 80                 }
 81             },
 82             callback : {
 83                 beforeDrag: beforeDrag,
 84                 beforeDrop: zTreeBeforeDrop
 85                 
 86             }
 87         };
 88 
 89         var zNodes =[
 90             { id:1, pId:0, name:"父节点 1", open:true},
 91             { id:11, pId:1, name:"叶子节点 1-1"},
 92             { id:12, pId:1, name:"叶子节点 1-2"},
 93             { id:2, pId:0, name:"父节点 2", open:true},
 94             { id:21, pId:2, name:"叶子节点 2-1"},
 95             { id:23, pId:2, name:"叶子节点 2-3"},
 96             { id:3, pId:0, name:"父节点 3", open:true},
 97             { id:32, pId:3, name:"叶子节点 3-2"},
 98             { id:33, pId:3, name:"叶子节点 3-3"}
 99         ];
100         var zNodes2 =[
101             { id:1, pId:0, name:"父节点 1", open:true, drag:false},
102             { id:11, pId:1, name:"叶子节点 1-1"},
103             { id:12, pId:1, name:"叶子节点 1-2"},
104             { id:13, pId:1, name:"叶子节点 1-3"},
105             { id:14, pId:1, name:"父节点 14", open:true, drag:false},
106             { id:141, pId:14, name:"叶子节点 14-1"},
107             { id:142, pId:14, name:"叶子节点 14-2"},
108             { id:143, pId:14, name:"叶子节点 14-3"},
109             { id:2, pId:0, name:"父节点 2", open:true, drag:false},
110             { id:21, pId:2, name:"叶子节点 2-1"},
111             { id:22, pId:2, name:"叶子节点 2-2"},
112             { id:23, pId:2, name:"叶子节点 2-3"},
113             { id:3, pId:0, name:"父节点 3", open:true, drag:false},
114             { id:31, pId:3, name:"叶子节点 3-1"},
115             { id:32, pId:3, name:"叶子节点 3-2"},
116             { id:33, pId:3, name:"叶子节点 3-3"}
117         ];
118         function canPrevAndNext(treeId, nodes, targetNode) {
119             return !targetNode.isParent;
120         }
121         
122         //用于捕获节点被拖拽之前的事件回调函数,并且根据返回值确定是否允许开启拖拽操作
123         function beforeDrag(treeId, treeNodes) {
124             for (var i=0,l=treeNodes.length; i<l; i++) {
125                 if (treeNodes[i].drag === false) {
126                     return false;
127                 } else if (treeNodes[i].parentTId && treeNodes[i].getParentNode().childDrag === false) {
128                     return false;
129                 }
130             }
131             return true;
132         }
133         //节点后的新建文件夹图标方法
134         var newCount = 1;
135         function addHoverDom(treeId, treeNode) {
136             var sObj = $("#" + treeNode.tId + "_span");
137             if (!treeNode.isParent || treeNode.editNameFlag || $("#addBtn_"+treeNode.tId).length>0) return;
138             var addStr = "<span class='button add' id='addBtn_" + treeNode.tId    + "' title='新建文件夹' onfocus='this.blur();'></span>";
139             sObj.after(addStr);
140             var btn = $("#addBtn_"+treeNode.tId);
141             if (btn) btn.bind("click", function(){
142                 var zTree = $.fn.zTree.getZTreeObj(treeId);
143                 var currentTime = GetCurrentTime();
144                 treeNode = zTree.addNodes(treeNode, {id:currentTime, pId:treeNode.id, isParent:true, name:"新建文件夹" + (newCount++)});
145                 if (treeNode) {
146                     zTree.editName(treeNode[0]);
147                 } else {
148                     alert("新建文件夹失败!请稍后再试");
149                 }
150             });
151         };
152         function removeHoverDom(treeId, treeNode) {
153             $("#addBtn_"+treeNode.tId).unbind().remove();
154         };
155         function showRenameBtn(treeId, treeNode) {
156             return treeNode.isParent;
157         }
158         function updateNodes(nodeList) {
159             var zTree = $.fn.zTree.getZTreeObj("treeDemo2");
160             for( var i=0; i<nodeList.length; i++) {
161                 nodeList[i].drag = (nodeList[i].drag == null) ? false: !nodeList[i].drag;
162                 zTree.updateNode(nodeList[i]);
163             }
164         }
165         //个性化样式
166         function setFontCss(treeId, treeNode) {
167             return (treeNode.isParent == false && treeNode.drag == false) ?  {color:"#d0d0d0", "font-weight":"bold"} : {color:"", "font-weight":""};
168         }
169         //用于捕获节点被删除之前的事件回调函数,并且根据返回值确定是否允许删除操作
170         function zTreeBeforeRemove(treeId, treeNode) {
171             if(!confirm(" 确认删除""+treeNode.name +""及其子节点?")){
172                  return false;
173             }
174             changNodesStat(treeNode);
175             return true;
176         }
177         //用于捕获节点编辑名称结束(Input 失去焦点 或 按下 Enter 键)之后,更新节点名称数据之前的事件回调函数,并且根据返回值确定是否允许更改名称的操作
178         function zTreeBeforeRename(treeId, treeNode, newName, isCancel) {
179             if (newName.length == 0) {
180                 alert("名称不能为空!");
181                 var zTree = $.fn.zTree.getZTreeObj(treeId);
182                 setTimeout(function(){zTree.editName(treeNode);}, 10);
183                 return false;
184             }
185         }
186         //用于捕获节点拖拽操作结束之前的事件回调函数,并且根据返回值确定是否允许此拖拽操作
187         function zTreeBeforeDrop(treeId, treeNodes, targetNode, moveType) {
188             updateNodes(treeNodes);
189         }
190         //“新建文件夹”按钮的方法
191         function add(e) {
192             var zTree = $.fn.zTree.getZTreeObj("treeDemo"),
193             isParent = e.data.isParent,
194             nodes = zTree.getSelectedNodes(),
195             treeNode = nodes[0];
196             if (treeNode && !treeNode.isParent == true) {
197                 alert("只能在文件夹或根目录下新建文件夹!");
198                 return;
199             }
200             var pId = (treeNode) ? treeNode.id : "0";
201             var currentTime = GetCurrentTime();
202             treeNode = zTree.addNodes(treeNode, {id:currentTime, pId:pId, isParent:isParent, name:"新建文件夹" + (newCount++)});
203             if (treeNode) {
204                 zTree.editName(treeNode[0]);
205             } else {
206                 alert("新建文件夹失败!请稍后再试");
207             }
208         };
209         function GetCurrentTime() {
210             var myDate = new Date();
211             var year = myDate.getFullYear();
212             var month = parseInt(myDate.getMonth().toString()) + 1; //month是从0开始计数的,因此要 + 1
213             var date = myDate.getDate();
214             var hour = myDate.getHours();
215             var minute = myDate.getMinutes();
216             var second = myDate.getSeconds();
217             var millisecond = myDate.getMilliseconds();
218             if (month < 10) month = "0" + month.toString();
219             if (date < 10) date = "0" + date.toString();
220             if (hour < 10) hour = "0" + hour.toString();
221             if (minute < 10) minute = "0" + minute.toString();
222             if (second < 10) second = "0" + second.toString();
223             if(millisecond < 10){
224                 millisecond = "00" + millisecond.toString();
225             }else if(millisecond < 100){
226                 millisecond = "0" + millisecond.toString();
227             }
228             var currentTime = year.toString() + month.toString() + date.toString() + hour.toString() + minute.toString() + second.toString() + millisecond.toString(); //返回时间的数字组合
229             return currentTime;
230         }
231         function changNodesStat(treeNode){
232             if(treeNode && treeNode.isParent === false){
233                 var zTree2 = $.fn.zTree.getZTreeObj("treeDemo2");
234                 var node = zTree2.getNodeByParam("id", treeNode.id);
235                 if(node) updateNodes([node]);
236             }else {
237                 var nodes = filterNodes(treeNode);
238                 updateNodes(nodes);
239             }
240         }
241         //自定义过滤器,传入treeDemo的父节点treeNode,返回treeDemo2中匹配id与treeNode下的叶子节点相同的节点
242         function filterNodes(treeNode){
243             var zTree2 = $.fn.zTree.getZTreeObj("treeDemo2");
244             var nodes = zTree2.getNodesByFilter(filter,false,null,treeNode);
245             return nodes;
246         }
247         function filter(node,treeNode) {
248             var flag = false;
249             var zTree = $.fn.zTree.getZTreeObj("treeDemo");
250             var nodes = zTree.getNodesByParam("isParent", false , treeNode);
251             for ( var i = 0; i < nodes.length; i++) {
252                 flag = (nodes[i].id == node.id);
253                 if(flag)break;
254             }
255             return flag;
256         }
257         
258         $(document).ready(function(){
259             $.fn.zTree.init($("#treeDemo"), setting, zNodes);
260             $.fn.zTree.init($("#treeDemo2"), setting2, zNodes2);
261             $("#addParent").bind("click", {isParent:true}, add);
262             changNodesStat();
263 
264             //获取全部节点
265             //var zTree2 = $.fn.zTree.getZTreeObj("treeDemo2");
266             //var nodes = zTree2.getNodesByParam("null", null , null);
267             //alert(nodes.length);            
268         });
269         //-->
270     </SCRIPT>
271     <style type="text/css">
272 .ztree li span.button.add {margin-left:2px; margin-right: -1px; background-position:-144px 0; vertical-align:top; *vertical-align:middle}
273     </style>
274 </HEAD>
275 
276 <BODY>
277 
278 <div class="content_wrap">
279     
280     <div class="zTreeDemoBackground left">
281         常用指标:<input type="button" id="addParent" value="新建文件夹" onclick="return false;">
282         <ul id="treeDemo" class="ztree"></ul>
283     </div>
284     
285     <div class="zTreeDemoBackground right">
286         全量指标:
287         <ul id="treeDemo2" class="ztree"></ul>
288     </div>
289 </div>
290 
291 </BODY>
292 </HTML>
demo

说明:里面需要的js和css都可以在ztree官网找到

 

原文地址:https://www.cnblogs.com/guodefu909/p/ztree-demo.html