栀子花开

追求完美

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

看到界面程序员用js绘制出各种诱人的界面,实在让人有些心痒痒。前几天忍不住自己写了一个用于展示和操纵树型数据结构的js对象。
目标:
1、高性能,假如树上有成千上万的节点,展示的速度应该不会考验用户的耐心
2、使用方便,允许这个对象的使用者能很方便根据对象接口来展示树型数据、向它增加节点、删除节点、展开和收缩节点、获取当前选中节点以及节点上的依附对象、选择和取消选择节点
3、跨平台,至少能够在IE以及firefox上运行。

为了实现这个目标,我提供了2个js对象:Tree和Node,还有一个超类:SkyObject。代码如下:
function SkyObject(){
var id = 0;
var className = "SkyObject";

this.getId = function(){
return id;
}
this.setId = function(_id){
id = _id;
}
this.setClassName = function(name){
className = name;
}
this.getClassName = function(){
return className
}

addToContainer(this);

this.getElement = function(){
return window.document.getElementById("obj_"+ this.getId());
}
}

var hxdObjPointer = 0;
var hxdContainer = [];

function addToContainer(obj){
obj.setId(hxdObjPointer);
hxdContainer[hxdObjPointer] = obj;
hxdObjPointer++;
}
function getFromContainer(id){
return hxdContainer[id];
}

function stateClick(id){
var node = getFromContainer(id);
node.changeState();
}

function nodeClick(id){
var node = getFromContainer(id);
node.onSelected();
var root = node.getRoot();
var preNodeId = root.getPreNodeId();
var preNode = getFromContainer(preNodeId);
if(preNode!=null)
preNode.onUnSelected();
}

function Node(data,parentNode){
SkyObject.call(this);
var pNode = parentNode;
var value = data;
var state = "closed";
var children = [];
this.setClassName("Node");
var childrenInited = false;
this.getRoot = function(){
var current = this;
var parent = null;
while(true){
try{
parent = current.getParentNode();
}catch(e){
}

if(parent==null || parent=="undefined"){
return current;
}else{
current = parent;
parent = null;
}
}
}

this.getParentNode = function() {
return pNode;
}

this.getValue = function(){
return value;
}



this.onSelected = function(){
var element = window.document.getElementById("namelink_"+this.getId());
element.style.color="blue";
var root = this.getRoot();
root.setCurrentNodeId(this.getId());
root.onNodeChange();
}

this.onUnSelected = function(){
var element = window.document.getElementById("namelink_"+this.getId());
if(element!=null)
element.style.color="black";
if(this.getId()==root.getCurrentNodeId()){
var root = this.getRoot();
root.setCurrentNodeId(-1);
}
}



this.removeChild = function(node){
if(node==null || node=="undefined")
return;
var newChildren = [];
var index = 0;
for(var i=0;i<children.length;i++){
var child = children[i];
if(node.getId()!=child.getId()){
newChildren[index] = child;
index++;
}
}

delete children;
children = newChildren;

var element = window.document.getElementById("children_"+this.getId());
if(element!=null){
if(children.length>0)
element.removeChild(node.getElement());
else
this.getElement().removeChild(element);
}
this.repaint();
}

this.addChild = function(node){
children[children.length] = node;
var element = window.document.getElementById("children_"+this.getId());

if(element==null){
if(value.children!=null && value.children.length>0){
this.paintChildren();
element = window.document.getElementById("children_"+this.getId());
}
element = window.document.createElement("div");
element.id="children_"+this.getId();
this.getElement().appendChild(element);
}
state="opened";
element.style.display = "block";
node.paint(element);
this.repaint();
}

this.changeState = function(){
var stateLink = window.document.getElementById("statelink_"+this.getId());
if(state=="opened"){
stateLink.innerHTML=" + ";
state="closed";
}else{
state="opened";
stateLink.innerHTML=" - ";
}

var childrenElement = window.document.getElementById("children_"+this.getId());
if(childrenElement==null){
if(state=="opened") this.paintChildren();
}else{
if(state=="opened"){
childrenElement.style.display = "block";
}else{
childrenElement.style.display = "none";
}
}
}


this.paintChildren = function(){
childrenInited = true;
var nodeElement = this.getElement();
var childrenElement = null;
if(value.children!=null && value.children.length>0){
childrenElement = window.document.createElement("div");
childrenElement.id="children_"+this.getId();
for(var i=0;i < value.children.length;i++){
var childNode = new Node(value.children[i],this);
children[i]=childNode;
childNode.paint(childrenElement);
}
childrenElement.style.display = "block";
nodeElement.appendChild(childrenElement);
}
}

this.repaint = function(){
var statelink = document.getElementById("statelink_"+this.getId());
var namelink = document.getElementById("namelink_"+this.getId());

if(children!=null && children.length>0){
if(state=="opened"){
statelink.innerHTML=" - ";
}else{
statelink.innerHTML=" + ";
}
statelink.href='javascript:stateClick(' + this.getId() +')';
}else{
statelink.innerHTML=" . ";
}
namelink.innerHTML = value.name;
}

this.paint = function(parent){
var nodeElement = window.document.createElement("div");
nodeElement.style.position = "relative";
nodeElement.id = "obj_"+ this.getId();


var statelink = window.document.createElement("a");
statelink.id = "statelink_"+this.getId();
if(value.children!=null && value.children.length>0){
if(state=="opened"){
statelink.innerHTML=" - ";
}else{
statelink.innerHTML=" + ";
}
statelink.href='javascript:stateClick(' + this.getId() +')';
}else{
statelink.innerHTML=" . ";
}

nodeElement.appendChild(statelink);

var namelink = window.document.createElement("a");
namelink.id = "namelink_" + this.getId();
namelink.href='javascript:nodeClick(' + this.getId() + ')';
namelink.innerHTML = value.name;
nodeElement.appendChild(namelink);

if(state=="opened"){
paintChildren();
}
if(parent.type=="nodesPane")
nodeElement.style.left = 2;
else
nodeElement.style.left = 20;
parent.appendChild(nodeElement);
}

}

