Events are one aspect of the Document Object Model (DOM) that are highly inconsistent across browsers. Back in the early days of the Web, Netscape introduced one model of attaching event listeners to the document. Unsurprisingly, Microsoft chose to implement events in a completely different way in Internet Explorer. When the W3C came along to standardize the process, this only served to muddy the waters even further by introducing a third approach.
Without a toolkit, the developer is left to work around these inconsistencies themselves. Event handling code without a JavaScript toolkit tends to look something like this:
/*
written by Dean Edwards, 2005
with input from Tino Zijdel - crisp@xs4all.nl
http://dean.edwards.name/weblog/2005/10/add-event/
*/
function addEvent(element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else {
if (!handler.$$guid) handler.$$guid = addEvent.guid++;
if (!element.events) element.events = {};
var handlers = element.events[type];
if (!handlers) {
handlers = element.events[type] = {};
if (element['on' + type]) handlers[0] = element['on' + type];
element['on' + type] = handleEvent;
}
handlers[handler.$$guid] = handler;
}
}
addEvent.guid = 1;
function removeEvent(element, type, handler) {
if (element.removeEventListener) {
element.removeEventListener(type, handler, false);
} else if {(element.events && element.events[type] && handler.$$guid)
delete element.events[type][handler.$$guid];
}
}
function handleEvent(event) {
event = event || fixEvent(window.event);
var returnValue = true;
var handlers = this.events[event.type];
for (var i in handlers) {
if (!Object.prototype[i]) {
this.$$handler = handlers[i];
if (this.$$handler(event) === false) returnValue = false;
}
}
if (this.$$handler) this.$$handler = null;
return returnValue;
}
function fixEvent(event) {
event.preventDefault = fixEvent.preventDefault;
event.stopPropagation = fixEvent.stopPropagation;
return event;
}
fixEvent.preventDefault = function() {
this.returnValue = false;
}
fixEvent.stopPropagation = function() {
this.cancelBubble = true;
}
Assuming we have markup like this...
<ol id="veggies">
<li>Carrots</li>
<li>Zucchini</li>
<li>Squash</li>
</ol>
...and we want to bind focus handlers to each item in the list, the code using our hand-rolled toolkit would look something like this:
var listOfVeggies = document.getElementById("veggies");
var veggieNodes = listOfVeggies.getElementsByTagName("li");
for (var i = 0; i < veggieNodes.length; i++) {
addEvent(veggieNodes[i], "onclick", function (evt) {
eatVegetable(evt.target);
});
}
jQuery provides a simple and succinct mechanism for attaching events to elements. Since we're reusing an existing toolkit, we can get rid of all the event handler workaround code from the first example. The jQuery code to actually bind event handlers to each item in the list looks like this:
jQuery("#veggies > li").focus(function (evt) {
eatVegetable(evt.target);
});
Just counting the code to actually bind the event handlers, that's a line-for-line savings of nearly 70%, and the latter form is significantly more readable.
1 Comment
Eli Cochran
In the interest of full disclosure, if you're not using a Javascript toolkit there is actually a slightly different (and we think better) version of this event handling code. We've referenced the Tino Zijdel version to clearly illustrate working around browser inconsistencies. The latest Dean Edwards version avoids the browser issue by ignoring it and attaching events using another method entirely. You can find it here: http://dean.edwards.name/weblog/2005/10/add-event/ and in John Resig's wonderful book Pro Javascript Techniques.