1. 程式人生 > >YUI Tree入門(二)各類Node詳細分析

YUI Tree入門(二)各類Node詳細分析

之前簡單介紹過TreeView的使用,現在來仔細分析一下YUI TreeView的結構,Tree相關的類如下:
[img]http://meladet.iteye.com/upload/picture/pic/40590/683be4ff-05f3-3013-b243-865800184465.png[/img]
我們分成3部分看,第一部分是前面幾個以Node結尾的類,這些類代表是樹上的節點,其中最頂層的是Node,這個類是其他幾種Node的父類。Node中有節點所具有的所有屬性和方法,比如children,parent,depth,expand(),collapse()等等。
第二部分是TreeView這個類,這個類就像是一棵樹的骨架,他有根有樹幹有樹枝,可以把上面介紹的Node節點掛在Treeview上,Treeview會統一管理記錄這些節點。通過TreeView你可以很方便的定位到一個或一些節點,比如 getNodeByElement(),getNodeByIndex,
getNodeByProperty(),getNodesByProperty()。通過TreeView可以控制一棵樹是展開還是合攏,可以移除一些節點等等。TreeView就是在全域性上控制樹上的Nodes。
第三部分是最後三個類,這三個類主要是個樹增加一些動畫的效果。

既然TreeView是整棵樹的總樞紐,我們就先從TreeView入手[b](由於TreeView的 原始碼太長,我就不直接貼出來,大家可以對照著原始碼看,碰到比較重要的程式碼我會把它們貼出來)[/b]。
首先是TreeView的建構函式:[b]YAHOO.widget.TreeView ( id )[/b],構造方法只有一個引數,就是HTML中的Div元素的ID,這就像是給樹挖個坑中下去,以後樹在頁面上顯示就有地方了。構造方法裡面呼叫了TreeView的Init方法,這個方法我們稍後再說。
TreeView裡面有不少屬性,不過大多都不用太深究,無非是用來與HTML元素做繫結,記錄TreeView的狀態等等,這些屬性我們只要構建好了樹,就由樹內部做管理,一般情況下我們都不會使用到。下面挑幾個重要的屬性做說明:
[b]YAHOO.widget.TreeView.nodeCount[/b],這是一個靜態屬性,這個屬性記錄TreeView上面Node的個數,也就是每次生成一個Node到TreeView上,這個屬性值就增加1,這個屬性有一個作用就是給Node分配一個唯一的標識,每個Node都有一個屬性叫做index,這個index就是由這個屬性來分配的。由於這個屬性值只增不減,所以永遠不會出現重複的。
[b]YAHOO.widget.TreeView.trees[/b],這是一個私有屬性,它是一個關聯陣列,用來快取所有的TreeView例項,每次生成一個TreeView的例項就會往trees裡面設定一個key(TreeView的Id)和value(TreeView的例項)。這樣方便以後通過Id可以直接取到TreeView的例項。TreeView有一個方法:YAHOO.widget.TreeView.getTree(treeId),看看它的實現:

YAHOO.widget.TreeView.getTree = function(treeId) {
var t = YAHOO.widget.TreeView.trees[treeId];//通過trees屬性取值
return (t) ? t : null;
};

[b]YAHOO.widget.TreeView.counter[/b],這個也是私有屬性,用來給TreeView生成一個唯一的ID,

generateId: function(el) {
var id = el.id;
if (!id) {
id = "yui-tv-auto-id-" + YAHOO.widget.TreeView.counter;
++YAHOO.widget.TreeView.counter;
}
return id;
},

這個屬性的值同樣是遞增的,generateId方法生成的Id,在TreeView的init方法中就直接賦值給Treeview例項。
說到TreeView的init方法,我們就仔細看看它做了什麼。

init: function(id) {
this.id = id;
if ("string" !== typeof id) {
this._el = id;
this.id = this.generateId(id);//通過YAHOO.widget.TreeView.counter生成
}
this.createEvent("animStart", this);
this.createEvent("animComplete", this);
this.createEvent("collapse", this);
this.createEvent("collapseComplete", this);
this.createEvent("expand", this);
this.createEvent("expandComplete", this);
this._nodes = [];
// store a global reference
YAHOO.widget.TreeView.trees[this.id] = this;
// Set up the root node
this.root = new YAHOO.widget.RootNode(this);
var LW = YAHOO.widget.LogWriter;
this.logger = (LW) ? new LW(this.toString()) : YAHOO;
this.logger.log("tree init: " + this.id);
},