function Tree(){

SkyObject.call(this);
var children = [];
var title = "title";
var element = null;
var parent = null;
var value = null;
var currentNodeId = -1;
var preNodeId = -1;

this.setCurrentNodeId = function(id){
preNodeId = currentNodeId;
currentNodeId = id;
}

this.getPreNodeId = function(){
return preNodeId;
}

this.getCurrentNodeId = function(){
return currentNodeId;
}

this.getCurrentNode = function(){
if(currentNodeId<0){
return null;
}else
return getFromContainer(currentNodeId);
}

this.onNodeChange = function(){};
this.bindData = function(data){
value = data;
}

this.addChild = function(data){
var node = new Node(data,this);
children[children.length] = node;
var treeElement = this.getElement();
node.paint(treeElement);
}

this.addChildToSelectedNode = function(data){
var selectednode = this.getCurrentNode();
var node = new Node(data,selectednode);
selectednode.addChild(node);
}

 

this.removeChild = function(node){
var parent = node.getParentNode();
if(parent.getId()==this.getId()){
var element = this.getElement();
element.removeChild(node.getElement());
var newChildren = [];
var index = 0;
for(var i=0;i<children.length;i++){
var child = children[i];
if(node.getId()!=child.getId()){
newChildren[index] = child;
index++;
}
}
delete children;
children = newChildren;

}else{
parent.removeChild(node);
}

}

this.getElement = function(){
var nodesPane = window.document.getElementById("obj_"+ this.getId())
if(nodesPane==null){
nodesPane = window.document.createElement("div");
nodesPane.id = "obj_"+this.getId();
nodesPane.type = "nodesPane";
if(value!=null && value.length>0){
for(var i=0;i<value.length;i++){
var node = new Node(value[i],this);
children[i] = node;
node.paint(nodesPane);
}
}
}
return nodesPane;
}

this.paint = function(parent){
var nodesPane = window.document.createElement("div");
nodesPane.id = "obj_"+this.getId();
if(value!=null && value.length>0){
for(var i=0;i<value.length;i++){
var node = new Node(value[i],this);
children[i] = node;
node.paint(nodesPane);
}
}
parent.appendChild(nodesPane);
}
}

源代码的确有点长,幸好,对开发者来说,他们不必要了解其中的细节。他们只需要知道有来写方法可用就行了。下面的代码是操纵这个组件的示例:
<script type="text/javascript" src="SigmaTree.js"></script>
<script language="javascript">
var tree1 = null;
var tree2 = null;
window.onload = function(){
var roots = {tree:[
{id:"1",name:"name1",children:
[

{id:"10",name:"child0",children:[]},
{id:"11",name:"child1",children:[]},
{id:"12",name:"child2",children:[]},
{id:"13",name:"child3",children:[]},
{id:"14",name:"child4",children:[]}
]
},

{id:"2",name:"name2",children:[]},
{id:"3",name:"name3",children:[]}
{id:"4",name:"name4",children:[]},
]};
var parent1 = document.getElementById("tree1");
tree1 = new Tree();
tree1.bindData(roots.tree);
tree1.paint(parent1);
tree1.onNodeChange = function(){

}
var parent2 = document.getElementById("tree2");

tree2 = new Tree();

tree2.bindData(roots.tree);
tree2.paint(parent2);

tree2.onNodeChange = function(){

}
}

function addNew(){
var nodedata = {id:"123",name:"newNode123"};
tree1.addChild(nodedata);
}

function addNewToSelected(){
var nodedata = {id:"123",name:"newNode123"};
tree1.addChildToSelectedNode(nodedata);
}

function removeSelectedNode(){
var node = tree1.getCurrentNode();
tree1.removeChild(node);
}
</script>


效果:
1、出色的性能:假如树上节点分布比较均匀的话,该组件可以轻松在1秒内载入上万个节点。所谓分布均匀,指的是每个节点的子节点的数量大致相等,比如,顶层节点有100个,每个节点又有100个子节点,那么总共有10000个节点。由于该组件只绘制需要展示的节点,因此刚启动的时候仅仅绘制了100个顶层节点,所以具有很高的性能。
2、良好的用户体验:页面可以在不刷新的情况下操纵这棵树,比如:删除节点、增加节点、展开和收缩节点。
3、良好的编程体验:程序员可以通过bindData() 来绑定一棵js对象组成的树,并在一个指定的div或者别的什么元素中绘制出这棵树,向树增加和删除节点是非常方便的。编程的便利和普通c/s ide提供的控件相比不遑多让。如果需要把对树的操纵持久化到服务器上,程序员可以通过xmlhttp来实现。

posted on 2007-10-23 16:11  杨林  阅读(358)  评论(0编辑  收藏  举报