首页 > 代码库 > Events with Dojo(翻译)

Events with Dojo(翻译)

In this tutorial, we will be exploring dojo/on and how Dojo makes it easy to connect to DOM events. We will also explore Dojo‘s publish/subscribe framework: dojo/topic.

在本教程中,我们将会探讨dojo/on模块,使用该模块可以更简单的关联DOM事件。我们也会探讨dojo的publish和dubcribe框架:dojo/topic。

Getting Started 开始

Much of your JavaScript code will be preparing for events: both responding to and generating new events. This means that the key to building responsive, interactive web applications is creating effective event connections. Event connections allow your application to respond to user interaction and wait for actions to happen. Dojo‘s main DOM event workhorse is dojo/on; let me show you how to use this module!

在很多javascript代码中,我们编写响应事件和创建新事件的代码。这就意味着,能够创建有效的事件连接机制,是我们创建一个交互响应丰富的Web应用的重要前提。事件可以让你的web应用和用户操作关联在一起,让你的事件响应代码或函数一直等待着事件被触发。在dojo中,定义事件相关功能的模块是dojo/on模块,下面我们看下如何使用该模块。

DOM Events DOM事件

You might be asking yourself, “Doesn’t the DOM already provide a mechanism for registering event handlers?” The answer is yes—but not all browsers follow the W3C DOM specification. Between the various DOM implementations from major browser vendors there are three ways to register event handlers: addEventListener, attachEvent, and DOM 0. Beyond that, there are two different event object implementations and at least one browser engine that fires registered listeners in random order.aspx) and leaks memory.aspx). Dojo improves the way you work with DOM events by normalizing differences between the various native APIs, preventing memory leaks, and doing it all in a single, straightforward event API called dojo/on.

Let’s say we have the following markup:

你可能会忍不住问,DOM本身没有提供原生态的事件响应机制吗? 是的,DOM提供了,但并不是所有的浏览器都是基于W3C标准设计的,而DOM提供的事件只支持W3C标准下的浏览器。在各种版本的DOM标准和各种浏览器中,注册事件有三种方式:addEventListener, attachEvent, and DOM 0。在此之前,有两个不同的事件实现对象和至少一个浏览器引擎。dojo则提供了一种标准的事件操作API,是用该方式,你可以忽略各种不同版本API的差异,防止内存泄漏等。所以你只要了解dojo/on模块提供的一种API就可以解决以前遇到的诸多问题。

Let’s say we have the following markup:

让我们看下下面的例子:

1 <button id="myButton">Click me!</button>2 <div id="myDiv">Hover over me!</div>

 

Let‘s also imagine that you want the div to change to blue when you click the button, red when you hover over it, and back to white when you‘re done hovering. dojo/on makes that very simple:

当鼠标点击按钮的时候,我们想把div的颜色变成蓝色,当鼠标移动到div的时候,把它的颜色变为红色,当鼠标离开的时候,又变回白色。使用dojo/on模块实现该功能是很简单的。

 1 require(["dojo/on", "dojo/dom", "dojo/dom-style", "dojo/mouse", "dojo/domReady!"], 2     function(on, dom, domStyle, mouse) { 3         var myButton = dom.byId("myButton"), 4             myDiv = dom.byId("myDiv"); 5  6         on(myButton, "click", function(evt){ 7             domStyle.set(myDiv, "backgroundColor", "blue"); 8         }); 9         on(myDiv, mouse.enter, function(evt){10             domStyle.set(myDiv, "backgroundColor", "red");11         });12         on(myDiv, mouse.leave, function(evt){13             domStyle.set(myDiv, "backgroundColor", "");14         });15 });

 

Notice that the dojo/mouse resource was also required. Not all browsers natively support mouseenter and mouseleave events, so dojo/mouse adds that support. You can write your own modules like dojo/mouse to enable support for other custom event types.

需要注意的是,我们需要加载dojo/mouse资源模块。并不是所有的浏览器都原生态支持mouseenter和mouseleave时间,所以,dojo/mouse做了一些操作,使这些浏览器也支持。你也可以写一个类似于dojo/mouse的自定义模块去支持其他的自定义事件。

