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