除了這種嚴格的分離,表現層和行為層都需要得到來自結構層的指令。它們必須知道在哪里應用樣式,在什么時候初始化行為――換句話說:它們需要觸發器。
CSS的觸發器大家都很了解。class和id屬性使你可以完全地控制網站的表現。然而,通過使用內聯的樣式屬性(譯者注:指寫在XHTML標簽中的style="..."屬性),你也可以在不使用這些觸發器的情況下工作,但這種用法是應該被強烈反對的。當你想要重新定義網站表現的時候,就會被迫連XHTML結構層也一起改掉。它們的出現破壞了表現和結構之間的分離。
JavaScript觸發器
行為層也應該可以用同樣的方式工作。通過拋棄使用內聯的事件句柄(比如onmouseover="switchImages('fearful',6,false)"),我們可以把行為和結構分開。和CSS一樣,我們應該使用觸發器去告訴腳本在哪里部署行為。
最簡單的JavaScript觸發器是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');for (var i=0;i<y.length;i++) y[i].onmouseover = addBehavior;
這樣一來,這段腳本就由是否出現id="navigation"來觸發了。如果沒有id="navigation",那么什么也不會發生(if (!x) return);如果它出現了,那么所有被它包圍的鏈接元素(指a標簽)都會得到一個mouseover行為。這種解決方案簡潔、優雅,在所有的瀏覽器中都能工作。如果這種方法已經能夠滿足你的需求,那么你就不需要再讀下去了:)
高級觸發器
不幸的是有些情況下你不能使用id作為觸發器:
一個id只能在文檔中出現一次,有時候你可能想把同樣的行為加到幾個(或一組)元素之上。
有些情況下腳本需要比僅僅指出“在這里部署”更多的信息(譯者注:比如傳遞一些參數)。
我們用表單腳本來作上面兩個問題的例子。給XHTML加上表單校驗觸發器會很實用,比如指定“這個輸入域(譯者注:文字輸入框、密碼輸入框等)是必填的”。為了實現這樣的觸發器,我們很可能得到如下的腳本:
function validateForm(){ var x = document.forms[0].elements; for (var i=0;i<x.length;i++) { if ([這個輸入域是必填的] && !x[i].value) // notify user of error }}
我們需要創建怎樣的觸發器才能告訴這個腳本哪些輸入域是必填的呢?顯然用id是不行的:理想的解決方案應該可以對一大堆輸入域起作用。很自然的我們會想到是否可以用class來觸發行為:
<input name="name" class="required" />if (x[i].className == 'required' && !x[i].value) //提示用戶輸入此域
然而嚴格地說,class屬性是用于定義CSS觸發器的。把CSS和JavaScript的觸發器合起來定義并不是不可能,但這樣做很可能使代碼變得一片混亂:
<input name="name" class="largefield required" />if ( x[i].className.indexOf('required') != -1 && !x[i].value)
依我看來,class屬性應該只能用于CSS。class是XHTML表現層的主要觸發器,如果再讓它承載行為層的信息就會使問題變得復雜化。用class屬性同時觸發兩個層是與行為和表現分離相抵觸的,但到底怎么做還是應該由你自己視情況而定。
信息傳遞觸發器
此外,觸發器也可以變得更復雜一些,而不僅僅是一個聲明“在這里部署(行為)”的命令。有時候你可能想給觸發器加一個變量,這樣可以使行為層變得更加通用,可以對每一個XHTML元素個體的需求作出響應,而不是傻乎乎地執行一個標準的腳本。
拿一個表單打比方,這個表單包括一些字符串長度有上限的文本輸入框。原先的maxlength屬性已經不再在textarea元素上工作,所以我們必須寫個腳本來做這件事。另外,并不是這個表單里所有的文本輸入框都有相同的字符串長度上限,所以在某個地方把它們個自的上限長度存起來就顯得很必要了。
我們希望有這樣一個東西:
var x = document.getElementsByTagName('textarea');for (var i=0;i<x.length;i++){ if ([這個文本輸入框有長度上限]) x[i].onkeypress = checkLength;}function checkLength(){ var max = [讀取長度上限的值]; if (this.value.length > max) // notify user of error}
這段腳本需要兩個關鍵的信息:
這個文本輸入框有長度上限嗎?這是一個很概括的觸發器,告訴腳本某些行為應該加在這里。
上限是什么?這是一個值,使得腳本可以正確地檢查用戶輸入。
在這里,用基于class的方式不再合適了。雖然從技術上講不是不能做到,但是所需的代碼會變得太復雜。打個比方,我們來給一個本身就帶有一個叫“large”的class的文本輸入框加上觸發器,以便告訴腳本它是必填的,而且長度上限是300:
<textarea class="large required maxlength=300"></textarea>
這樣做不光把表現層和行為層混合在了一起,而且用于讀出這個值的腳本也會變得比較怪異:
var max = this.className.substring( this.className.indexOf('maxlength')+10);if (this.value.length > max) // 提醒用戶出錯了。
很容易就注意到這段腳本只有當我們把maxlength=x放在最后一個的時候才能工作。如果我們想讓這個腳本可以處理不是放在最后一個的maxlength=x(這種情況是常有的,比如我們想再加一個傳值的觸發器),它會變得更加復雜。
面臨的問題
這就是我們現在面臨的問題。如何才能添加完美的JavaScript觸發器,讓我們可以方便地把一般聲明(“在這里部署行為”)和針對元素的值一起傳給腳本?
從技術上來說,把這些信息一起加到class屬性上是可能的,但問題是class是被設計出來做這件事的嗎?
自定義屬性
我轉向另一種解決方案。再來看一下前面提到的textarea長度限制的例子。我們需要兩部分信息:
這個文本輸入框有長度上限嗎?
上限是什么?
用自然、有語義的方式來表達這些信息需要添加一個自定義屬性給textarea:
<textarea class="large" maxlength="300"></textarea>
maxlength屬性通知腳本檢查用戶輸入,并通過屬性的值把長度上限傳給腳本。同理,我們可以把“required”觸發器也改為一個自定義屬性。比如:required="true",無論給它賦什么值都可以,因為它只是起一個通知的作用,無須附帶任何額外的信息。
<textarea class="large" maxlength="300" required="true"></textarea>
從技術上說,這么做沒有任何問題。W3C DOM的 getAttribute()方法可以從任何一個標簽中讀取任意屬性的值。只有7.54版以前的Opera不允許從標簽(如 <h2>)中讀取一個錯誤的屬性(如src)。幸運的是之后的版本對 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}
依我看來,這種方案很容易實現,而且與JavaScript觸發器應有的形式一致:一對“變量名/值”提供了觸發器的名字和腳本所需的值,它同時允許你針對每一個元素個體定義行為。最后,這種向XHTML添加觸發器的方式對于初學者來說也是相當簡單的。
自定義DTD
但這里又出現了另一個問題,用這種方案制作的頁面無法通過校驗。檢驗器認為required 和 maxlength非法的。檢驗器這樣做當然是完全正確的,XHTML當中壓根就沒有前一個屬性,后一個屬性也只屬于 <input>元素。
解決方案就是讓它們合法:定義一個自定義的文檔類型定義(Document TYpe Definition,DTD),把XHTML作一個小小的擴展,使它包含我們定義的屬性。這個自定義的DTD定義了新增的屬性以前它們應該出現的正確位置,然后校驗器就會按我們自定義的XHTML口味來校驗文檔。如果DTD說這些屬性是正確的,那它們就是正確的。
如果你不了解如何創建一個的DTD,請閱讀這篇J.David Eisenberg發表的《創建自定義的DTD》(譯者注:看大家的意見再決定是否翻譯^_^),在這篇文章中他會告訴你所有你想知道的。
依我看來,用自定義屬性來觸發行為層――配合使這些屬性合法的自定義的DTD ――可以幫助我們把行為層與結構層分離,同時保持簡潔的代碼和高效的腳本。此外,一旦把這些屬性和腳本定義好,即使最菜的菜鳥也可以方便地把觸發器加入到XHTML文檔中。
JavaScript技術:JavaScript觸發器詳解,轉載需保留來源!
鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯系我們修改或刪除,多謝。