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