Notes and examples on the Dojo event system, by Dylan Schiemann
Purpose
The Dojo event system is designed to provide a unified event system for both DOM events and programmatic events, with an AOP (aspect oriented programming) style advice mechanism, without creating extra burden on developers who want to use the most common simple DOM event mechanism.
dojoAttachEvent
dojoAttachEvent allows you to specify DOM event handlers in a very concise manner:
dojo.webui.widgets.Foo = function() {
...
this.templateString = '<div dojoAttachPoint="divNode"
dojoAttachEvent="onClick; onMouseOver: onFoo;"></div>';
}
In other words, for a widget that contains the above templateString, there is a div node, that will listen for onclick and onmouseover DOM events. The onclick event will be routed to a handler called onClick for that widget, while the onmouseover event will be routed to handler onFoo:
dojo.webui.widgets.Foo = function() {
...
this.onClick = function(evt) {
// do something when clicking on the div for this widget
}
this.onFoo = function(evt) {
// do something else when mousing over the div for
// this widget
}
...
}
Note that onFoo and onClick are just arbitrary names of methods in the widget. The limitation of dojoAttachEvent is that it only currently works for binding from DOM events.
dojo.event.connect
dojo.event.connect is a mechanism for connecting a dom event or method call to another event handler or method call:
dojo.webui.widgets.Foo = function() {
...
dojo.event.connect(this.divNode, "onclick", this, "onClick");
dojo.event.connect(this, "onClick", this, "onFoo");
dojo.event.connect(widgetBar, "onBar", this, "onFoo");
...
}
The first connect method above does the same thing as the example above defined with dojoAttachEvent. The second one is more interesting, as it connects the onClick handler method to another method called onFoo. What this means is that it is possible to connect any two methods, and pass the arguments from the first method to the second one. The third example shows how you might connect any two method from any two objects.
Adding a New Handler "Before" Any Existing
You can force your new handler to run before previously installed ones using kwConnect. For example to install myFunction as a new onclick event handler for myObject, you could do:
dojo.event.kwConnect
({type: "before", srcObj: myObject, srcFunc: "onclick",
adviceFunc: myFunction});
Advice
dojo.event.connect is very useful, but what if the argument structure of the two methods do not match? This is where AOP style advice becomes useful. Advice allows intercession, i.e. it allows us to alter behavior at runtime.
dojo.webui.widgets.Foo = function() {
...
dojo.event.connect("after", this, "onClick", this,
"onHandleClick", this, "aroundObject");
this.aroundObject = function(mi) {
mi.args[0] = "foo";
if(mi.args[1]){
mi.args[1] = "bar";
}
mi.args[2] = "baz";
return mi.proceed();
}
this.onClick = function(evt) {
// dom something onClick
}
this.onHandleClick = function(arg0, arg1, arg2) {
// do something else, for example, maybe make a call
// to dojo.io.bind to make a POST request, or grab an
// arbitrary data fragment from the server
}
...
}
The advice object contains an arguments array that is prepoulated from the first functions arguments, and then rewrites the arguments after the first function is completed, before passing these arguments to the second object. Hence the term "after" for the first parameter, which specifies the type of advice.
Advice can also be used to intercept and modify the arguments of a method before it is called, and need not even be connected to a second method:
dojo.webui.widgets.Foo = function() {
...
dojo.event.connect("around", this, "onHandleClick",
this, "aroundObject");
// mi is an abbreviation for \MethodInvocation
this.aroundObject = function(mi) {
mi.args[0] = "foo";
if(mi.args[1]){
mi.args[1] = "bar";
}
mi.args[2] = "baz";
return mi.proceed();
}
this.onHandleClick = function(arg0, arg1, arg2) {
}
...
}
Dojo's advice system was based on BurstLib's MOP implemetation
dojo.event.kwConnect
dojo.event.kwConnect (the kw follows the standard Python convention for the phrase: key words) allows you to specify connect through an object literal rather than through a traditional method call. This is useful in the case of having a large number of parameters that would otherwise be null. For example, instead of :
dojo.event.connect("after", this, "onClick", this,
"onHandleClick", this, "aroundObject"):
... you would instead do something like:
dojo.event.kwConnect({
srcObj: this,
srcFunc: "onClick",
adviceObj: this,
adviceFunc: "onHandleClick",
aroundObj: this,
aroundFunc: "aroundObject",
adviceType: "after"
});
dojo.event.topic
Say you have a case where you have a number of widgets in a document, and you want them all to be able to listen for the same event, and also push events to the other widgets that are listening. With the normal dojo.event.connect, you would need to create a connection between each set of widgets manually. Instead, you just do the following:
// to send/publish an event to the topic
dojo.event.topic.publish("topicFoo", filterFoo);
// to listen/subscribe to all events published to a topic
dojo.event.topic.subscribe("topicFoo", this, "onFoo");
There is an additional example of this in the source tree at tests/event/test_topic.html. filterFoo in the example above is an object literal that is passed to onFoo.
disconnect and unsubscribe
To remove an event connection or a topic subscription, dojo.event.disconnect and dojo.event.topic.unsubscribe take exactly the same parameters as their counterparts.
event callbacks
People often ask questions like:
whenever buttonX is pressed, how do I call "saveHandler('x', evt)"?
This is a javascript closure question and is unrelated to dojo, but here's one way:
// createCallback() returns a function!
function createCallback(fieldId){
// a new fieldId variable is created every time this function
// is called, and it's saved by closure magic
return function(evt){ saveHandler(fieldId, evt); } ;
}
var fieldId='x';
var callbackFunc = createCallback(fieldId);
dojo.event.connect(dojo.widget.byId(fieldId), "onSave", callbackFunc );
Event object
dojo.event.browser.fixEvent(/* event object or nothing for IE*/)
can normalize event objects so you can use common event code in any browser. dojo.event.connect
uses fixEvent
automatically, so connected functions always get a normalized event object as an argument. Fixed event objects have these modifications:
For key events, a set of event key code aliases are installed, so you can express (e.keyCode == e.KEY_ESC)
. Also, a reverse key code lookup is installed, so you can express (e.revKeys[e.keyCode] == 'KEY_ESC')
.
These properties/methods are made available in all browsers:
target
currentTarget
pageX/pageY - position of cursor relative to viewport
layerX/layerY
fromElement
toElement
charCode
stopPropagation() - stops other event handlers (on parent domnodes) from firing
preventDefault() - stops things like following the href on a hyperlink.
callListener() - ???
Additionally, event
(W3) vs. window.event
(IE) is taken care of: all connected event handlers get passed a fixed event object (even in IE).