This example illustrates the common pattern: on(element,event name,handler). Or, put another way, for this event on this element, connect this handler. This pattern applies to all window, document, node, form, mouse, and keyboard events.

该例子通过举例说明了on(element,event name,hanslder)函数的用法。用另外一种方式描述就是为一个元素的时间关联了一个函数。在处理window、document、node、form、mouse、和keyboard事件时也是使用的这种模式。

Note that unlike the older dojo.connect API, the "on" event name prefix must be omitted when using the dojo/on module.

注意,不同于之前旧的dojo.connectAPI,在引用dojo/on模块之后,on前面的dojo前缀是可以省略掉的。

The on method not only normalizes the API to register events, but it also normalizes how event handlers work:

on函数不仅规范化了注册事件的API,而且还规范了事件引用的工作机制:

  • Event handlers are always called in the order they are registered.
  • They are always called with an event object as their first parameter.
  • The event object will always be normalized to include common W3C event object properties, including things like a target property, a stopPropagation method, and a preventDefault method.
  • 注册到事件上的处理函数在执行时,执行的顺序和其注册的顺序是一致的。
  • 注册到事件上的处理函数的第一个参数是事件对象参数。
  • 事件对象是支持W3C标准规范的,其对象包括target属性、stopPropagation函数和preventDefault函数。

Much like the DOM API, Dojo also provides a way to remove an event handler: _handle_.remove. The return value of on is a simple object with a remove method, which will remove the event listener when called. For example, if you wanted to have a one-time only event, you could do something like this:

和DOM原生态API一样,dojo也提供了移除一个事件委托函数的方法:_handle_.remove。on函数返回的是一个简答对象,该对象包含了一个remove函数,当我们想移除该事件监听的时候,调用该函数即可。例如,如果你想让事件只执行一次,那么你可以写下面的代码:

