要達到 unobtrusive JavaScript (最新譯名: 不亂入JavaScript) 的境界,就必須對 Event-Driven 有深入的認識,這章對 Events 議題做了很詳盡的解說,前一陣子看YUI Event的時候,裡面提到的兩個IE fix都不太了解緣由,看過這章之後就恍然大悟啦。
相對於 W3C DOM 跟 XMLHttpRequest,Events有更多的 Browser Wars 留下來的問題,有著 Microsoft 蓄意的不相容問題,你必須注意這些實做差異,有些解法甚至有些 tricky,而且必須 case by case,沒有泛用型的偵測法則,例如沒有簡單的 if (W3C model) {.. } else if ( Microsoft model ) { … } 可以用。
Events 可分成三種類型 1. 滑鼠事件 mouse 2. 鍵盤事件 keyboard 3. 界面事件 interface events (例如 form submit,readystatechange等)
click是唯一自動有 accessible 的mouse event,因為當鍵盤 focus 且按 Enter 時也會觸發。而其它mouse events必須手動再加對應的keyboard events,例如 mouseover 等同於 focus,mouseout等於blurred,但有些對應就難了,例如 mousemove (滑鼠移動一個1px就觸發一個事件,但要對應鍵盤就不行了)。因此在 DOM Scripting 這本書也建議(p.111),設了 onclick 的就不要設定 onkeypress event了,因為 click 除了滑鼠,鍵盤也會有作用,多設 onkeypress 反而可能會有新麻煩。
另外一個 keyboard 的 accessability 重點是,可以被 keyboard focus 的元素只有 links,form fields,buttons 這四樣,這是個需要注意的事情,任何 keyboard-frientld event 或 script 都必須設定在這三種元素上面,例如在寫 mouseover 的鍵盤等價方案的時候,畢竟要可以被使用者按 tab 移動到,寫 event script 才有用…:p
Event-handler 註冊的四種方法:
-
inline ,例如直接在element中寫 onclick=”test()”
-
traditional model,例如 x.onclick= test
-
W3C model ,例如 x.addEventListener(‘click’,test,false) 或移除 x.removeEventListener(‘click’,test,false)
-
Microsoft model,例如 x.attachEvent(‘onclick’,test) 或移除 x.detachEvent(‘onclick’,test)
要為後兩者做一個跨瀏覽器的解法並不難,object detection 一下即可。
法一是 JavaScript亂入,不要用。
法二 traditional model 的問題是,無法連續為 x 掛載不同的事件,例如 x.onclick = test; x.onclick = test2 就只剩下 test2 了。因此需要再寫一個 function 包起來,如 x.onclick = function () { test(); test2(); },比較不彈性一點,但如果你可以完整掌握 page 到底有哪些 script,作者建議你可以用 traditional model。
法三法四比較先進(?)是用 事件掛載的方式,讓各個 script 都不會互相影響到。不過有趣的是竟然無法得知 x 已經掛載多少 event handlers,而如果不知道掛載了什麼,就無法移除。
接著是 W3C model 和 Microsoft model 的兩個重大差異,Microsoft model 不支援 event capturing,只有 bubbling(請參考 O3noBLOG addEventListener的第三個參數 ) 。另外就是 Microsoft model 把 event-handler function 當成一般 global function 來處理,也就是如果你用法四 attachEvent 來註冊事件時, event handler裡面的 this 指的是 window 而非發生事件的 object。(作者推薦 Dean Edward’s solution 可以fix)
event bubbling 能有什麼功效? 你可以非常有效率的只設定一個event handler到一個HTML element,然後就可以處理那個element裡面一整群的element,因為它會 bubbling 上去觸發。例如作者的 Usable Forms 的範例中,有132個 form fields,他不想每個都給他加上 event hander,這時就可以利用 event bubbling 在 document 加上 click event 即可,觸發任一個field都會 bubbling 上去到 document,然後在 event handler 中再來根據被觸發的 element 來決定做什麼動作。
在 event handler中,瀏覽器會自動產生一個 event object,W3C model 是根據第一個參數,Microsoft 則總是window.event,因此程式是
document.onclick = handleEvent;
function handleEvent(e) {
var evt = e || window.event;
var evtTarget = evt.target || evt.srcElement;
}
這個 event object 包含了觸發這個事件的 event type (即mouseover 或 keypress 等等)、觸發的 target element ( W3C叫 target, Microsoft叫 srcElement)、觸發時的滑鼠位置(跨瀏覽器處理很複雜,作者不建議去用。而且大部分的情況可以改用你觸發的element的相對位置來解決,第九章會詳述)、觸發時按下的滑鼠button跟鍵盤button 等等資訊。
注意到觸發事件的element跟 當初被定義 event handler 的 element 並不一定相同 (因為event bubbling),因此 target element 並不一定等於 this。在 traditional跟W3C model中,this 指被定義event handler的 element。但如前所述,微軟的 this 指成 window,也就是如果你有用微軟 attachEvent 這個 this 等於沒什麼用。
看樣子用 evtTarget 好像比較好? 不過瀏覽器對於到底是 “哪個element” 觸發也有不同看法!! 所以尤其是碰到 mouseover,mouseout 你必須特別check抓到的 element是你要的。
所以結論是到底用那種啦? (跨平台 event 果然難搞) 如果你不用Microsoft model的 addEvent的話,而且要被觸發的element跟被定義的element是同一個的話 (不玩event bubbling),那作者推薦用 this 吧。
注意到在DOM Scripting那本書中,Event-handler 完全使用 traditional model 法,也沒有用 event bubbling 進階手法,因此他的 this 一律就是 target element。
最後提一下 Yahoo! UI Library: Event,裡面的 Automatic Event Object Browser Abstraction 跟 Automatic Scope Correction 就是修正那兩個問題。
(待續,第八章 DOM 第九章 CSS 第十章 Ajax )