Practical .NET
ASP.NET: Managing DOM Events Interoperably
Peter Vogel introduces you to the new dynamic event model for JavaScript that's available in all the contemporary browsers.
While all the buzz is around the new elements in HTML5 (and when they will be supported in the various models), the new DOM event model hasn't gotten as much attention, which is too bad because it's implemented and ready to use in all the contemporary browsers. The new model provides a flexible way to attach events to objects/elements in the page while cleanly separating the code for wiring up events from HTML. The new model also makes it very easy to define, fire and catch your own custom events.
In this new world, you don't "fire" or "raise" events—you "dispatch" them. To process events you add a listener to an element. When an event is dispatched it propagates up through the tree of elements in the page, triggering any listeners on the elements it passes through.
For this example, I'll use a simple example of a table and a button, nested within a div element (which, in turn, is nested within the DOM's document and window objects):
<div id="MyDiv">
<table id="MyTable">
..table definition
</table>
<input id="MyButton" type="button" value="Post Data">
</div>
In real life, I might want to capture events for individual cells or rows in the table but, for clarity's sake, I'll just assume that I want to capture the click event that's fired when the user clicks anywhere in the table.
Processing Default Events
Since the table will, all by itself, fire a click event, my first step is to tie an event listener to the table element to listen for click events. The jQuery code to find the element with an id attribute of MyTable and tie a function called processTable to an event called 'click' looks like this (I'll come back to the third parameter being passed to processTable shortly):
$("#MyTable").each(function () {
this.addEventListener("click", processTable, false);
});
Of course, I could have tied a click event to every row by using $("tr") as my jQuery selector.
The processTable function that, in this example, will be called when the click event for the table is dispatched can accept a single parameter (that parameter is automatically provided by the DOM event model). My function would look like this:
function processTable(e) {
…process table click event…
}
The parameter that's passed contains a wealth of information. Its target property, for instance, gives you access to the element that fired the event.
However, the event doesn't stop moving up through the elements in the page just because a listener processes it. This means I could have tied my click event listener to the div element and still process the click event fired by the table. That listener would also catch the click event fired by the button within the div element. That design would make sense if the processing for the two events was similar (and, if there were differences, I could check the e.Target.id property to determine whether it was the button or the table that had fired the click event).
You can manage that propagation process in two ways. First, when you add an event listener, you can specify that this listener is to get the event before any other listener by passing true in the addEventListener's third parameter. This code, for instance, adds an event listener to the document but specifies that all click events from any element in the page are to go to this listener first before continuing up through the normal propagation path:
document.addEventListener("click", helloDiv, true);
Within an event listener, I can prevent an event from propagating up the tree by calling the stopPropagation method on the parameter passed to the function (this method's name may change to stopImmediatePropagation when the specification is finalized—IE 9 supports both names). This example prevents any subsequent listener seeing the event:
function processTable(e) {
…process table click event…
e.stopPropagation();
}
Dispatching Your Own Events
If you want to dispatch your own events from within your functions, first define your event using the document object's createEvent method. When you create an event, you specify the event model you want to use. This controls, for instance, what properties will appear on the parameter passed to the function that handles the event. There are several libraries including one dedicated to user interface event (UIEvents). In this example, I'm using the standard Event library:
var e = document.createEvent("Event");
After you've created an event, you need to give it a name and specify whether it can bubble up or be cancelled. In this example, I'm specifying that the event won't bubble but can be cancelled:
e.initEvent("MyEvent", false, true);
Adding a listener for this event would look like this:
window.addEventListener("MyEvent", processMyEvent, false);
Finally, to dispatch this event from within your code, you call the dispatchEvent on any element:
.dispatchEvent(evt);
These are the key functions in the new event model (there's more, of course). What's most attractive to me about the model is how consistent it is among the contemporary browsers -- very good news for Web developers.
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.