圖一:用JavaScript實現的樹形控件
HTML本身不支持樹形控件,但我們可以通過一些JavaScript腳本代碼實現。為了提高控件的可重用性,我們要充分運用JavaScript對面向對象編程技術的支持。本文的樹形控件適用于IE 4+和Netscape 6.x,應當說這已經涵蓋了當前的主流瀏覽器。
一、JavaScript與面向對象
面向對象的編程有三個最基本的概念:繼承,封裝,多態性。繼承和封裝這兩個概念比較好理解,相對而言,多態性這個概念就比較難于掌握和運用。一般而言,多態性是指以多種形式表現的能力。在面向對象編程技術中,多態性表示編程語言擁有的一種根據對象的數據類型或類的不同而采取不同處理方式的能力。
在“純”面向對象的語言中,例如Java,多態性一般與類的繼承密不可分。也就是說,必須定義一種類的層次關系,處于頂端的是抽象類,處于下層的是各種具體的實現。抽象類定義了子類必須實現或覆蓋的方法,不同的子類根據自己的需要以不同的方式覆蓋抽象類的方法。
例如,計算圓面積和矩形面積的公式完全不同,按照面向對象的設計思路,我們要先定義一個抽象類Shape,Sharp類有一個findArea()方法,所有從Shape類派生的子類都必須實現findArea()方法。然后,我們定義一個代表矩形的Rectangle類,一個代表圓的Circle類,這兩個類都從Shape類繼承。Rectangle類和Circle類分別實現findArea()方法,兩者用不同的計算公式計算面積。最終達到這樣一個目標:不論對象屬于Shape的哪一種子類(Rectangle或Circle),都可以用相同的方式調用findArea()方法,不用去管被調用的findArea()采用什么公式計算面積,從而有效地隱藏實現細節。
JavaScript語言不支持以類為基礎的繼承,但仍具有支持多態性的能力。JavaScript的繼承是一種基于原型(Prototype)的繼承。實際上,正如本文例子所顯示的,這種繼承方式簡化了多態性方法的編寫,而且從結構上來看,最終得到的程序也與純面向對象語言很接近。
二、準備工作
整個樹形控件由四部分構成:圖形,CSS樣式定義,HTML框架代碼,JavaScript代碼。從圖一可以看出,樹形控件需要三個圖形,分別表示折疊的分支(closed.gif)、展開的分支(open.gif)和葉節點(doc.gif)。
下面是樹形控件要用到的CSS樣式定義:
<style>
body{
font: 10pt 宋體,sans-serif; color: navy; }
.branch{
cursor: pointer;
cursor: hand;
display: block; }
.leaf{
display: none;
margin-left: 16px; }
a{ text-decoration: none; }
a:hover{ text-decoration: underline; }
</style>
CSS規則很簡單:body規則設置了文檔的字體和前景(文字)顏色。branch規則的用途是:當鼠標經過擁有子節點的節點時,指針會變成手的形狀。之所以要定義兩個cursor屬性,是因為IE和Netscape使用不同的屬性名稱。在leaf規則中設置display屬性為none,這是為了實現葉節點(不含子節點的最終節點)的折疊效果。在腳本代碼中,我們通過把display屬性設置成block顯示出節點,設置成none隱藏節點。
三、腳本設計
本文實現的樹形控件包含一個tree對象,tree對象擁有一個branches子對象集合;每一個branch(分支)對象又擁有一個子對象的集合。子對象可以是branch對象,也可以是leaf(樹葉)對象。所有這三種對象分別實現一個多態性的write()方法,不同對象的write()方法根據所屬對象的不同而執行不同的操作,也就是說:tree對象的write()方法與branch對象的write()方法不同,branch對象的write()方法又與leaf對象的write()方法不同。另外,tree和branch對象各有一個add()方法,分別用來向各自所屬的對象添加子對象。
在HTML文檔的部分加入下面這段代碼。這段代碼的作用是創建兩個Image對象,分別對應分支打開、折疊狀態的文件夾圖形。另外還有幾個工具函數,用于打開或折疊任意分支的子元素,同時根據分支的打開或折疊狀態相應地變換文件夾圖形。
<script language="JavaScript">
var openImg = new Image();
openImg.src = "open.gif";
var closedImg = new Image();
closedImg.src = "closed.gif";
function showBranch(branch){
var objBranch = document.getElementById(branch).style;
if (objBranch.display=="block")
objBranch.display="none";
else
objBranch.display="block";
swapFolder('I' + branch);
}
function swapFolder(img){
objImg = document.getElementById(img);
if (objImg.src.indexOf('closed.gif')>-1)
objImg.src = openImg.src;
else
objImg.src = closedImg.src;
}
</script>
代碼預先裝入圖形對象,這有利于提高以后的顯示速度。showBranch()函數首先獲得參數提供的分支的樣式,判斷并切換當前樣式的顯示屬性(在block和none之間來回切換),從而形成分支的擴展和折疊效果。swapImage()函數的原理和showBranch()函數基本相同,它首先判斷當前分支的圖形(打開的文件夾還是折疊的文件夾),然后切換圖形。
四、tree對象
下面是tree對象的構造函數:
function tree(){
this.branches = new Array();
this.add = addBranch;
this.write = writeTree;
}
tree對象代表著整個樹形結構的根。tree()構造函數創建了branches數組,這個數組用來保存所有的子元素。add和write屬性是指向兩個多態性方法的指針,兩個多態性方法的實現如下:
function addBranch(branch){
this.branches[this.branches.length] = branch;
}
function writeTree(){
var treeString = '';
var numBranches = this.branches.length;
for (var i=0;i <numBranches;i++)
treeString += this.branches
.write();
document.write(treeString);
}
addBranch()方法把參數傳入的對象加入到branches數組的末尾。writeTree()方法遍歷保存在branches數組中的每一個對象,調用每一個對象的write()方法。注意這里利用了多態性的優點:不管branches數組的當前元素是什么類型的對象,我們只需按照統一的方式調用write()方法,實際執行的write()方法由branches數組當前元素的類型決定——可能是branch對象的write()方法,也可能是leaf對象的write()方法。
必須說明的是,雖然JavaScript的Array對象允許保存任何類型的數據,但這里我們只能保存實現了write()方法的對象。象Java這樣的純面向對象語言擁有強健的類型檢查機制,能夠自動報告類型錯誤;但JavaScript這方面的限制比較寬松,我們必須手工保證保存到branches數組的對象具有正確的類型。
五、branch對象
branch對象與tree對象相似:
function branch(id, text){
this.id = id;
this.text = text;
this.write = writeBranch;
this.add = addLeaf;
this.leaves = new Array();
}
branch對象的構造函數有id和text兩個參數。id是一個唯一性的標識符,text是顯示在該分支的文件夾之后的文字。leaves數組是該分支對象的子元素的集合。注意branch對象定義了必不可少的write()方法,因此可以保存到tree對象的branches數組。tree對象和branch對象都定義了write()和add()方法,所以這兩個方法都是多態性的。下面是branch對象的add()和write()方法的具體實現:
function addLeaf(leaf){
this.leaves[this.leaves.length] = leaf;
}
function writeBranch(){
var branchString =
'<span class="branch" ' + onClick="showBranch(\'' + this.id + '\')"';
branchString += '><img src="closed.gif" id="I' + this.id + '">' + this.text;
branchString += '</span>';
branchString += '<span class="leaf" id="';
branchString += this.id + '">';
var numLeaves = this.leaves.length;
for (var j=0;j<numLeaves;j++) branchString += this.leaves[j].write();
branchString += '</span>';
return branchString;
}
addLeaf()函數和tree對象的addBranch()函數相似,它把通過參數傳入的對象加入到leaves數組的末尾。
writeBranch()方法首先構造出顯示分支所需的HTML字符串,然后通過循環遍歷leaves數組中的每一個對象,調用數組中每一個對象的write()方法。和branches數組一樣,leaves數組也只能保存帶有write()方法的對象。
六、leaf對象
leaf對象是三個對象之中最簡單的一個:
function leaf(text, link){
this.text = text;
this.link = link;
this.write = writeLeaf;
}
leaf對象的text屬性表示要顯示的文字,link屬性表示鏈接。如前所述,leaf對象也要定義write()方法,它的實現如下:
function writeLeaf(){
var leafString = '<a href="' + this.link + '">';
leafString += '<img src="doc.gif" border="0">';
leafString += this.text;
leafString += '</a><br>';
return leafString;
}
writeLeaf()函數的作用就是構造出顯示當前節點的HTML字符串。leaf對象不需要實現add()方法,因為它是分支的終結點,不包含子元素。
七、裝配樹形控件
最后要做的就是在HTML頁面中裝配樹形控件了。構造過程很簡單:創建一個tree對象,然后向tree對象添加分支節點和葉節點。構造好整個樹形結構之后,調用tree對象的write()方法把樹形控件顯示出來。下面是一個多層的樹形結構,只要把它加入標記內需要顯示樹形控件的位置即可。注意下面例子中凡是應該加入鏈接的地方都以“#”替代:
<script language="JavaScript">
var myTree = new tree();
var branch1 = new branch('branch1','JavaScript參考書');
var leaf1 = new leaf('前言','#');
var leaf2 = new leaf('緒論','#');
branch1.add(leaf1);
branch1.add(leaf2);
myTree.add(branch1);
var branch2 = new branch('branch2','第一章');
branch2.add(new leaf('第一節','#'));
branch2.add(new leaf('第二節','#'));
branch2.add(new leaf('第三節','#'));
branch1.add(branch2);
var branch3 = new branch('branch2','第二章');
branch3.add(new leaf('第一節','#'));
branch3.add(new leaf('第二節','#'));
branch3.add(new leaf('第三節','#'));
branch1.add(branch3);
myTree.add(new leaf('聯系我們','#'));
myTree.write();
</script>
上述代碼的運行效果如圖一所示。可以看到,裝配樹形控件的代碼完全符合面向對象的風格,簡潔高效。
從本質上看,用面向對象技術構造的樹形控件包含一組對象,而且這組對象實現了純面向對象的語言中稱為接口的東西,只不過由于JavaScript語言本身的限制,接口沒有明確定義而已。例如,本文的樹形控件由tree、branch、leaf三個對象構成,而且這三個對象都實現了write接口,也就是說,這三個對象都有一個write()方法,不同對象的write()方法根據對象類型的不同提供不同的功能。又如,tree、branch對象實現了add接口,兩個對象分別根據自身的需要定義了add()方法的不同行為。可見,多態性是面向對象技術中一個重要的概念,它為構造健壯的、可伸縮的、可重用的代碼帶來了方便。
posted on 2006-09-29 09:22
圣域飛俠 閱讀(256)
評論(0) 編輯 收藏