zTree 是一个依靠 jQuery 实现的多功能 “树插件”。优异的性能、灵活的配置、多种功能的组合是 zTree 最大优点。
如何简单构建一个树?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <html> <head> <meta charset="UTF-8"> <title>zTree Test</title>
<script src="../bower_components/jquery/dist/jquery.js"></script>
<link rel="stylesheet" href="../bower_components/ztree/css/zTreeStyle/zTreeStyle.css" type="text/css"> <script type="text/javascript" src="../bower_components/zTree/js/jquery.ztree.core.js"></script> </head> <body> <ul id="treeDemo" class="ztree"></ul>
<script> var zTreeObj; var setting = {}; var zNodes = [ { name: "test1", open: true, children: [ { name: "test1_1" }, { name: "test1_2" }] }, { name: "test2", open: true, children: [ { name: "test2_1" }, { name: "test2_2" }] } ]; $(document).ready(function () { zTreeObj = $.fn.zTree.init($("#treeDemo"), setting, zNodes); }); </script> </body> </html>
|
效果:
$.fn.zTree.init()
是否可以多次调用?
可以。因为在init方法中会对容器做清理和事件解绑操作。
如何获得一个树的数据?
zTreeObj.getNodes()
方法可以获取所有节点:
可以看出节点是对传入的节点数据做了拷贝和扩展的。
获取数据是否有开销?
1 2 3
| zTreeObj.getNodes: function (setting) { return data.nodeChildren(setting, data.getRoot(setting)); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| data.getRoot: function (setting) { return setting ? roots[setting.treeId] : null; }
data.nodeChildren: function (setting, node, newChildren) { if (!node) { return null; } var key = setting.data.key.children; if (typeof newChildren !== 'undefined') { node[key] = newChildren; } return node[key]; }
|
从代码中可以可以看出,获取节点数据是直接返回内部维护的数据,所以基本无开销。
“zTree内部的数据对象”是什么意思
在zTree的文档中,很多地方会看到“请务必保证此节点数据对象 是 zTree 内部的数据对象”这种描述,比如要更新一个节点,那么会要求指定的节点必须是内部数据对象。
这里的内部数据对象是什么意思呢?
因为zTree对于传入的节点数据都会复制一份,所以用于新建传入的节点数据是没什么用的,zTree内部不会保存引用,所以也就无法判断这个到底是哪个节点。
然后zTree内部维护了一个树对应的数据结构,是在传入的节点数据上做了扩展的对象。我们在getNodes系列函数中可以得到这些节点,然后就可以对这些节点进行操作。
所以文档要强调是“内部”数据结构。
这种设计是一种设计风格。另外的设计风格是在使用外部传入的对象,那么会带来很多的复杂性。因为外部对于对象的修改是不可控的,那么内部维持这个更新就非常麻烦了。
如何添加一个树节点?
zTreeObj.addNodes(parentNode, [index], newNodes, isSilent)
parentNode
在哪个父节点下添加。如果要添加顶级节点,传null。
index
添加的位置,0为第一个,-1为最后一个(可选)
newNodes
新添加的节点数据,传入后会被拷贝
isSilent
是否静默添加节点,如果为true,不会展开节点。默认为false(可选)
如何更新一个树节点?
更新节点是一个比较泛的操作。
- 更新节点的名称
- 更新节点图标
- 关闭/打开节点
- 向节点插入/删除子元素
更新节点名称/图标等
zTreeObj.updateNode(treeNode, checkTypeFlag)
- 可针对 name、target、 url、icon、 iconSkin、checked、nocheck 等这几个用于显示效果的参数进行更新,其他用于 zTreeNodes 的参数请不要随意更新,对于展开节点,还请调用 expandNode方法,因此请勿随意修改 open 属性。
- 用此方法修改 checked 勾选状态不会触发 beforeCheck / onCheck 事件回调函数。
打开关闭节点
zTreeObj.expandNode(treeNode, [expandFlag], [sonSign], [focus], [callbackFlag])
treeNode
需要打开关闭的节点
expandFlag
true展开节点,false折叠节点(可选,不传则根据当前状态)
sonSign
是否对子孙节点进行expandFlag的操作(可选,默认false)
focus
是否通过设置焦点保证此焦点进入可视区域内(可选,默认true)
callbackFlag
true 表示执行此方法时触发 beforeExpand / onExpand 或 beforeCollapse / onCollapse 事件回调函数(可选,默认false)
- 返回值:表示最终实际操作情况,true:展开,false:折叠,null:不是父节点
zTreeObj.expandAll(expandFlag)
此方法不会触发 beforeExpand / onExpand 和 beforeCollapse / onCollapse 事件回调函数。
向节点插入/删除子元素
添加/插入节点:zTreeObj.addNodes(parentNode, [index], newNodes, isSilent)
删除节点:zTreeObj.removeNode(treeNode, callbackFlag)
清空子节点:zTreeObj.removeChildNodes(parentNode)
如何获取选中的节点?
获取选中的节点:zTreeObj.getSelectedNodes()
- 如果没有选中节点,返回空数组。如果选中多个节点,按照选中的顺序返回。
选中节点:zTreeObj.selectNode(treeNode, [addFlag], [isSilent])
treeNode
需要被选中的节点
addFlag
是否添加选中到已有的选中数组中(可选,默认false)
isSilent
false表示会让选中的节点滚动到视野中(可选,默认false)
取消选中的节点:zTreeObj.cancelSelectedNode(treeNode)
treeNode
如果省略此参数,则将取消全部被选中节点的选中状态。
树在DOM上是如何组织的?
根节点
- dom元素是ul
- id属性会被作为
treeId
,可以通过setting.treeId
访问到
- class属性用于样式,默认的样式表假设的class为
ztree
子节点:
- dom元素是li
- 有三个子元素:折叠开关(span),图标和标题(a),子树(ul)
新建树的流程是如何的?
初始化zTree的方法:$.fn.zTree.init(obj, zSetting, zNodes)
参数说明:
obj
树DOM容器,比如$("#treeDemo")
zSetting
zTree配置数据
zNodes
zTree节点数据,可以是Array/Object
返回值:zTree对象,对象上有setting对象和有操作树的方法。
init方法代码分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| init: function(obj, zSetting, zNodes) { var setting = tools.clone(_setting); $.extend(true, setting, zSetting);
setting.treeId = obj.attr("id");
setting.treeObj = obj; setting.treeObj.empty();
settings[setting.treeId] = setting;
data.initRoot(setting); var root = data.getRoot(setting),
childKey = setting.data.key.children; zNodes = zNodes ? tools.clone(tools.isArray(zNodes)? zNodes : [zNodes]) : [];
if (setting.data.simpleData.enable) { root[childKey] = data.transformTozTreeFormat(setting, zNodes); } else { root[childKey] = zNodes; }
data.initCache(setting);
event.unbindTree(setting); event.bindTree(setting);
event.unbindEvent(setting); event.bindEvent(setting);
var zTreeTools = { setting : setting, addNodes : function(parentNode, newNodes, isSilent) { } root.treeTools = zTreeTools; data.setZTreeTools(setting, zTreeTools);
if (root[childKey] && root[childKey].length > 0) { view.createNodes(setting, 0, root[childKey]); } else if (setting.async.enable && setting.async.url && setting.async.url !== '') { view.asyncNode(setting); } return zTreeTools; }
|
init中的逻辑只是新建了一个root节点,然后把传入的节点数据都放到这个root节点上。具体新建节点的逻辑在view.createNodes()
这个内部方法中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| view.createNodes: function (setting, level, nodes, parentNode, index) { if (!nodes || nodes.length == 0) return; var root = data.getRoot(setting), openFlag = !parentNode || parentNode.open || !!$$(data.nodeChildren(setting, parentNode)[0], setting).get(0); root.createdNodes = [];
var zTreeHtml = view.appendNodes(setting, level, nodes, parentNode, index, true, openFlag), parentObj, nextObj;
if (!parentNode) { parentObj = setting.treeObj; } else { var ulObj = $$(parentNode, consts.id.UL, setting); if (ulObj.get(0)) { parentObj = ulObj; } } if (parentObj) { if (index >= 0) { nextObj = parentObj.children()[index]; } if (index >= 0 && nextObj) { $(nextObj).before(zTreeHtml.join('')); } else { parentObj.append(zTreeHtml.join('')); } }
view.createNodeCallback(setting); }
|
可以看出关键代码在view.appendNodes()
方法中,这个方法返回了节点的HTML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| view.appendNodes: function (setting, level, nodes, parentNode, index, initFlag, openFlag) { if (!nodes) return []; var html = [];
var tmpPNode = (parentNode) ? parentNode : data.getRoot(setting), tmpPChild = data.nodeChildren(setting, tmpPNode), isFirstNode, isLastNode;
if (!tmpPChild || index >= tmpPChild.length - nodes.length) { index = -1; }
for (var i = 0, l = nodes.length; i < l; i++) { var node = nodes[i]; if (initFlag) { isFirstNode = ((index === 0 || tmpPChild.length == nodes.length) && (i == 0)); isLastNode = (index < 0 && i == (nodes.length - 1)); data.initNode(setting, level, node, parentNode, isFirstNode, isLastNode, openFlag); data.addNodeCache(setting, node); } var isParent = data.nodeIsParent(setting, node);
var childHtml = []; var children = data.nodeChildren(setting, node); if (children && children.length > 0) { childHtml = view.appendNodes(setting, level + 1, children, node, -1, initFlag, openFlag && node.open); }
if (openFlag) { view.makeDOMNodeMainBefore(html, setting, node); view.makeDOMNodeLine(html, setting, node); data.getBeforeA(setting, node, html); view.makeDOMNodeNameBefore(html, setting, node); data.getInnerBeforeA(setting, node, html); view.makeDOMNodeIcon(html, setting, node); data.getInnerAfterA(setting, node, html); view.makeDOMNodeNameAfter(html, setting, node); data.getAfterA(setting, node, html); if (isParent && node.open) { view.makeUlHtml(setting, node, html, childHtml.join('')); } view.makeDOMNodeMainAfter(html, setting, node); data.addCreatedNode(setting, node); } } return html; }
|
zTree的新建View的模式是,一行一行的拼接HTML,然后放到数组中,等于是通过数组来解耦。。
好坏暂时无法评论。
传入的初始设置和数据,zTree是否会持有引用?
根据上文分析$.fn.zTree.init()
,传入的设置和节点数据,zTree都会复制一份。
所以传入后修改这些对象是无意义的。也没必要继续持有这些对象的引用。
参考资料