Init方法做的事情很簡單,給TreeView分配一個Id,建立一些事件,把TreeView例項加入到[b]YAHOO.widget.TreeView.trees[/b]陣列中,生成一個RootNode。還記得我們往樹上加第一個節點的時候,父節點就是用的這個RootNode,通過treeView.getRoot()這個方法可以取得。
在上次的那個例子中,最後呼叫了treeView.draw()來顯示樹,現在我們看看這個方法的實現:

draw: function() {
var html = this.root.getHtml();
this.getEl().innerHTML = html;
this.firstDraw = false;
},

this.root.getHtml()這個方法通過RootNode的getHtml得到節點的Html表示,this.getEl(),取得TreeView對應Div的物件,然後設定innerHTML。
TreeView類的介紹就到這,TreeView還有很多控制相關的方法,比如expand,collpase(),removeChildren(),getNodeByIndex()等等,這些方法實現都比較簡單,而且不知道他們的實現對使用也沒有什麼影響,所以就不對這些方法做具體的介紹,有興趣可以自己檢視原始碼。
下面要說的是幾個以Node結尾的類,先從頂層的[b]YAHOO.widget.Node[/b]開始。

YAHOO.widget.Node = function(oData, oParent, expanded) {
if (oData) { this.init(oData, oParent, expanded); }
};
……….
init: function(oData, oParent, expanded) {
this.data = oData;
this.children = [];
this.index = YAHOO.widget.TreeView.nodeCount;
++YAHOO.widget.TreeView.nodeCount;
this.expanded = expanded;
this.logger = new YAHOO.widget.LogWriter(this.toString());
this.createEvent("parentChange", this);
if (oParent) {
oParent.appendChild(this);
}
},
………


上面這個是建構函式和Init方法,生成一個Node需要三個引數,第一個oData,這個屬性可以是任意型別的,可以是Object,這一點很重要,以後擴充套件樹的時候可以傳入你定義好的資料。第二個屬性是oParent,就是你構造這個節點的父親,值可以是樹上的其他節點,第三個屬性expanded,有true和false兩個值,true的時候預設節點展開,false的時候不展開。
Init方法做一些初始化工作,this.index = YAHOO.widget.TreeView.nodeCount;這個是給每個Node一個唯一Id,之前介紹TreeView類的時候有說到YAHOO.widget.TreeView.nodeCount這個屬性。This.children用來儲存它的所有子節點,最後oParent.appendChild(this);把新生成的Node加入到oParent的children陣列中。
下面把構造的過程畫一張圖 (圖中的橢圓為屬性):
[img]http://meladet.iteye.com/upload/picture/pic/40606/e3128ba6-a06b-3d47-a465-c4b1be046d2b.png[/img]
我把餘下的方法進行了下簡單的分類便於大家理解掌握
[b]/*插入節點的方法*/[/b]
appendTo(parentNode) {
return parentNode.appendChild(this);
}//很常用的插入節點的方法 但被插入到了引數節點子節點的最後一個
insertBefore: function(node)
如果這個節點在一個樹裡則先從這棵樹裡把該節點刪除(注意:appendTo可沒有刪)
然後獲取引數Node父節點的孩子列表以及該引數Node的位置以此操作該孩子列表在引數節點的位置前面新增
實現新增。然後重定向該節點和引數Node的前後sibling屬性
最後一步 呼叫applyParent()方法 重設depth同時也把自己的子節點也引過來了
insertAfter: function(node)
機理同上 只在第二步操作的位置不一樣罷了

[b]/*幾個獲取ID的方法*/[/b]
getElId() 返回這個節點容器div的id id結構:"ygtv" + this.index
getChildrenElId() 返回節點的Children節點DIV的id 注意是所有子節點在一個DIV裡 id結構 "ygtvc" + this.index;
getToggleElId() 返回節點的toggle的id id結構:"ygtvt"+ this.index toggle區域就是 樹種節點的左邊的+ -號的區域
*這些個方法都對應有通過id獲取到物件的方法 getEl() getChildrenEl() getToggleEl()

[b]/*節點的收起展開*/[/b](可以新增一些自定義的事件)
collapse() 隱藏起子節點(如果有必要的話可建立子節點)
核心是:一堆判斷之後 呼叫 this.hideChildren()方法 此方法即獲取孩子區域的物件將其display屬性設為none
當然了,也呼叫了this.updateIcon()方法 此方法用於更新了左邊的+ -顯示
expand(boolean) 顯示子節點 核心:也是一堆的判斷 之後呼叫this.showChildren() 此方法即獲取孩子區域的物件將其display屬性設為""
expandAll() 和 collapseAll() 很顯然 這兩個方法是同時展開或收起所有節點以下的節點

