在这个教程里,我们将解释 dojo/on 以及如何使用它来监听DOM事件。 我们也会讲解 Dojo的 publish/subscribe框加: dojo/topic. 开始 许多Javascript 代码用于处理事件:响应一个件事或者产生一个新
在这个教程里,我们将解释 dojo/on 以及如何使用它来监听DOM事件。 我们也会讲解 Dojo的 publish/subscribe框加: dojo/topic.
开始
许多Javascript 代码用于处理事件:响应一个件事或者产生一个新的事件。交互式web应用程序创建有效的事件连接,关键在于建立响应。事件连件允许你的应用响应用户的交互并且等待动作的发生。 Dojo的主要DOM事件处理器是通过dojo/on完成。 接下来让我告诉你如何使用这个模块!DOM事件
你可能会问自己, “DOM是否已经提供了一个注册事件处理的机制?" ,文档对象模型本身是提供了事件处理,但不是所有的浏览器都遵循W3C的DOM规范。主流的三种处理事件方式: addEventListener, attachEvent,和 DOM 0. 除此以外,这里还有两种不同的事件对象实现 和至少一个在随机的顺序下触发监听事件和内存泄露的浏览器引擎。你只需简单的使用dojo/on模块就可以处理所有问题,不用考虑各种不同的原生API以及防止内存的工作。让我们看看下面的HTML标记:
<button id="myButton">Click me!</button> <div id="myDiv">Hover over me!</div>
让我们假设, 当你点击按纽时想要改变 div的颜色为蓝色。 当鼠标停在上面时, 显示为红色, 当移开后,返回到最初的白色。 通过dojo/on非常容易实现:
require(["dojo/on", "dojo/dom", "dojo/dom-style", "dojo/mouse", "dojo/domReady!"], function(on, dom, domStyle, mouse) { var myButton = dom.byId("myButton"), myDiv = dom.byId("myDiv"); on(myButton, "click", function(evt){ domStyle.set(myDiv, "backgroundColor", "blue"); }); on(myDiv, mouse.enter, function(evt){ domStyle.set(myDiv, "backgroundColor", "red"); }); on(myDiv, mouse.leave, function(evt){ domStyle.set(myDiv, "backgroundColor", ""); }); });
!* 注间 dojo/mouse资源是必须要的。 不是所有的浏览器都本地支持mouseenter和mouseleave 事件, 所以需要添加dojo/mouse. 可以自己像dojo/mouse一样创建自己的模块,以支持其它自定义事件类型。
这个例子说明了常见的模式: on(element, event name, handler). 或换句话说, 发现在这个元素的这个事件,连接到这个处理程序上。 这种模式可以应用到所有的窗口,文档, 节点,form, 鼠标, 和键盘事件上。
!* 注意,不像老的dojo.connect API, 当使用dojo/on模块时, "on" 事件名称的前缀(onClick中的on)必须被省略。
on 方法不仅权能使用注册事件API正规化。它使用处理事件也正规化:
- 事件的处理顺序是注册时的顺序。
- 事件处理程序的第一个参数是一个事件对象。
- 事件对象会被标准化为包含 W3C中事件对象的属性, 包含 target属性, stopPropagation方法以及preventDefault方法。
var handle = on(myButton, "click", function(evt){ // Remove this event using the handle handle.remove(); // Do other stuff here that you only want to happen one time alert("This alert will only happen one time."); });
!* 附带提一下, dojo/on 包含 一个更方像的方法:on.once. 它跟on法方一下,接受相同的参数。 但它将在一次触发后自动移除监听器。
最后需要注意的事项: 默认时, on 将会在第一个被传入的参数(节点)上下文里运行 事件处理函数。 除非on 使用了事件委托,之后会讨论什么是事件委托。
但是,你可以使用lang.hitch(dojo/_base/lang模块)来指名事件处理程序的运行上下文。 勾子在对象方法里非常有用。
require(["dojo/on", "dojo/dom", "dojo/_base/lang", "dojo/domReady!"], function(on, dom, lang) { var myScopedButton1 = dom.byId("myScopedButton1"), myScopedButton2 = dom.byId("myScopedButton2"), myObject = { id: "myObject", onClick: function(evt){ alert("The scope of this handler is " + this.id); } }; // This will alert "myScopedButton1" on(myScopedButton1, "click", myObject.onClick); // This will alert "myObject" rather than "myScopedButton2" on(myScopedButton2, "click", lang.hitch(myObject, "onClick")); });
查看示例
!* 不像on 的前任, dojo.connect. on 不在接受监听器的作用和方法参数。 如果你想要保留执行上下文,你需要在第三个参数中使用lang.hitch.
节点列表事件
在上个教程(dojo/query)提到过的, NodeList 提供了给多个节点注册事件的方法 - on 方法。 这个方法除了第一个参数跟dojo/on不一样外,其它的都是一样。!* dojo/query 包含了 on方法, 所以你不需要在加载dojo/on模块来使用NodeList.on。
让我们看看比上一个更高级的例子
<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> <script> require(["dojo/query", "dojo/_base/lang", "dojo/domReady!"], function(query, lang) { var myObject = { id: "myObject", onClick: function(evt){ alert("The scope of this handler is " + this.id); } }; query(".clickMe").on("click", myObject.onClick); query(".clickMeAlso").on("click", lang.hitch(myObject, "onClick")); }); </script>!* 注意,不像NodeList.connect, NodeList.on方法不会为了链式调用而返回NodeList对象。 代替的是它返回一个监听器数组,之后可以被移除。 这个数组包含一个数组级的remove方法(相对于遍历每个元素,在remove) 它可以一次移除所有的监听器。
查看Demo
事件委托
如上面的讨论, NodeList的on方法可以给多个DOM节点的相同事件连接相同的处理函数。 dojo/on也可以通过更加有效的事件委托机制,完成相同的任务。事件委托的原理是把原先给每个自己感兴趣的节点注册监听器,改为给它们的更高一级的节点注册一个监听器。 之后可以检查它捕获的事件,是否是你感觉的实际节点触发(冒泡到父节点)。 如果是, 监听器的代码会被执行。
以前,它是(现在依然是)通过dojox/NodeList/delegate扩展到NodeList. 在1.9, 它也可以dojo/on模块来完成事件委托, 使用语法 on(parent element, "selector:event name", handler).
为了更好的演示原理,我们来看另外一个例子。 效果跟之前的一样:
<div id="parentDiv"> <button id="button1" class="clickMe">Click me</button> <button id="button2" class="clickMe">Click me also</button> <button id="button3" class="clickMe">Click me too</button> <button id="button4" class="clickMe">Please click me</button> </div> <script> require(["dojo/on", "dojo/dom", "dojo/query", "dojo/domReady!"], function(on, dom){ var myObject = { id: "myObject", onClick: function(evt){ alert("The scope of this handler is " + this.id); } }; var div = dom.byId("parentDiv"); on(div, ".clickMe:click", myObject.onClick); }); </script>
!*注意, 我们虽然没有直接使用到dojo/query 模块,但也加载了它, 这是因为dojo/on为了事件委托中能够使用选择器,所以需要用到dojo/query的选择器引擎。 dojo/on不会自动加载dojo/query, 是为了开发者在不需要这个功能时,不需额外加载dojo/query模块。
查看Demo
当运行上面的 Demo, 注意到 this 依然会引用实际的节点,而不是parentDiv(父节点)。 当为事件委托使用on 和一般注册监听器使用on,有一个最要的区别: this 不在引用第一个被传入的参数的节点, 而是 选择器匹配到的节点。
对象方法(面向方面编程)
dojo/on的前任, dojo.connect, 可以处理函数到函数的事件连接。 这个功能已经单独到 dojo/aspect。 期待不久出的dojo/aspect教程发布与订阅
到目前为此,上面的例子都是给已知的对象(节点)注册已知的事件。 但是如果你不知道给节点注册什么事件或者 事件源对像还没有创建时,该怎么做? 这里需要使用到Dojo的 publish 和 subscribe(pub/sub)框架, 1.9采用dojo/topic模块。 Pub/sub允许你给一个"topic" (设想的一个事件,可以有多个事件源,为一个字符串)注册一个监听器。 这个监听器会在topic被发布时调用。让我们想下一个正在开发的应用, 我们想某个按纽提示用户的一个行为。 我们不想一次又一次的写提示这个功能, 但我们又不想给 button注册一个包装对象(定义一个对象,如上面的例子)。Pub/sub可以达到这个目标的:
<button id="alertButton">Alert the user</button> <button id="createAlert">Create another alert button</button> <script> require(["dojo/on", "dojo/topic", "dojo/dom-construct", "dojo/dom", "dojo/domReady!"], function(on, topic, domConstruct, dom) { var alertButton = dom.byId("alertButton"), createAlert = dom.byId("createAlert"); on(alertButton, "click", function() { // When this button is clicked, // publish to the "alertUser" topic topic.publish("alertUser", "I am alerting you."); }); on(createAlert, "click", function(evt){ // Create another button var anotherButton = domConstruct.create("button", { innerHTML: "Another alert button" }, createAlert, "after"); // When the other button is clicked, // publish to the "alertUser" topic on(anotherButton, "click", function(evt){ topic.publish("alertUser", "I am also alerting you."); }); }); // Register the alerting routine with the "alertUser" topic. topic.subscribe("alertUser", function(text){ alert(text); }); }); </script>
查看 Demo
使用这种事件模式的优势在于, 我们的应用程序在单元测试时可以不需要创建DOM 对象。 例行程序(监听器的代码) 可以跟 事件的生产者解偶。
如果你不在想接收到一个topic的通知, topic.subscribe会返回一个对象, 这个对像中的remove方法可以删除掉监听器。(如同dojo/on).
!* 注意不像dojo.publish, topic publish 不在接受一个数组。 例如 topic.publish("someTopic", "foo","bar") 等于 dojo.publish("someTopic",["foo","bar"]).