javascript 树形控件
学了点Ext,确实很酷很强大,但学习起来有点复杂,大部分功能都要依赖CSS,不使用Ext的Resources就用不了。于是能不能自己写一个不用CSS虽然不好看但也能用的控件。下面就是树形控件Tree的测试和实现(使用了prototype)。
HTML测试页。有两个按钮,一个是生成两棵树,另一个是显示当前选择的节点的标签(显示在树上的字符串)。树的数据和树的HTML是分开的,数据是具有树状结构的对象,这里使用的是这种结构:{label: '...', children:[childtree, childtree, ...]}。数据是随机生成的,包括标签、树的深度、子树的数量都是在一定范围内随机生成的。
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Tree 2</title> <style type="text/css"> .selected { font-weight: bolder; } </style> <script type="text/javascript" src="js/prototype.js"></script> <script type="text/javascript" src="js/tree2.js"></script> <script type="text/javascript"> var trees = new Array(2); Event.observe(window, 'load', function() { // 初始化两个按钮事件 $('btnTree').observe('click', function() { trees[0] = showTree($('dJSON'), $('dTree')); trees[1] = showTree($('dJSON2'), $('dTree2')); }); $('btnShow').observe('click', function(e) { var next = e.element().next(); var content = trees[0].selectedNode ? trees[0].selectedNode.innerHTML : ''; next.update(content); }); }); //生成并显示树。divJSON:显示JSON格式的数据;divTree:树所在的DIV。 function showTree(divJSON, divTree) { divTree.update(''); var data = randomData(3); divJSON.update(Object.toJSON(data)); var tree = new Tree(divTree, {data: data, onNodeClick: onnc, selectedClass: 'selected'}); tree.load(); return tree; function onnc(e) { var content = e.element().innerHTML; $('btnTree').next().update(content); } } //随机生成数据。level:树的深度。 function randomData(level) { var CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789'; var CHARS_LENGTH = CHARS.length; return createData(level); // 真正干活的函数。 function createData(level) { var o = {}; o.label = randomString(10); if (level > 0) { var seed = randomInt(0, 10); if (seed > 2) { o.children = new Array(randomInt(2, 4)); for (var i = 0, n = o.children.length; i < n; ++i) { o.children[i] = createData(level - 1); } } } return o; } // 随机整数,[bottom, top) function randomInt(bottom, top) { return Math.floor(Math.random() * (top - bottom)) + bottom; } // 使用CHARS生成随机字符串。length:字符串长度,返回的字符串一定是这个长度。用数组join的方法实现,高效简洁。 function randomString(length) { var a = new Array(length); for (var i = 0; i < length; ++i) { a[i] = CHARS.charAt(randomInt(0, CHARS_LENGTH)); } return a.join(''); } } </script> </head> <body> <div id="dJSON"></div><div id="dTree"></div> <div id="dJSON2"></div><div id="dTree2"></div> <div><input id="btnTree" type="button" value="tree" /><span></span></div> <div><input id="btnShow" type="button" value="show"/><span></span></div> </body> </html>
树形控件。初始化使用两个参数:一是控件所在的父标签。二是配置对象,配置对象中必须设置的是data,如果数据对象有children或label属性,也不用设置getChildren和getLabel函数,getChildren与getLabel这两个函数以数据对象为参数,分别返回节点的子树数组和节点的标签字符串;树可用按钮折叠,disableCollapse属性可以取消折叠的功能;selectedClass是设置当前选择的节点的CSS Class,这里不设置在外观上就看不出来区别,这里还是使用CSS最为合理;onNodeClick为点击节点的事件函数,这里可以设计得更好一些能处理多种和多个事件。树的HTML结构使用UL和LI实现,在逻辑上LI是一棵树,树的子节点放在LI下的UL中。LI的子节点最多有三个:第一个是折叠按钮;第二个是标签;第三个是UL,若没有子树则省略UL。
(function() { var Tree = Class.create({ initialize: function(element, config) { this.element = element; this.data = config.data; this.disableCollapse = config.disableCollapse; this.selectedClass = config.selectedClass; this.getChildren = config.getChildren ? config.getChildren : function(o) { return Object.isArray(o.children) ? o.children : []; }; this.getLabel = config.getLabel ? config.getLabel : function(o) { return Object.isUndefined(o.label) ? 'unknown' : o.label; }; this.onNodeClick = config.onNodeClick ? config.onNodeClick : function(){}; this.root = new Element('ul'); this.element.appendChild(this.root); }, // 载入数据,创建树。分离此步骤为更灵活地把握创建的时刻。 load: function() { if (!this.data) return; var _createNode = this._createNode; var _setNode = this._setNode; var getLabel = this.getLabel; var root_li = this._traverseData(this.data, 0, function(data, level, nodes) { var li = _createNode(); _setNode(li, level, '[-]', getLabel(data)); var length = nodes.length; if (length > 0) { var ul = new Element('ul'); for (var i = 0; i < length; ++i) { ul.appendChild(nodes[i]); } li.appendChild(ul); } return li; }); this.root.update(''); this.root.appendChild(root_li); if (!this.disableCollapse) { var me = this; this._traverseNode(root_li, 0, function(children, results) { var anchor = children[0]; anchor.observe('click', collapse); var label = children[1]; label.observe('click', me.onNodeClick); label.observe('click', function(e) { if (me.selectedClass) { if (me.selectedNode) me.selectedNode.removeClassName(me.selectedClass); me.selectedNode = e.element(); me.selectedNode.addClassName(me.selectedClass); } }); }); } function collapse(e) { var a = e.element(); var next = a.next(1); if (next) { a.update(a.innerHTML == '[-]' ? '[+]' : '[-]'); next.toggle(); } } }, // 创建节点,虽然这里封装了,但其他地方深入了节点的内部结构,这块没有做好设计。 _createNode: function() { var li = new Element('li'); li.appendChild(new Element('a', {href: 'javascript:void(0)'})); li.appendChild(new Element('span')); return li; }, // 设置节点的属性(数据) _setNode: function(node, level, button, label) { var children = node.childElements(); if (!Object.isUndefined(button)) children[0].update(button); if (!Object.isUndefined(label)) children[1].update(label); }, // 后序遍历数据,适合某一个数据及其子树数据的综合计算。data:数据对象;level:树深;callback:回调函数,格式如下 // callback(data, level, results):返回回调的结果,通常是这个节点及其子树的综合值;data:数据对象;level:树深;results:子树的计算结果数组。 _traverseData: function(data, level, callback) { if (!data) return; var children = this.getChildren(data); var n = children.length; var results = new Array(n); for (var i = 0; i < n; ++i) { results[i] = this._traverseData(children[i], level + 1, callback); } return callback(data, level, results); }, // 后序遍历结构,适合某一个节点及其子树节点的综合计算。data:数据对象;level:树深;callback:回调函数,格式如下 // callback(children, results):返回回调的结果,通常是这个节点及其子树的综合值;results:数据对象;results:子树的计算结果数组。(这里忘了level……) _traverseNode: function(linode, level, callback) { var children = linode.childElements(); var length = children.length; var results; if (length > 2) { var ul = children[2]; var lis = ul.childElements(); var n = lis.length; results = new Array(n); for (var i = 0; i < n; ++i) { results[i] = this._traverseNode(lis[i], level + 1, callback); } } else { results = []; } return callback(children, results); } }); window.Tree = Tree; })();