原題:Events with Dojo
原文連結: http://dojotoolkit.org/documentation/tutorials/1.6/events/
作者: Bran Forbes
譯者: wangqiang
本文將與讀者一同深入探究dojo.connect,如何使用Dojo來輕鬆的綁定DOM事件以及在原生對象上自訂事件。同時我們也將對Dojo的publish/subscribe架構進行探討。
難度:初學者
適用Dojo版本:1.6
前言
很多的JavaScript代碼都是圍繞著事件的,包括建立新事件或是對事件的響應。這意味著建立一個互動網路應用的關鍵就是建立有效事件串連體制。事件串連體制支援你的應用程式建立與使用者的互動及等待接受使用者的操作。在瀏覽器環境下具有原生的DOM事件,但同時我們也希望函數也可以具有類似這些事件一樣的調用方式:“當某件事發生時,調用此函數。”dojo.connect——Dojo事件體制中一個主要方法就提供了這一功能。
DOM事件
你可以會提出疑問:“DOM不是已經提供了為事件註冊處理函數的機制了嗎?”的確如此,但並非所有瀏覽器都提供對DOM規範的全面支援,縱觀主流瀏覽器的DOM實現機制,共有三種方式來實現對事件處理函數的註冊機制(addEventListener, attachEvent,以及DOM0)。另外還有兩種其他的事件實現機制和一個瀏覽器採用“隨機順序”對處理函數進行註冊,並在註冊事件處理器時會導致記憶體泄露,這對使用者的應用角度來說也是一個潛在的災難性因素。
幸好,Dojo為使用者提供了統一的DOM事件機制,通過使用Dojo的dojo.connectAPI,使用者可以避免各種DOM API的分歧,同時DOJO也預防了記憶體泄露問題。
假設我們有如下一段頁面代碼:
<button id="myButton">Click me!</button><br /><div id="myDiv">Hover over me!</div>
這裡假設我們希望在點擊按鈕時使div變為藍色,而當滑鼠懸浮在其上時變為紅色,移出時變回白色。下面的程式碼範例讓我們看到使用dojo.connect可以很容易做到這些:
var myButton = dojo.byId("myButton"),<br /> myDiv = dojo.byId("myDiv");</p><p>dojo.connect(myButton, "onclick", function(evt){<br /> dojo.style(myDiv, "backgroundColor", "blue");<br />});<br />dojo.connect(myDiv, "onmouseenter", function(evt){<br /> dojo.style(myDiv, "backgroundColor", "red");<br />});<br />dojo.connect(myDiv, "onmouseleave", function(evt){<br /> dojo.style(myDiv, "backgroundColor", "");<br />});
通過上面的例子我們得出dojo.connect的一般用法:dojo.connect(element, event name, handler)。這一用法可用於所有的視窗(window), 文檔(document), 節點(node), 表單(form),滑鼠以及鍵盤事件上。注意,在這個例子中,所有的事件名都採用了小寫,雖然這並非強制性的,而且Dojo會針對不同瀏覽器對這一參數進行格式化,可對事件名稱採用一致的格式化是一個較好的編碼習慣。
dojo.connect方法不僅是一個時間註冊API,同時它也可以定義如何對事件處理器進行調用:
- 事件處理器總是按其註冊順序進行調用
- 當事件處理器被調用時,第一個參數始終為一個事件對象
- 事件對象將會有一個 “target”屬性,一個"stopPropagation"方法,和一個“preventDefault”方法
如同DOM API一樣,Dojo提供了如何對事件處理器進行登出(解除串連)的方法:dojo.disconnect。將dojo.connect方法的傳回值作為參數傳遞給dojo.disconnect即可解除該事件處理器與事件之間的串連。例如,如果你想定義一個只運行一次的事件處理器,可以如下例所示進行定義:
var handle = dojo.connect(myButton, "onclick", function(evt){<br /> // Disconnect this event using the handle<br /> dojo.disconnect(handle);</p><p> // Do other stuff here that you only want to happen one time<br /> alert("This alert will only happen one time.");<br />});
最後一項需要注意的是:dojo.connect方法可在handler參數前定義一個選擇性參數,該參數用於定義handler的上下文。如果該參數未被指定,事件處理器的預設運行上下文將被設定為所傳入的第一個參數node或是window對象(這一選擇依賴於瀏覽器)。當使用widget時,
這一參數是非常重要的。
var myScopedButton1 = dojo.byId("myScopedButton1"),<br /> myScopedButton2 = dojo.byId("myScopedButton2"),<br /> myObject = {<br /> id: "myObject",<br /> onClick: function(evt){<br /> alert("The scope of this handler is " + this.id);<br /> }<br /> };</p><p>// This will alert "myScopedButton1"<br />dojo.connect(myScopedButton1, "onclick", myObject.onClick);<br />// This will alert "myObject" rather than "myScopedButton2"<br />dojo.connect(myScopedButton2, "onclick", myObject, "onClick");
查看Demo
當scope對象參數被指定後,handler參數必須為一個scope對象中的方法的名稱或者是一個函數對象,如上例中的最後一行,dojo.connect(myDiv, "onclick", myObject, myObject.onClick);。當handler參數為一個字串時,其必須是一個大小寫敏感的scope對象中的方法名,Dojo是無法對這一參數進行自動格式化的。
NodeList事件
如之前提到過的,dojo.NodeList提供了一個方法用於向多個節點註冊事件。除了第一個參數外,其用法與dojo.connect方法基本一致。首先讓我們看一個例子:
<button id="button1" class="clickMe">Click me</button><br /><button id="button2" class="clickMeAlso">Click me also</button><br /><button id="button3" class="clickMe">Click me too</button><br /><button id="button4" class="clickMeAlso">Please click me</button><br /><mce:script type="text/javascript"><!--<br />var myObject = {<br /> id: "myObject",<br /> onClick: function(evt){<br /> alert("The scope of this handler is " + this.id);<br /> }<br />};<br />dojo.query(".clickMe").connect("onclick", myObject.onClick);<br />dojo.query(".clickMeAlso").connect("onclick", myObject, "onClick");<br />// --></mce:scrip
然而,這一用法有一個缺點:我們無法登出已串連的事件處理器。dojo.NodeList.connect出於便於使用的原因,將返回一個dojo.NodeList對象以用於鏈式調用,而並不是返回事件處理器列表。如果你確認並不需要對你的事件處理器進行登出操作,那麼可以使用該方法。
查看Demo
對象方法
前面提到過,dojo.connect是Dojo的事件處理機制的核心。該結論也同樣適用於原生對象,只不過針對原生對象,你需要將其中的成員方法想象成為事件。假設在頁面上有一個按鈕,同時我們有一個JS對象用於顯示(或隱藏)該按鈕:
<button id="myButton">My button</button><br /><mce:script type="text/javascript"><!--<br /> var myButtonObject = {<br /> onClick: function(evt){<br /> alert("The button was clicked");<br /> }<br /> };<br /> dojo.connect(dojo.byId("myButton"), "onclick", myButtonObject, "onClick");<br />// --></mce:script>
這裡我們希望能夠有一段代碼用於通知按鈕何時被點擊。我們可以串連myButtonObject的onClick方法而並不需要對按鈕的DOM節點上再綁定事件處理器:
dojo.connect(myButtonObject, "onClick", function(evt){<br /> alert("The button was clicked and 'onClick' was called");<br />});
這裡需要注意的是,如果串連到的是一個原生對象,那麼將無法對事件名(dojo.connect的第二個參數)進行格式化,另外,所有被傳入到被串連方法的參數也將作為參數傳給處理器方法:
var myButtonObject2 = {<br /> onClickHandler: function(evt){<br /> this.onClick(evt, "another argument");<br /> },<br /> onClick: function(){}<br />};<br />dojo.connect(dojo.byId("myButton2"), "onclick",<br /> myButtonObject2, "onClickHandler");<br />dojo.connect(myButtonObject2, "onClick", function(evt, another){<br /> alert("The button was clicked, we were given a second argument: " + another);<br />});
由於DOM節點的事件處理器方法僅僅有一個參數,即事件對象,那麼其串連的事件處理器方法也僅僅會被傳入這一個參數;而串連到原生對象上的處理器方法將接受與被串連方法一樣的多個參數。除了以上兩點不同外,對於在DOM節點和原生對象上使用dojo.connect則再沒有其他的區別。
串連到原生對象方法上現在看起來好像不是特別實用,不過接下來我們就會看到這一技術在小組件(widgets)上面是非常有作用的。另外,這一技術也很適用於特效應用,在其他的tutorial中會有關於特效的深入講解,但在這裡我們可以提供一個例子:
<button id="fadeButton">Fade block out</button><br /><div id="fadeTarget" class="red-block"></div><br /><mce:script type="text/javascript"><!--<br /> var fadeButton = dojo.byId("fadeButton"),<br /> fadeTarget = dojo.byId("fadeTarget");</p><p> dojo.connect(fadeButton, "onclick", function(evt){<br /> var anim = dojo.fadeOut({ node: fadeTarget });</p><p> dojo.connect(anim, "onEnd", function(){<br /> alert("The fade has finished");<br /> });</p><p> anim.play();<br /> });<br />// --></mce:script>
查看Demo
這裡我們不對特效相關的內容做過多的介紹,只需要知道dojo.fadeOut將返回一個帶有onEnd方法的對象,onEnd方法將在特效完成後被觸發。在此,我們可以將返回對象的onEnd方法進行綁定,彈出對話方塊告訴使用者動畫特效何時結束。在這一例子裡,當紅色地區淡出效果結束後,我們所綁定的處理函數就將會被觸發。
Publish/Subscribe
到目前為止,以上的例子都是針對已經建立的對象(DOM節點,某個小組件[widget],或是某個特效對象),將我們的事件處理器綁定在其上,並以其作為事件發行者。那麼,當我們並沒有將事件處理器綁定到某個對象上,或者我們不知道要綁定的對象是否已經被建立時,我們又該如何去做呢?在這種情況下,我們就會需要用到Dojo的publish和subscribe(pub/sub)架構了。pub/sub使我們可以將某個處理器註冊(或稱之為“訂閱”[subscribe])到某個“主題”(一個具有多個事件觸發源的事件的特定名稱,可用字串表示),我們所註冊的處理器將在該綁定“主題”被發布時被觸發調用。
假設我們正在開發某一個應用,其中需要建立一些按鈕來告知使用者相應的行為。我們既不想重複的寫這一通知程式,也不希望通過在按鈕中寫入內嵌對象來實現事件註冊。那麼最好的方法就是使用pub/sub:<button id="alertButton">Alert the user</button><br /><button id="createAlert">Create another alert button</button></p><p><mce:script type="text/javascript"><!--<br /> var alertButton = dojo.byId("alertButton"),<br /> createAlert = dojo.byId("createAlert");</p><p> dojo.connect(alertButton, "onclick", function(evt){<br /> // When this button is clicked,<br /> // publish to the "alertUser" topic<br /> dojo.publish("alertUser", ["I am alerting you."]);<br /> });<br /> dojo.connect(createAlert, "onclick", function(evt){<br /> // Create another button<br /> var anotherButton = dojo.create("button", {<br /> innerHTML: "Another alert button"<br /> }, createAlert, "after");</p><p> // When the other button is clicked,<br /> // publish to the "alertUser" topic<br /> dojo.connect(anotherButton, "onclick", function(evt){<br /> dojo.publish("alertUser", ["I am also alerting you."]);<br /> });<br /> });</p><p> // Register the alerting routine with the "alertUser"<br /> // topic.<br /> dojo.subscribe("alertUser", function(text){<br /> alert(text);<br /> });<br />// --></mce:script>
這一事件模式的一個優點是,我們不需要建立任何的DOM對象來進行單元測試,通知程式與事件是完全解耦合的。以下是一些pub/sub的一些注意事項:
- dojo.subscribe的調用方式與dojo.connect的調用方式相類似(dojo.subscribe(topic, handler)或dojo.subscribe(topic, scope, handler or method name))
- dojo.publish方法的第二個參數必須是一個數組對象,該數組對象中的元素即為主題處理器函數的參數。
- dojo.sbuscribe將返回一個對象,該對象可被傳入到dojo.unsubscribe方法用於登出該主題中的特定的處理器(作用與dojo.connect及dojo.disconnect相似)
小結
Dojo的事件系統十分強大,同時也十分便於使用。dojo.connect方法可以使使用者忽略DOM對象與原生對象的事件的區別,以及事件在不同瀏覽器的不一致。Dojo的pub/sub則提供給開發人員一種很方便的解耦合事件處理器與事件發行者的方法。一旦你對這些工具有所瞭解,它們將成為你開發Web應用中的一項利器。