一個(gè)網(wǎng)站的前端由三個(gè)層構(gòu)成。由XHTML構(gòu)建的結(jié)構(gòu)層,它包括結(jié)構(gòu)化和有語(yǔ)義的標(biāo)簽,以及網(wǎng)站的內(nèi)容??梢栽谶@一層之上增加一個(gè)表現(xiàn)層(CSS)和一個(gè)行為層(JavaScript),它們使網(wǎng)站看起來(lái)更漂亮,對(duì)用戶更友好。這三層之間應(yīng)該保持嚴(yán)格的分離。打個(gè)比方來(lái)說(shuō),應(yīng)該具有這樣的可能性:可以重寫整個(gè)表現(xiàn)層而完全不需要觸動(dòng)到結(jié)構(gòu)層和行為層。
除了這種嚴(yán)格的分離,表現(xiàn)層和行為層都需要得到來(lái)自結(jié)構(gòu)層的指令。它們必須知道在哪里應(yīng)用樣式,在什么時(shí)候初始化行為——換句話說(shuō):它們需要觸發(fā)器。
CSS的觸發(fā)器大家都很了解。class和id屬性使你可以完全地控制網(wǎng)站的表現(xiàn)。然而,通過(guò)使用內(nèi)聯(lián)的樣式屬性(譯者注:指寫在XHTML標(biāo)簽中的style="..."屬性),你也可以在不使用這些觸發(fā)器的情況下工作,但這種用法是應(yīng)該被強(qiáng)烈反對(duì)的。當(dāng)你想要重新定義網(wǎng)站表現(xiàn)的時(shí)候,就會(huì)被迫連XHTML結(jié)構(gòu)層也一起改掉。它們的出現(xiàn)破壞了表現(xiàn)和結(jié)構(gòu)之間的分離。
行為層也應(yīng)該可以用同樣的方式工作。通過(guò)拋棄使用內(nèi)聯(lián)的事件句柄(比如onmouseover="switchImages(‘fearful‘,6,false)")
,我們可以把行為和結(jié)構(gòu)分開(kāi)。和CSS一樣,我們應(yīng)該使用觸發(fā)器去告訴腳本在哪里部署行為。
最簡(jiǎn)單的JavaScript觸發(fā)器是id屬性。
<div id="navigation"><ul><li><a href="#">Link 1</a></li><li><a href="#">Link 2</a></li><li><a href="#">Link 3</a></li></ul></div>var x = document.getElementById(‘navigation‘);if (!x) return;var y = x.getElementsByTagName(‘a(chǎn)‘);for (var i=0;i<y.length;i++)y[i].onmouseover = addBehavior;
這樣一來(lái),這段腳本就由是否出現(xiàn)id="navigation"
來(lái)觸發(fā)了。如果沒(méi)有id="navigation"
,那么什么也不會(huì)發(fā)生(if (!x) return
);如果它出現(xiàn)了,那么所有被它包圍的鏈接元素(指a標(biāo)簽)都會(huì)得到一個(gè)mouseover行為。這種解決方案簡(jiǎn)潔、優(yōu)雅,在所有的瀏覽器中都能工作。如果這種方法已經(jīng)能夠滿足你的需求,那么你就不需要再讀下去了:)
不幸的是有些情況下你不能使用id作為觸發(fā)器:
id
只能在文檔中出現(xiàn)一次,有時(shí)候你可能想把同樣的行為加到幾個(gè)(或一組)元素之上。 我們用表單腳本來(lái)作上面兩個(gè)問(wèn)題的例子。給XHTML加上表單校驗(yàn)觸發(fā)器會(huì)很實(shí)用,比如指定“這個(gè)輸入域(譯者注:文字輸入框、密碼輸入框等)是必填的”。為了實(shí)現(xiàn)這樣的觸發(fā)器,我們很可能得到如下的腳本:
function validateForm(){var x = document.forms[0].elements;for (var i=0;i<x.length;i++){if ([這個(gè)輸入域是必填的] && !x[i].value)// notify user of error}}
我們需要?jiǎng)?chuàng)建怎樣的觸發(fā)器才能告訴這個(gè)腳本哪些輸入域是必填的呢?顯然用id是不行的:理想的解決方案應(yīng)該可以對(duì)一大堆輸入域起作用。很自然的我們會(huì)想到是否可以用class
來(lái)觸發(fā)行為:
<input name="name" class="required" />if (x[i].className == ‘required‘ && !x[i].value)//提示用戶輸入此域
然而嚴(yán)格地說(shuō),class
屬性是用于定義CSS觸發(fā)器的。把CSS和JavaScript的觸發(fā)器合起來(lái)定義并不是不可能,但這樣做很可能使代碼變得一片混亂:
<input name="name" class="largefield required" />if (x[i].className.indexOf(‘required‘) != -1 &&!x[i].value)
依我看來(lái),class
屬性應(yīng)該只能用于CSS。class
是XHTML表現(xiàn)層的主要觸發(fā)器,如果再讓它承載行為層的信息就會(huì)使問(wèn)題變得復(fù)雜化。用class
屬性同時(shí)觸發(fā)兩個(gè)層是與行為和表現(xiàn)分離相抵觸的,但到底怎么做還是應(yīng)該由你自己視情況而定。
此外,觸發(fā)器也可以變得更復(fù)雜一些,而不僅僅是一個(gè)聲明“在這里部署(行為)”的命令。有時(shí)候你可能想給觸發(fā)器加一個(gè)變量,這樣可以使行為層變得更加通用,可以對(duì)每一個(gè)XHTML元素個(gè)體的需求作出響應(yīng),而不是傻乎乎地執(zhí)行一個(gè)標(biāo)準(zhǔn)的腳本。
拿一個(gè)表單打比方,這個(gè)表單包括一些字符串長(zhǎng)度有上限的文本輸入框。原先的maxlength
屬性已經(jīng)不再在textarea
元素上工作,所以我們必須寫個(gè)腳本來(lái)做這件事。另外,并不是這個(gè)表單里所有的文本輸入框都有相同的字符串長(zhǎng)度上限,所以在某個(gè)地方把它們個(gè)自的上限長(zhǎng)度存起來(lái)就顯得很必要了。
我們希望有這樣一個(gè)東西:
var x = document.getElementsByTagName(‘textarea‘);for (var i=0;i<x.length;i++){if ([這個(gè)文本輸入框有長(zhǎng)度上限])x[i].onkeypress = checkLength;}function checkLength(){var max = [讀取長(zhǎng)度上限的值];if (this.value.length > max)// notify user of error}
這段腳本需要兩個(gè)關(guān)鍵的信息:
在這里,用基于class的方式不再合適了。雖然從技術(shù)上講不是不能做到,但是所需的代碼會(huì)變得太復(fù)雜。打個(gè)比方,我們來(lái)給一個(gè)本身就帶有一個(gè)叫“large”的class的文本輸入框加上觸發(fā)器,以便告訴腳本它是必填的,而且長(zhǎng)度上限是300:
<textarea class="large required maxlength=300"></textarea>
這樣做不光把表現(xiàn)層和行為層混合在了一起,而且用于讀出這個(gè)值的腳本也會(huì)變得比較怪異:
var max = this.className.substring(this.className.indexOf(‘maxlength‘)+10);if (this.value.length > max)// 提醒用戶出錯(cuò)了。
很容易就注意到這段腳本只有當(dāng)我們把maxlength=x
放在最后一個(gè)的時(shí)候才能工作。如果我們想讓這個(gè)腳本可以處理不是放在最后一個(gè)的maxlength=x
(這種情況是常有的,比如我們想再加一個(gè)傳值的觸發(fā)器),它會(huì)變得更加復(fù)雜。
這就是我們現(xiàn)在面臨的問(wèn)題。如何才能添加完美的JavaScript觸發(fā)器,讓我們可以方便地把一般聲明(“在這里部署行為”)和針對(duì)元素的值一起傳給腳本?
從技術(shù)上來(lái)說(shuō),把這些信息一起加到class
屬性上是可能的,但問(wèn)題是class
是被設(shè)計(jì)出來(lái)做這件事的嗎?
我轉(zhuǎn)向另一種解決方案。再來(lái)看一下前面提到的textarea
長(zhǎng)度限制的例子。我們需要兩部分信息:
用自然、有語(yǔ)義的方式來(lái)表達(dá)這些信息需要添加一個(gè)自定義屬性給textarea
:
<textarea class="large" maxlength="300"></textarea>
maxlength
屬性通知腳本檢查用戶輸入,并通過(guò)屬性的值把長(zhǎng)度上限傳給腳本。同理,我們可以把“required”觸發(fā)器也改為一個(gè)自定義屬性。比如:required="true"
,無(wú)論給它賦什么值都可以,因?yàn)樗皇瞧鹨粋€(gè)通知的作用,無(wú)須附帶任何額外的信息。
<textarea class="large" maxlength="300" required="true"></textarea>
從技術(shù)上說(shuō),這么做沒(méi)有任何問(wèn)題。W3C DOM的 getAttribute()
方法可以從任何一個(gè)標(biāo)簽中讀取任意屬性的值。只有7.54版以前的Opera不允許從標(biāo)簽(如 <h2>
)中讀取一個(gè)錯(cuò)誤的屬性(如src
)。幸運(yùn)的是之后的版本對(duì) getAttribute()
提供了完全的支持。
下面就是我的解決方案:
function validateForm(){var x = document.forms[0].elements;for (var i=0;i<x.length;i++){if (x[i].getAttribute(‘required‘) && !x[i].value)// notify user of error}}var x = document.getElementsByTagName(‘textarea‘);for (var i=0;i<x.length;i++){if (x[i].getAttribute(‘maxlength‘))x[i].onkeypress = checkLength;}function checkLength(){var max = this.getAttribute(‘maxlength‘);if (this.value.length > max)// notify user of error}
依我看來(lái),這種方案很容易實(shí)現(xiàn),而且與JavaScript觸發(fā)器應(yīng)有的形式一致:一對(duì)“變量名/值”提供了觸發(fā)器的名字和腳本所需的值,它同時(shí)允許你針對(duì)每一個(gè)元素個(gè)體定義行為。最后,這種向XHTML添加觸發(fā)器的方式對(duì)于初學(xué)者來(lái)說(shuō)也是相當(dāng)簡(jiǎn)單的。
但這里又出現(xiàn)了另一個(gè)問(wèn)題,用這種方案制作的頁(yè)面無(wú)法通過(guò)校驗(yàn)。檢驗(yàn)器認(rèn)為required
和 maxlength
非法的。檢驗(yàn)器這樣做當(dāng)然是完全正確的,XHTML當(dāng)中壓根就沒(méi)有前一個(gè)屬性,后一個(gè)屬性也只屬于 <input>
元素。
解決方案就是讓它們合法:定義一個(gè)自定義的文檔類型定義(Document TYpe Definition,DTD),把XHTML作一個(gè)小小的擴(kuò)展,使它包含我們定義的屬性。這個(gè)自定義的DTD定義了新增的屬性以前它們應(yīng)該出現(xiàn)的正確位置,然后校驗(yàn)器就會(huì)按我們自定義的XHTML口味來(lái)校驗(yàn)文檔。如果DTD說(shuō)這些屬性是正確的,那它們就是正確的。
如果你不了解如何創(chuàng)建一個(gè)的DTD,請(qǐng)閱讀這篇J.David Eisenberg發(fā)表的《創(chuàng)建自定義的DTD》(譯者注:看大家的意見(jiàn)再?zèng)Q定是否翻譯^_^),在這篇文章中他會(huì)告訴你所有你想知道的。
依我看來(lái),用自定義屬性來(lái)觸發(fā)行為層——配合使這些屬性合法的自定義的DTD ——可以幫助我們把行為層與結(jié)構(gòu)層分離,同時(shí)保持簡(jiǎn)潔的代碼和高效的腳本。此外,一旦把這些屬性和腳本定義好,即使最菜的菜鳥(niǎo)也可以方便地把觸發(fā)器加入到XHTML文檔中。
Translated with the permission of A List Apart Magazine and the author.
在A List Apart雜志和原作者的授權(quán)下翻譯。
聯(lián)系客服