树形组件是一种具备层级结构的组件,普遍应用于各类场景。本章会实现一个简单的树形组件,尽管功能有限,但你能够经过扩展它来实现本身所须要的树形组件。css
<img src="http://xmlplus.cn/img/tree.png" class="img-responsive"/>json
树形组件的数据源能够是 JSON 格式的数据对象,也能够是具备 XML 结构的数据或者是其它的具备层级结构的数据。本章将采用具备以下 JSON 格式的数据对象。数组
// 09-01 { name: 'My Tree', children: [ { name: 'hello' }, { name: 'world' }, { name: 'child folder', children: [{ name: 'alice' }]} ] };
上述数据源中,name 值会做为树结点的名称显示,含 children 的数组表明节点的子级。ide
由 HTML 中的列表元素 ul 以及 li 组合而成对象具备自然的树形结构,咱们不妨采用它们做为构建树形组件的基本元素。树形组件的最外层必然是一个 ul 元素,因此咱们能够暂时定义树形组件以下:函数
// 09-01 Tree: { xml: `<ul id='tree'> <Item id='item'/> </ul>` }
这里的未定义的组件 Item 是一个须要递归定义的子项组件,它会根据提供的数据递归地生成子孙对象。下面是一种可能的设计:测试
// 09-01 Item: { xml: `<li id='item'> <div id='content'> <span id='neme'/><span id='expand'/> </div> <ul id='children'/> </li>`, map: { defer: "children" } }
注意,上面的 neme 对象是用于显示 name 属性的。expand 对象用于展开或者关闭子级对象 entries。子级对象 children 被设置为须要延迟实例化,只有当用户点击 expand 对象展开子级时,该对象才会实例化。spa
如上一节所述,咱们设定了子级对象 children 须要延迟实例化。因此,在给子项 Item 提供的数据设置接口不该该立马对 children 实例化。下面咱们先给出数据接口函数。设计
// 09-01 Item: { // css, xml, map 项同上 fun: function (sys, items, opts) { var data; return function (value) { data = value; sys.neme.text(data.name); data.children && data.children.length && sys.expand.show().text(" [+]"); }; } }
该接口函数只是设置了当前节点相关的内容。下面咱们来侦听 expand 对象的点击事件,并适时地完成组件对象 children 的实例化。代理
// 09-01 Item: { // css, xml, map 项同上 fun: function (sys, items, opts) { var data, open; sys.expand.on("click", function () { open = !open; sys.expand.text(open ? " [-]" : " [+]"); open ? (sys.children.show() && load()) : sys.children.hide(); }); function load() { if ( sys.children.children().length == 0 ) for ( var item of data.children ) sys.add.before("Item").value()(item); } return function (value) { data = value; sys.neme.text(data.name); data.children && data.children.length && sys.expand.show().text(" [+]"); }; } }
上述代码中包含一个 open 参数,该参数记录了当前节点的是否处于展开状态以供相关的侦听器使用。code
如今咱们对上述组件进行一个小的扩展,使得它支持动态添加树节点的功能。首先,咱们在对象 children 的子级添加一个触发按钮,并命名为 add。
// 09-01 Item: { xml: `<li id='item'> <div id='content'> <span id='neme'/><span id='expand'/> </div> <ul id='children'> <li id='add'>+</li> </ul> </li>`, map: { defer: "children" } }
其次,须要侦听 add 对象的点击事件,在侦听器中完成对象的添加。
// 09-01 Item: { // css, xml, map 项同前 fun: function (sys, items, opts) { var data, open; sys.item.on("click", "//*[@id='add']", function () { var stuff = {name: 'new stuff'}; data.children.push(stuff); sys.add.before("Item").value()(stuff); }); // 其他代码同前 } }
这里须要注意,对 add 对象的侦听不能采起直接式的侦听:sys.add.on("click",...)
,而应该使用代理的方式,不然会报错。由于其父级属于延迟实例化的组件,在 children 对象未实例化之间,add 对象并不可见。
综合以上的内容,如今给出一个完整版本的树形组件,下面先给出的是树组件的子项组件:
// 09-01 Item: { css: "#item { cursor: pointer; }", xml: `<li id='item'> <div id='content'> <span id='neme'/><span id='expand'/> </div> <ul id='children'> <li id='add'>+</li> </ul> </li>`, map: { defer: "children" }, fun: function (sys, items, opts) { var data, open; sys.item.on("click", "//*[@id='add']", function () { var stuff = {name: 'new stuff'}; data.children.push(stuff); sys.add.before("Item").value()(stuff); }); sys.expand.on("click", function () { open = !open; sys.expand.text(open ? " [-]" : " [+]"); open ? (sys.children.show() && load()) : sys.children.hide(); }); function load() { if ( sys.children.children().length == 1 ) for ( var item of data.children ) sys.add.before("Item").value()(item); } return function (value) { data = value; sys.neme.text(data.name); data.children && data.children.length && sys.expand.show().text(" [+]"); }; } }
其次给出树组件。在实际应用中的树形组件会比这里的功能更丰富些,你能够在上述代码的基础上进一步的改进,好比添加些 ICON 图标、让子项成为可拖动的等等。但在改进过程当中尽可能避免代码的复杂化,适当地剥离些代码出来封装成组件是很是有必要的。
// 09-01 Tree: { css: `#tree { font-family: Menlo, Consolas, monospace; color: #444; } #tree, #tree ul { padding-left: 1em; line-height: 1.5em; list-style-type: dot; }`, xml: `<ul id='tree'> <Item id='item'/> </ul>`, fun: function (sys, items, opts) { return items.item; } }
咱们最后给出一个测试用例。该例子的测试数据与本章开始给出的数据一致。
Index: { xml: "<Tree id='tree' xmlns='tree'/>", fun: function (sys, items, opts) { items.tree({ name: 'My Tree', children: [ { name: 'hello' }, { name: 'world' }, { name: 'child folder', children: [{ name: 'alice' }]} ] }); } }