[b]/*這個跟動態載入相關*/[/b]
setDynamicLoad(fnDataLoader, iconMode) 設定第一次展開節點時動態載入子節點資料的方法 獲取方法通過引數fnDataLoader傳遞給節點的dataLoader屬性 fnDataLoader方法的引數 第一個是Node節點型別 第二個是回撥函式 如果不設定回撥函式的話 在第一次載入之後節點 會關閉動態生成(即設dynamicLoadComplete為true )因為你不指定的話 會呼叫預設的
setDynamicLoad()第二個引數可選 int型 會賦給iconMode屬性(具體用途見前面的屬性解析)

[b]/*這個很重要跟自定義節點有關*/[/b]
getNodeHtml() 這個方法被設計用來被重寫的 本身返回空字串 以便支援不同種的Node. 方法會在生成節點的時候把取得的html寫入節點,從而產生了各種不同的節點樣式
getHtml() 這個方法= getNodeHtml()+getChildrenHtml()
getChildrenHtml() 在構建樹的時候會被呼叫 我們總是先構建裝子節點的div 但並不提供進去資訊 除非這個節點被展開
refresh() 將節點getHtml()獲得的html寫入到子節點的區域裡

getStyle() 獲取節點應有的左邊+ -號狀態 updateIcon()方法會呼叫
isRoot() 返回節點是否為根節點
isChildOf(parentNode) 獲取該節點在父節點的孩子節點列表中的位置 insertBefore/insertAfter方法第二步呼叫了該方法我自己測試的從0開始。
getSiblings() 返回所有的兄弟節點 包括節點自己
isDynamic() 返回節點的子節點是否應該是被動態生成的
getAncestor: function(depth) 返回節點某一深度的祖先

YAHOO.widget.Node就介紹到這,接下來看看繼承自YAHOO.widget.Node的其他Node。[b]YAHOO.widget.RootNode[/b]的原始碼如下:

YAHOO.widget.RootNode = function(oTree) {
this.init(null, null, true);
this.tree = oTree;
};
YAHOO.extend(YAHOO.widget.RootNode, YAHOO.widget.Node, {

// overrides YAHOO.widget.Node
getNodeHtml: function() {
return "";
},

toString: function() {
return "RootNode";
},

loadComplete: function() {
this.tree.draw();
},

collapse: function() {},
expand: function() {}

});

很簡單的實現,從建構函式來看,就是給RootNode指定一個tree屬性。其Init方法都基本傳入的是null,呼叫YAHOO.widget.Node的Init方法。getNodeHtml返回””,就是說RootNode不能顯示出來,是一個隱藏的節點。其他幾個方法的實現都很簡單,不用多說。
[b]YAHOO.widget.HTMLNode[/b]繼承自YAHOO.widget.Node,看原始碼可以發現HTMLNode多加了三個屬性,建構函式也加了一個屬性,叫hasIcon,這個屬性是用來控制是否要顯示樹前面的那些加號、減號以及虛線等圖示的。

YAHOO.widget.HTMLNode = function(oData, oParent, expanded, hasIcon) {
if (oData) {
this.init(oData, oParent, expanded);
this.initContent(oData, hasIcon);
}
};

YAHOO.extend(YAHOO.widget.HTMLNode, YAHOO.widget.Node, {
contentStyle: "ygtvhtml",
contentElId: null,
html: null,

增加的三個屬性分別是:
contentStyle: "ygtvhtml",設定顯示的CSS樣式
contentElId: null,給Content Element設定一個ID
html: null,用來顯示的Html內容
它重寫的方法有toString(),getNodeHtml(),getNodeHtml()是節點顯示的主要方法。
它增加的方法有initContent: function(oData, hasIcon),setHtml: function(o),這些方法的實現都很簡單,不用詳細說明。
[b]YAHOO.widget.TextNode[/b]是比較常用的一種Node,它根據你傳入的oData來構造顯示,如果傳入的oData是String,則把String當作Label顯示出來,如果傳入的是Object,則根據Object的label屬性來顯示,同時還可以給Object設定style,title等屬性,可以很靈活的控制節點要顯示成什麼樣,要顯示哪些資訊。
具體的顯示它重寫了getNodeHtml()方法。新加了onLabelClick方法,在點選樹節點的Label的時候觸發。
最後是[b]YAHOO.widget.MenuNode[/b] ,它繼承自YAHOO.widget.TextNode,只從寫了toString()方法。

通過分析上面幾個繼承自YAHOO.widget.Node的Node,可以發現實現一個Node很簡單,在init方法裡面加入一些新的操作,加入一些新的屬性,最後重寫一些方法,就能得出符合自己要求的Node。YUI在Node的設計上做得是相當不錯的,給了我們很方便的方法來擴充套件已有的Node。下一次我將會詳細的講解如何構建滿足自己需求的Node。