Dojo 1.6 官方教程:Dojo中的事件

原题: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> <div id="myDiv">Hover over me!</div>

这里假设我们希望在点击按钮时使div变为蓝色,而当鼠标悬浮在其上时变为红色,移出时变回白色。下面的代码示例让我们看到使用dojo.connect可以很容易做到这些:

var myButton = dojo.byId("myButton"), myDiv = dojo.byId("myDiv"); dojo.connect(myButton, "onclick", function(evt){ dojo.style(myDiv, "backgroundColor", "blue"); }); dojo.connect(myDiv, "onmouseenter", function(evt){ dojo.style(myDiv, "backgroundColor", "red"); }); dojo.connect(myDiv, "onmouseleave", function(evt){ dojo.style(myDiv, "backgroundColor", ""); });

通过上面的例子我们得出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){ // Disconnect this event using the handle dojo.disconnect(handle); // Do other stuff here that you only want to happen one time alert("This alert will only happen one time."); });

最后一项需要注意的是:dojo.connect方法可在handler参数前定义一个可选参数,该参数用于定义handler的上下文。如果该参数未被指定,事件处理器的默认运行上下文将被设置为所传入的第一个参数node或是window对象(这一选择依赖于浏览器)。当使用widget时,
这一参数是非常重要的。

var myScopedButton1 = dojo.byId("myScopedButton1"), myScopedButton2 = dojo.byId("myScopedButton2"), myObject = { id: "myObject", onClick: function(evt){ alert("The scope of this handler is " + this.id); } }; // This will alert "myScopedButton1" dojo.connect(myScopedButton1, "onclick", myObject.onClick); // This will alert "myObject" rather than "myScopedButton2" 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> <button id="button2" class="clickMeAlso">Click me also</button> <button id="button3" class="clickMe">Click me too</button> <button id="button4" class="clickMeAlso">Please click me</button> <mce:script type="text/javascript"><!-- var myObject = { id: "myObject", onClick: function(evt){ alert("The scope of this handler is " + this.id); } }; dojo.query(".clickMe").connect("onclick", myObject.onClick); dojo.query(".clickMeAlso").connect("onclick", myObject, "onClick"); // --></mce:scrip

然而,这一用法有一个缺点:我们无法注销已连接的事件处理器。dojo.NodeList.connect出于便于使用的原因,将返回一个dojo.NodeList对象以用于链式调用,而并不是返回事件处理器列表。如果你确认并不需要对你的事件处理器进行注销操作,那么可以使用该方法。

查看Demo

对象方法

前面提到过,dojo.connect是Dojo的事件处理机制的核心。该结论也同样适用于原生对象,只不过针对原生对象,你需要将其中的成员方法想象成为事件。假设在页面上有一个按钮,同时我们有一个JS对象用于显示(或隐藏)该按钮:

<button id="myButton">My button</button> <mce:script type="text/javascript"><!-- var myButtonObject = { onClick: function(evt){ alert("The button was clicked"); } }; dojo.connect(dojo.byId("myButton"), "onclick", myButtonObject, "onClick"); // --></mce:script>

这里我们希望能够有一段代码用于通知按钮何时被点击。我们可以连接myButtonObject的onClick方法而并不需要对按钮的DOM节点上再绑定事件处理器:

dojo.connect(myButtonObject, "onClick", function(evt){ alert("The button was clicked and 'onClick' was called"); });

这里需要注意的是,如果连接到的是一个原生对象,那么将无法对事件名(dojo.connect的第二个参数)进行格式化,另外,所有被传入到被连接方法的参数也将作为参数传给处理器方法:

var myButtonObject2 = { onClickHandler: function(evt){ this.onClick(evt, "another argument"); }, onClick: function(){} }; dojo.connect(dojo.byId("myButton2"), "onclick", myButtonObject2, "onClickHandler"); dojo.connect(myButtonObject2, "onClick", function(evt, another){ alert("The button was clicked, we were given a second argument: " + another); });

由于DOM节点的事件处理器方法仅仅有一个参数,即事件对象,那么其连接的事件处理器方法也仅仅会被传入这一个参数;而连接到原生对象上的处理器方法将接受与被连接方法一样的多个参数。除了以上两点不同外,对于在DOM节点和原生对象上使用dojo.connect则再没有其他的区别。

连接到原生对象方法上现在看起来好像不是特别实用,不过接下来我们就会看到这一技术在小部件(widgets)上面是非常有作用的。另外,这一技术也很适用于特效应用,在其他的tutorial中会有关于特效的深入讲解,但在这里我们可以提供一个例子:

<button id="fadeButton">Fade block out</button> <div id="fadeTarget" class="red-block"></div> <mce:script type="text/javascript"><!-- var fadeButton = dojo.byId("fadeButton"), fadeTarget = dojo.byId("fadeTarget"); dojo.connect(fadeButton, "onclick", function(evt){ var anim = dojo.fadeOut({ node: fadeTarget }); dojo.connect(anim, "onEnd", function(){ alert("The fade has finished"); }); anim.play(); }); // --></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> <button id="createAlert">Create another alert button</button> <mce:script type="text/javascript"><!-- var alertButton = dojo.byId("alertButton"), createAlert = dojo.byId("createAlert"); dojo.connect(alertButton, "onclick", function(evt){ // When this button is clicked, // publish to the "alertUser" topic dojo.publish("alertUser", ["I am alerting you."]); }); dojo.connect(createAlert, "onclick", function(evt){ // Create another button var anotherButton = dojo.create("button", { innerHTML: "Another alert button" }, createAlert, "after"); // When the other button is clicked, // publish to the "alertUser" topic dojo.connect(anotherButton, "onclick", function(evt){ dojo.publish("alertUser", ["I am also alerting you."]); }); }); // Register the alerting routine with the "alertUser" // topic. dojo.subscribe("alertUser", function(text){ alert(text); }); // --></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应用中的一项利器。

你可能感兴趣的:(Dojo 1.6 官方教程:Dojo中的事件)