看到界面程序員用js繪制出各種誘人的界面,實在讓人有些心癢癢。前幾天忍不住自己寫了一個用于展示和操縱樹型數(shù)據(jù)結(jié)構(gòu)的js對象。 目標: 1、高性能,假如樹上有成千上萬的節(jié)點,展示的速度應(yīng)該不會考驗用戶的耐心 2、使用方便,允許這個對象的使用者能很方便根據(jù)對象接口來展示樹型數(shù)據(jù)、向它增加節(jié)點、刪除節(jié)點、展開和收縮節(jié)點、獲取當前選中節(jié)點以及節(jié)點上的依附對象、選擇和取消選擇節(jié)點 3、跨平臺,至少能夠在IE以及firefox上運行。 為了實現(xiàn)這個目標,我提供了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); } } 源代碼的確有點長,幸好,對開發(fā)者來說,他們不必要了解其中的細節(jié)。他們只需要知道有來寫方法可用就行了。下面的代碼是操縱這個組件的示例: <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、出色的性能:假如樹上節(jié)點分布比較均勻的話,該組件可以輕松在1秒內(nèi)載入上萬個節(jié)點。所謂分布均勻,指的是每個節(jié)點的子節(jié)點的數(shù)量大致相等,比如,頂層節(jié)點有100個,每個節(jié)點又有100個子節(jié)點,那么總共有10000個節(jié)點。由于該組件只繪制需要展示的節(jié)點,因此剛啟動的時候僅僅繪制了100個頂層節(jié)點,所以具有很高的性能。 2、良好的用戶體驗:頁面可以在不刷新的情況下操縱這棵樹,比如:刪除節(jié)點、增加節(jié)點、展開和收縮節(jié)點。 3、良好的編程體驗:程序員可以通過bindData() 來綁定一棵js對象組成的樹,并在一個指定的div或者別的什么元素中繪制出這棵樹,向樹增加和刪除節(jié)點是非常方便的。編程的便利和普通c/s ide提供的控件相比不遑多讓。如果需要把對樹的操縱持久化到服務(wù)器上,程序員可以通過xmlhttp來實現(xiàn)。 |
|