Event delegation without a JavaScript library

August 9th, 2007

Most of the articles and examples I’ve seen of handling events with event delegation use some kind of JavaScript library. Chris Heilmann’s much cited article uses the YUI library, and Dan Webb‘s presentation at @media last month used the Prototype framework.

For those of us just building a bog standard JavaScript application without one of these fancy libraries, it’s worth taking a look at how event delegation works in the real world. It sounds more complicated than handling events in the standard way, but it really isn’t.

For those that haven’t come across the term before, event delegation refers to the technique of reducing the number of event listeners attached to the document by attaching just one listener to a containing element and testing in the handler where that event has bubbled up from.

Let me give you an example from the Multimap website. The main navigation on the site includes 6 links across the top, 4 of which require event handlers to alter the href attribute of the link when it is clicked. These 4 links have a class attribute of ‘bundle’.

Traditionally you might approach that situation in the following way.


var MMNav = {
init: function() {
var nav = document.getElementById('mainNav');
var links = nav.getElementsByTagName('a');
for ( var i = 0, j = links.length; i < j; ++i ) {
if ( links[i].className == 'bundle' ) {
links[i].onclick = this.onclick;
}
}
},
onclick: function() {
this.href = this.href + '?name=value;
return true;
}
}

There's a lot of overhead in the above code. First of all the getElementsByTagName loops through every DOM node in the mainNav element to find all the links. We then have to loop through them again manually to check the class names of the links. That's wasted CPU cycles at every stage.

How about if we could just attach one event listener to the mainNav element and then capture any click on the links within that?


var MMNav = {
init: function() {
var nav = document.getElementById('mainNav');
nav.onclick = this.onclick;
},
onclick: function(e) {
if ( e.target.className == 'bundle' ) {
e.target.href = e.target.href + '?name=value';
}
return true;
}
}

The simplicity and elegance of this should be pretty clear, but it has a number of performance benefits as well:

There’s one caveat to the above code. Getting the target element of the event is not always as simple as using e.target. In Internet Explorer you need to use e.srcElement. The easiest way of dealing with this is to use a small getEventTarget function. Below is what I use.


function getEventTarget(e) {
var e = e || window.event;
var targ = e.target || e.srcElement;
if (targ.nodeType == 3) { // defeat Safari bug
targ = targ.parentNode;
}
return targ;
}

Event delegation is a fairly well established practice when dealing with large numbers of event handlers (eg, a map with hundreds of points, all with click events attached), but in my opinion it should be the default way you go about adding and handling events. It’s a simpler, more intuitive, and more optimized way of dealing with a common pattern in client-side scripting, and it doesn’t require thousands of lines of a JavaScript library for it to be useful to you.

You can subscribe to my blog or follow me on Twitter.

View archives