1 var handle = on(myButton, "click", function(evt){2     // Remove this event using the handle3     handle.remove();4 5     // Do other stuff here that you only want to happen one time6     alert("This alert will only happen one time.");7 });

 

Incidentally, dojo/on includes a convenience method for doing exactly this: on.once. It accepts the same parameters as on, but will automatically remove the handler once it is fired.

事实上,dijo/on模块包含了一个更简单的方法:on.once。该函数的参数和on函数一样,但该事件被触发一次之后,事件委托句柄就会被移除掉。

One last thing to note: by default, on will run event handlers in the context of the node passed in the first argument. The one exception to this is when on is used for event delegation, which we will discuss shortly.

最后需要注意的是:默认情况下,on函数会吧返回的事件句柄作为事件函数的第一个参数传到函数中。一个例外的情况,就是on作为事件委托使用,这点我们会稍后讨论。

However, you can use lang.hitch (from the dojo/_base/lang module) to specify the context in which to run the handler. Hitching is very helpful when working with object methods:

我们可以使用lang.hitch(定义在dojo/_base/lang模块)指定运行事件委托函数的上下文环境。当在对象的函数基础上运行时,Hitching是非常有用的:

 1 require(["dojo/on", "dojo/dom", "dojo/_base/lang", "dojo/domReady!"], 2     function(on, dom, lang) { 3  4         var myScopedButton1 = dom.byId("myScopedButton1"), 5             myScopedButton2 = dom.byId("myScopedButton2"), 6             myObject = { 7                 id: "myObject", 8                 onClick: function(evt){ 9                     alert("The scope of this handler is " + this.id);10                 }11             };12 13         // This will alert "myScopedButton1"14         on(myScopedButton1, "click", myObject.onClick);15         // This will alert "myObject" rather than "myScopedButton2"16         on(myScopedButton2, "click", lang.hitch(myObject, "onClick"));17 18 });

 

View Demo

Unlike on‘s predecessor, dojo.connect, on does not accept the handler scope and method arguments. You will need to make use of lang.hitch for the third argument if you wish to preserve execution context.

不同于on的前身dojo/connection,on模块不接受事件委托函数范围以及函数参数。你可以使用lang.hitch作为第三个参数,为当前运行的代码提供上下文环境。

NodeList events 节点列表事件

As mentioned in the previous tutorial, NodeList provides a way to register events to multiple nodes: the on method. This method follows the same pattern as dojo/on without the first argument (since the nodes in the NodeList are the objects you are connecting to).

正如我们在前面教程阐述的,NodeList提供了on函数,用来为节点批量注册事件。和dojo/on模块中的on函数相比,除了不需要第一个参数之外,其他的实现机制都是一样的。

The on method is included with dojo/query, so you do not have to require the dojo/on resource explicitly to use NodeList.on.

NodeList的on函数定义在dojo/query模块中,所以你在使用NodeList的on函数时,不需要在加载dojo/on模块。

Let‘s look at a more advanced example than before:

让我们再看下比之前的稍复杂些的例子:

 1 <button id="button1" class="clickMe">Click me</button> 2 <button id="button2" class="clickMeAlso">Click me also</button> 3 <button id="button3" class="clickMe">Click me too</button> 4 <button id="button4" class="clickMeAlso">Please click me</button> 5 <script> 6 require(["dojo/query", "dojo/_base/lang", "dojo/domReady!"], 7     function(query, lang) { 8  9         var myObject = {10             id: "myObject",11             onClick: function(evt){12                 alert("The scope of this handler is " + this.id);13             }14         };15         query(".clickMe").on("click", myObject.onClick);16         query(".clickMeAlso").on("click", lang.hitch(myObject, "onClick"));17 18 });19 </script>

 

Note that unlike NodeList.connect, the NodeList.on method does not return the NodeList back for further chaining; instead, it returns an array of on handles that can be removed later. The array also includes a convenient top-level remove method, which will remove all of the listeners at once.

注意,不同于NodeList.connect,NodeList.on函数不在返回NodeList,而是返回一组事件委托对象集合,这些集合可以在后面的代码中被移除掉。这个集合也包含了remove函数,可以一次把所有事件监听一次全部移除。

View Demo

Event Delegation 事件委托

As discussed above, the on method of NodeList makes it easy to hook up the same handler to the same event of multiple DOM nodes. dojo/on also makes this achievable through a more efficient means known as event delegation.

正如我们在上面讨论的,NodeList的on函数可以很方便的为多个DOM节点设置同样的事件响应函数引用。当我们得到一个事件委托的时候,使用dojo/on模块,也可以很快速的达到该目的。

The idea behind event delegation is that instead of attaching a listener to an event on each individual node of interest, you attach a single listener to a node at a higher level, which will check the target of events it catches to see whether they bubbled from an actual node of interest; if so, the handler‘s logic will be performed.

事件委托出现的目的是为了不让每个要响应事件的节点都添加监听。

Previously, this was (and still is) achievable through the dojox/NodeList/delegate extension to NodeList. In 1.10, it is also possible using the dojo/on module, using the syntax on(parent element, "selector:event name",handler).

To better illustrate this, let‘s look at another example, based on the same premise as the previous one:

 1 <div id="parentDiv"> 2     <button id="button1" class="clickMe">Click me</button> 3     <button id="button2" class="clickMe">Click me also</button> 4     <button id="button3" class="clickMe">Click me too</button> 5     <button id="button4" class="clickMe">Please click me</button> 6 </div> 7 <script> 8 require(["dojo/on", "dojo/dom", "dojo/query", "dojo/domReady!"], 9     function(on, dom){10 11         var myObject = {12             id: "myObject",13             onClick: function(evt){14                 alert("The scope of this handler is " + this.id);15             }16         };17         var div = dom.byId("parentDiv");18         on(div, ".clickMe:click", myObject.onClick);19 20 });21 </script>

 

Notice that we also required the dojo/query module, although we don‘t use it directly. This is because dojo/on needs a selector engine exposed by dojo/query in order to be able to match selectors used for event delegation. This is not pulled in automatically by dojo/on in order to reduce its footprint and avoid "penalizing" developers for a feature that might not always be used.

需要注意的时,虽然我们没有直接引用dojo/query模块,但我们依然加载了它。主要是因为我们在dojo/on模块中使用到了dojo/query模块提供的筛选函数,以通过选择器匹配事件委托器。主要是该方法可能开发者不是经常使用,为了避免脚本下载的大小影响效率,dojo/on没有做dojo/query显示依赖,所以需要手动的加载一下。

View Demo

When running the above demo, notice how this still refers to the actual node we are interested in, not the parentDiv node. This is an important distinction to note when using on for event delegation: this no longer refers to the node passed as the first argument, but rather to the node which the selector matched. This can actually be quite useful, once you know to expect it.

当我们运行上面的例子时,注意到,为什么this这个引用依然指向我们感兴趣的Button,而不是div节点?在使用事件委托和on函数时和直接使用事件注册一个很重要的区别:this已经不再指向函数的第一个参数,而是指向选择器匹配的节点。这点在实际开发时非常有用。

Object Methods

dojo/on‘s predecessor, dojo.connect, was also responsible for function-to-function event connections. This functionality has been broken out into its own resource called dojo/aspect. Look forward to seeing a dojo/aspect tutorial very soon!

dojo.connect,作为dojo/on模块的前任,依然可以负责通过函数对函数进行事件连接。这些功能被分散到了dojo/aspect模块中,我们将很快会看到该教程。

Publish/Subscribe 发布/订阅

All of the examples up until this point have used an existing object as an event producer that you register with to know when something happens. But what do you do if you don‘t have a handle to a node or don‘t know if an object has been created? This is where Dojo‘s publish and subscribe (pub/sub) framework comes in, exposed via the dojo/topic module in 1.10. Pub/sub allows you to register a handler for (subscribe to) a "topic" (which is a fancy name for an event that can have multiple sources, represented as a string) which will be called when the topic is published to.

到目前为止,所有的例子都是你已经存在了一个对象,该对象有一些事件,你定义了一些函数注册到该事件上。但如果你没有一个节点的引用,或者你不知道一个对象是否被创建怎么办?这就是dojo订阅和发布框架出现的原因,该框架定义在dojo1.10的dojo/topic模块中。

Let‘s imagine that in an application that we‘re developing, we want certain buttons to alert the user of an action; we don‘t want to write the routine to do the alerting more than once, but we also don‘t want to make a wrapping object that will register this small routine with the button. Pub/sub can be used for this:

 1 <button id="alertButton">Alert the user</button> 2 <button id="createAlert">Create another alert button</button> 3  4 <script> 5 require(["dojo/on", "dojo/topic", "dojo/dom-construct", "dojo/dom", "dojo/domReady!"], 6     function(on, topic, domConstruct, dom) { 7  8         var alertButton = dom.byId("alertButton"), 9             createAlert = dom.byId("createAlert");10 11         on(alertButton, "click", function() {12             // When this button is clicked,13             // publish to the "alertUser" topic14             topic.publish("alertUser", "I am alerting you.");15         });16 17         on(createAlert, "click", function(evt){18             // Create another button19             var anotherButton = domConstruct.create("button", {20                 innerHTML: "Another alert button"21             }, createAlert, "after");22 23             // When the other button is clicked,24             // publish to the "alertUser" topic25             on(anotherButton, "click", function(evt){26                 topic.publish("alertUser", "I am also alerting you.");27             });28         });29 30         // Register the alerting routine with the "alertUser" topic.31         topic.subscribe("alertUser", function(text){32             alert(text);33         });34 35 });36 </script>

View Demo

One useful advantage to this pattern of events is that now our alerting routine can be tested in a unit test without the need to create any DOM objects: the routine is decoupled from the event producer.

If you ever wish to stop receiving notifications of a topic, topic.subscribe returns an object with a remove method that can be used to remove the respective handler (much like on).

Note that unlike dojo.publish, topic.publish does not expect published arguments to be passed in an array. For instance, topic.publish("someTopic", "foo", "bar") is equivalent to dojo.publish("someTopic", ["foo", "bar"]).

Conclusion

Dojo‘s event system is quite powerful, while being fairly simple to use. The on method normalizes DOM event inconsistencies between browsers. Dojo‘s pub/sub framework, dojo/topic, gives developers a way to easily decouple event handlers from the event producers. Take time to familiarize yourself with these tools — they will be a valuable asset in creating web applications.

 

Events with Dojo(翻译)