基于Web標(biāo)準(zhǔn)的UI組件 — 樹狀菜單(2)
getElementsByClassName()
為了從一大堆HTML代碼中找出我們的樹狀菜單(也許有多個(gè)),我們先來實(shí)現(xiàn)一個(gè)通過className
找DOM節(jié)點(diǎn)的方法:getElementsByClassName
。這是對(duì)瀏覽器自有DOM方法的一個(gè)簡單但實(shí)用的擴(kuò)充。
此方法有兩個(gè)參數(shù):ele
指出以哪個(gè)DOM節(jié)點(diǎn)為根節(jié)點(diǎn)尋找(也就是說只找ele
的子節(jié)點(diǎn)),className
指出符合條件的節(jié)點(diǎn)的class屬性中必須包含怎樣的className
。它的返回值是一個(gè)數(shù)組,存放了所有符合條件的節(jié)點(diǎn)。
function getElementsByClassName(ele,className) {
//獲取所有子節(jié)點(diǎn)
if(document.all){
var children = ele.all;
}else{
var children = ele.getElementsByTagName('*');
}
//遍歷子節(jié)點(diǎn)并檢查className屬性
var elements = new Array();
for (var i = 0; i < children.length; i++) {
var child = children[i];
var classNames = child.className.split(' ');
for (var j = 0; j < classNames.length; j++) {
if (classNames[j] == className) {
elements[elements.length] = child;
break;
}
}
}
return elements;
}
最前面的一個(gè)if-else語是為了兼容IE5(IE5不能運(yùn)行document.getElementsByTagName('*')
)。需要注意的是千萬不要用瀏覽器檢測(cè)的方法來寫腳本,而應(yīng)該直接使用將要用到的語句來測(cè)試是否可以執(zhí)行,如果返回值為null
或undefined
,那再換一種方法。這樣的腳本可以有更好的兼容性,也更健壯。
elements[elements.length] = child;
,這句同樣是為了兼容IE5才沒有使用數(shù)組的push
方法。如果你一定要使用push
方法,那么可以在執(zhí)行getElementsByClassName()
之前先重載一遍push
方法。代碼如下:
Array.prototype.push = function(value){
this[this.length] = value;
}
注:原本我希望getElementsByClassName
也能像push
方法一樣寫,比如HTMLElement.prototype.getElementsByClassName = ...
。不過實(shí)際操作的時(shí)候發(fā)現(xiàn)在運(yùn)行時(shí)HTMLElement
這個(gè)對(duì)象并不是固定的,每種tag似乎都不一樣,只能作罷。如果你有解決方案請(qǐng)告訴我,謝謝。
現(xiàn)在我們就可以方便地找出頁面上所有的樹狀菜單了:
var trees = getElementsByClassName(document,'TreeView');
for(var i=0;i<trees.length;i++){
alert('我是第' + i + '個(gè)樹狀菜單');
//在這里可以更細(xì)致地處理每一個(gè)樹狀菜單
}
最后把上面這幾句加到window.onload
事件中運(yùn)行,以便文檔一加載完就對(duì)樹狀菜單進(jìn)行初始化。完整的代碼請(qǐng)查看下面例子的源代碼。
區(qū)分樹枝與樹葉
在上一篇中我們說到樹枝和樹葉的區(qū)別就是這個(gè)節(jié)點(diǎn)有沒有子節(jié)點(diǎn),所以判斷樹枝和樹葉的方法也可以從這個(gè)角度來考慮。一個(gè)比較直觀的方法就是遍歷整個(gè)樹狀菜單的DOM樹(注意這里兩個(gè)“樹”的區(qū)別),看看每個(gè)節(jié)點(diǎn)是不是擁有子節(jié)點(diǎn),如果有的話我們就給這個(gè)節(jié)點(diǎn)一個(gè)專門的class以示區(qū)分。我們這里用一種比較取巧的方法,就是判斷各個(gè)節(jié)點(diǎn)的innerHTML中有沒有出現(xiàn)字符串'<ul>'
。如果有的話,那么很顯然它擁有一個(gè)或多個(gè)子節(jié)點(diǎn)。
var trees = getElementsByClassName(document,'TreeView');
for(var i=0;i<trees.length;i++){
//先把所有的節(jié)點(diǎn)找出來
var nodes = trees[i].getElementsByTagName('LI');
//判斷各個(gè)節(jié)點(diǎn)是否有子節(jié)點(diǎn)
for(var j=0;j<nodes.length;j++){
if(nodes[j].innerHTML.toLowerCase().indexOf('<ul>') > -1)
nodes[j].className += 'Open';
}
}
這里給每個(gè)樹枝加了一個(gè)className:Open
,因?yàn)槲覀儸F(xiàn)在還不能打開關(guān)閉樹枝,所以只要是樹枝那就是open的。當(dāng)然后面我們會(huì)用到Close的:)。相應(yīng)的修改一下CSS,給樹枝一個(gè)帶減號(hào)的圖標(biāo),表示它是打開的:
.TreeView li.Open{
background:transparent url(opened.gif) 12px 2px no-repeat;
}
高亮選中項(xiàng)
接下來實(shí)現(xiàn)把當(dāng)前選中的樹枝(或樹葉)高亮的功能。有兩個(gè)時(shí)候需要高亮:菜單初始化的時(shí)候和點(diǎn)擊某個(gè)菜單項(xiàng)的時(shí)候。
初始化的時(shí)候比較容易處理,直接給需要高亮的節(jié)點(diǎn)一個(gè)特殊的Class即可,比如“Selected
”:
.TreeView li.Selected a:link,
.TreeView li.Selected a:visited,
.TreeView li.Selected a:hover,
.TreeView li.Selected a:active{
background-color:#05F;
color:#FFF;
text-decoration:none;/*去除下劃線*/
cursor:default;/*讓光標(biāo)變?yōu)槠胀^,假裝是不能點(diǎn)的^_^*/
padding:0 2px;/*為了美觀考慮,也可以不要這句*/
}
這里有幾點(diǎn)可能還需要補(bǔ)充說明一下:
- 選擇器(Selector)的前面為什么要加上
.TreeView
,這不是冗余代碼嗎?
在這個(gè)例子中確實(shí)是冗余代碼,但在實(shí)際項(xiàng)目中,一個(gè)頁面上可能會(huì)有各種不同的組件,比如還有一個(gè)菜單,被選中的菜單項(xiàng)也用.Selected
來表示。這時(shí)就需要在選擇符的前面先指出是什么組件的選中項(xiàng)以防沖突。當(dāng)然還有其他的解決辦法,比如這里的類不取名為Selectd,改為TreeSelected或者其他什么的,但是這樣做人為的把命名方案復(fù)雜化了,我個(gè)人不推薦這樣做。 - 選擇器為什么分作四行來寫?
因?yàn)槲覀冎耙呀?jīng)設(shè)置過a
的樣式,為了提高優(yōu)先級(jí)重載舊的樣式,所以需要指定a
的四種偽狀態(tài)(還有其他提高優(yōu)先級(jí)的辦法,關(guān)于優(yōu)先級(jí)算法,在《網(wǎng)站重構(gòu)》一書中有詳細(xì)說明)。 Selected
為什么要用在li
上,而不直接用在a
上?
這又是一個(gè)不太容易說明白的地方,因?yàn)楹艽蟪潭壬纤且环N個(gè)人習(xí)慣,只是我個(gè)人覺得這樣做更合適一些。事實(shí)上,寫在li
上或a
上都是可以的,至少看上去(表現(xiàn)層的視角)不會(huì)有太大的區(qū)別,但是如果你從“表現(xiàn)層”中跳出來,站在“結(jié)構(gòu)層”的視角來看,無論這個(gè)菜單的樹結(jié)構(gòu)還是DOM結(jié)構(gòu),一個(gè)節(jié)點(diǎn)都是由一個(gè)li
來表達(dá)的,a
只不過是這個(gè)節(jié)點(diǎn)內(nèi)的更細(xì)節(jié)的部分。雖然我最終是希望給a
指定一個(gè)特殊的樣式(為什么不指定給li
?你可以自己試一下),但從XHTML結(jié)構(gòu)來說,這個(gè)class="Selected"
還是寫在li
上更合適。(上帝保佑我說清楚了……)
下一篇講什么?
這篇文章是我第一次加入Javascript內(nèi)容,不是很清楚是說淺了還是說深了,請(qǐng)大家在右邊留言告訴我你的想法。從下一篇開始,我們開始進(jìn)入部署鼠標(biāo)事件和響應(yīng)鼠標(biāo)事件方面的內(nèi)容。也許從下下篇開始再加入一些Javascript面向?qū)ο缶幊痰膬?nèi)容,待定待定……h(huán)ehe^_^
相關(guān)文章
javascript嚴(yán)格模式詳解(含嚴(yán)格模式與非嚴(yán)格模式的區(qū)別)
這篇文章主要介紹了javascript嚴(yán)格模式詳解(含嚴(yán)格模式與非嚴(yán)格模式的區(qū)別),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11聊聊JavaScript中的try...catch使用小技巧
作為一名web前端工程師,JavaScript中的try...catch是我們常用的特性之一,用于處理代碼中可能出現(xiàn)的錯(cuò)誤,本文小編將和大家一起聊聊JavaScript中的try...catch使用小技巧,需要的朋友可以參考下2023-11-11詳解webpack打包時(shí)排除其中一個(gè)css、js文件或單獨(dú)打包一個(gè)css、js文件(兩種方法)
本文通過兩種方法給大家介紹了webpack打包時(shí)排除其中一個(gè)css、js文件,或單獨(dú)打包一個(gè)css、js文件的方法 ,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-10-10理解Javascript_01_理解內(nèi)存分配原理分析
在正式開始之前,我想先說兩句,理解javascript系列博文是通過帶領(lǐng)大家分析javascript執(zhí)行時(shí)的內(nèi)存分配情況,來解釋javascript原理,具體會(huì)涵蓋javascript預(yù)加載,閉包原理,面象對(duì)象,執(zhí)行模型,對(duì)象模型...,文章的視角很特別,也非常深入,希望大家能接受這種形式,并提供寶貴意見。2010-10-10JavaScript實(shí)現(xiàn)標(biāo)題欄文字輪播效果代碼
這篇文章主要介紹了JavaScript實(shí)現(xiàn)標(biāo)題欄文字輪播效果代碼,涉及JavaScript基于時(shí)間函數(shù)及流程控制操作標(biāo)題欄文字的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10javaScript 計(jì)算兩個(gè)日期的天數(shù)相差(示例代碼)
本篇文章主要介紹了javaScript 計(jì)算兩個(gè)日期的天數(shù)相差(示例代碼) 需要的朋友可以過來參考下,希望對(duì)大家有所幫助2013-12-12javascript編程異常處理實(shí)例小結(jié)
這篇文章主要介紹了javascript編程異常處理的方法,結(jié)合實(shí)例形式分析總結(jié)了JavaScript編程中異常處理的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11