原文:
http://dojotoolkit.org/documentation/tutorials/1.10/events/
难度等级:初级 Dojo 版本:1.10
Events with Dojo
本教程中,我们会探讨 dojo/on 以及 Dojo 如何使用它来简化 DOM events 关联。我们还会探索 Dojo 的 publish/subscribe (发布/订阅)框架: dojo/topic。
起步
你的JavaScript 代码大部分应该都是用来处理事件的:响应或者产生新的事件。这也意味着一个事实,创建响应式的,交互式的 web应用核心就在于创建有效的事件关联。事件连接(event connections)让你的应用响应用户交互,等待动作发生。Dojo 主要的 DOM 事件机制就是依赖 dojo/on;让我们学习如何使用这个 module!
DOM 事件
你可能经常问自己,“DOM 是不是还没有一套完备的机制用来注册事件句柄?”答案是 yes,因为并不是所有的浏览器都遵循 W3C DOM 规范。来自于主流浏览器厂家的 DOM 实现主要采用三种方法注册事件句柄: addEventListener, attachEvent, 和 DOM 0。除此外还有两种不同的 event object 实现,至少一种浏览器有 和 问题(大家都知道是谁吧)。Dojo 改进了这些 DOM events 方法,消除了各种 native APIs 的差异,阻止了内存泄露,并将其封装成唯一一个简明的事件 API,就是 dojo/on。
假定我们有如下标记:
-
<button id="myButton">Click me!</button>
-
<div id="myDiv">Hover over me!</div>
我们再想象这个功能,点击 button 后,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)。可理解为,将某元素上的某事件,绑定到某句柄。这个模式适用于all window, document, node, form, mouse, 和 keyboard 事件。
注意同老的 dojo.connect API 不同,使用 dojo/on 模块时, "on" 这个event name 前缀必须忽略。
on 方法不仅标准化事件注册的 API,同时也标准化事件句柄的工作方式:
-
事件句柄总是以其注册顺序被调用。
-
他们被调用时,总是以 event object 为其 第一个参数。
-
事件对象被标准化,包含通用的 W3C 事件对象属性,包括 target 属性,一个 stopPropagation 方法和一个 preventDefault 方法。
同 DOM API 不同, Dojo 还提供了移除事件句柄的方法:handle.remove。on 函数的返回值是一个简单对象,带一个 remove 方法,当其调用时会删除事件 listener。比如,你想要一个只运行一次的事件,可以如下编写:
-
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 是在第一个参数传入的 node 的上下文中运行 event handlers 。只有当 on 用于 event delegation(事件委派) 时例外,我们马上会讨论到这些。
你还可以使用 lang.hitch (来自 dojo/_base/lang 模块) 来指定 handler 运行的上下文。Hitching 同对象方法协作时非常有用。
-
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 不会接受 handler 作用域以及方法等参数。如果你想保留运行上下文,你需要通过 lang.hitch 接收第三方参数。
NodeList 事件
前面其他文章提到过,NodeList 提供了给多个 nodes 注册事件的方法:on 方法。这个方法同 dojo/on 调用模式基本一样,除了没有第一个参数(因为 NodeList 中的 nodes 已经指明了 on 函数需要关联的对象,因此省略)。
dojo/query 中已经包含了 on 方法,因此想要使用 NodeList.on 的话也不用 require dojo/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 方法的返回值是一个 handles 组成的数组,而不是 NodeList 本身(可形成链式调用),这种方式便于后续对 handles 进行删除。这个返回数组包含了一个顶层的 remove 方法,可以一次性删除所有的 listeners。
事件委派-Event Delegation
如上面讨论所说,NodeList 的 on 方法可以很容易的对多个 DOM 节点的同一事件关联相同的 handler。dojo/on 使用一种效率更高的方法 event delegation(事件委派) 实现同一目的。
事件委派的核心思想就是:与其对每一个单独的节点各自关联一个事件 listener,不如在更高一层的节点上只关联一个 listener,这个 listener 捕获到事件后,会判断目标节点是否需要处理,如果需要,则执行相关逻辑。
在以前(现在也可以)这是通过针对 NodeList 的 dojox/NodeList/delegate 扩展来实现的。Dojo 1.10 版本中,使用 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 自动载入这个模块,这是为了尽量削减其调用,以避免不靠谱的程序员仅仅为了实现某些特性就将其一直占用。
运行这个 demo,我们会注意到 this 是指向我们需操作的节点上的,而非 parentDiv 节点。将 on 用于事件委派模式时:this 不再指向第一个参数传入的 node ,而是指向选择器匹配的 node 。一旦你真正理解了,这个特性会非常有用。
Object Methods
dojo/on 的前身 dojo.connect 可以实现一个功能,就是 function-to-function 事件关联(译注:就是可以在函数间实现关联,一个函数调用,会触发关联函数的调用。用于实现AOP 编程,如记录日志等动作。)。这个功能已经分离出去,放入一个独立的模块 dojo/aspect 中。我们很快就会介绍到 dojo/aspect 了!
发布/订阅-Publish/Subscribe
截止目前以上所有例子都有一个特点,我们需要有一个现成的对象作为事件载体,产生事件以及处理事件。但如果某个节点上没有句柄或者根本不知道某个 object 是否已经创建出来了,该怎么办?这就要讲到 Dojo 的发布/订阅(pub/sub)框架了,在 Dojo 1.10 版本中这是由 dojo/topic 模块提供的。pub/sub 允许你为某个主题 "topic"注册一个 handler (就是订阅这个 topic),一旦这个 "topic"被发布,handler 就会被调用(topic 就是你为事件起的名字,是一个字符串,它可以有多个发布源)。
我们设想一个正在开发中的程序,我们需要些 buttons 用来提醒用户某个 action 发生了;对通知提醒响应的方法我们只希望编写一次,而且我们也不想仅仅为了给 button 添加个小方法就封装个 object。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>
这种事件模式有一个优点很实用,我们可以不用创建任何 DOM 对象,就可以 在单元测试中调用并测试 alerting routine (响应方法)了。事件处理方法,同事件的发生已经完全解耦了。
如果你要取消某个主题的关注, topic.subscribe 函数的返回值是个对象,带有一个 remove 方法,调用它就可以删除这个 handler。
注意同 dojo.publish 不同,topic.publish 发布消息时带的参数不需要压入一个数组。
比如 topic.publish("someTopic", "foo", "bar") 同 dojo.publish("someTopic", ["foo", "bar"]) 就是等价的。
小结
Dojo 的事件系统非常强大,使用上又非常简单。on 方法还消除了不同浏览器间 DOM 事件的差异。Dojo 的 pub/sub 框架,dojo/topic提供给开发者一个手段,可以将事件处理器和事件发器生轻松解耦。花时间好好熟悉这些工具,这对开发 web 应用是非常值得的。
阅读(4106) | 评论(3) | 转发(0) |