学了点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;
})();