Javascript Events - The cross browser method.

This article was inspired by a question from a forum. One of the forum members was having issues getting a script that used events to work under IE. He was hampered a lot by the fact that he was using a cross browser addEvent script he'd grabbed from somewhere that was simply not cross browser.

The script he was using started in the right place, using object detection to determine whether the script supported addEventListener or attachEvent (The new IE and W3C event methods) and if neither appeared using the old style elem.oneventname method.

Close but no cigar. The issue that this has is that addEventListener and attachEvent both have slightly differently results and are therefore not interchangable. I won't get into the nitty gritty of why not, if you are interested you can read the great article at quirksmode called Introduction to Events. The short version is that if you use attachEvent you can't use the this reference in your handler functions and with addEventListener you have no guarantee as to the order the event handlers will be called.

The Solution

To solve this problem I decided to write my own addEvent function that would solve both these problems. Handlers would be executed in the order they are added and the this reference would work from inside the hander functions.

While I was at it I decided it would be a good idea to also address another browser difference. Under IE you get at the event object via the global event object while in other browsers the event is passed to the function as the 1st parameter. To make it easier to write cross browser event handlers the addEvent function ensures that the event object is passed as the 1st parameter under all browsers. It also adds the two W3C event methods (preventDefault & stopPropagation) for controlling how the event will be handled after the current handler has finished with it.

How to use it

The solution I came up with uses 3 functions. 1 function to add new event handlers to an object (addEvent), another to remove event handlers (removeEvent) and another function which handles the calling of the event handlers when an event reaches the object (handleEvent).

You can download the event handler functions and include them in any of your pages to expose these functions. Once included, you can add event handlers using the addEvent function. This function takes 3 parameters.

  1. elem - The element the handler will be added to.
  2. eventType - The event that will be handled (e.g. click, mousedown, etc..)
  3. handler - The function that will be called for the event.

An example of this function in use can be seen in this page. A handler is added to the window object so that an init function will be called when the page is loaded by the following line of code.

addEvent(window, 'load', init);

To remove a handler from an element use the removeEvent function. This function also takes 3 parameters.

  1. elem - The element the handler will be added to.
  2. eventType - The event that will be handled (e.g. click, mousedown, etc..)
  3. handler - The function that will be called for the event.

Have a look at the javascript used on this this page.

var outputDiv = null; function init() { outputDiv = document.getElementById("outputDiv"); addEvent(outputDiv, "click", handler1); addEvent(outputDiv, "mousedown", handler1); addEvent(outputDiv, "click", handler2); addEvent(outputDiv, "mousedown", handler2); addEvent(outputDiv, "mousedown", handler2); addEvent(outputDiv, "click", handler3); removeEvent(outputDiv, "click", handler1); } function handler1(e) { outputDiv.innerHTML += "Event Handler 1 Fired : e.type = '" + e.type + "', this.id = '" + this.id + "'<br />"; } function handler2(e) { outputDiv.innerHTML += "Event Handler 2 Fired : e.type = '" + e.type + "', this.id = '" + this.id + "'<br />"; } function handler3(e) { outputDiv.innerHTML += "Event Handler 3 Fired : e.type = '" + e.type + "', this.id = '" + this.id + "'<br />"; } addEvent(window, 'load', init);

As mentioned earlier, the init function is called on page load because we added a handler for the load event to the window. The init function then adds a number of handlers to the div with id "outputDiv" for click or mousedown events.

The handler functions are very simple, they just output which handler they are, the event type and the id of the this variable. They demonstrate that e contains a reference to the event and that this refers to the element the handler is registered for.

You should also notice that the init function adds handler1, handler2 and handler3 as handlers for the click event and then removes handler1. This leaves handler2 & handler3 as click handlers and leaves the mousedown handlers unaffected.

These handlers are all added to the div below. Click the mouse button inside the div to see the handlers in action.

Output Div : Click the mouse in here to see some handlers in action.

The Script

Here are the three functions that are doing all the work for us.

/** Written by Peter Wilkinson of http://dynamic-tools.net Feel free to use or modify this script for any purpose. I'd appreciate you leaving this header in though. */ function addEvent(elem, eventType, handler) { if (!elem.eventHandlers) elem.eventHandlers = []; if (!elem.eventHandlers[eventType]) { elem.eventHandlers[eventType] = []; if (elem['on' + eventType]) elem.eventHandlers[eventType].push(elem['on' + eventType]); elem['on' + eventType] = handleEvent; } elem.eventHandlers[eventType].push(handler); } function removeEvent(elem, eventType, handler) { var handlers = elem.eventHandlers[eventType]; for (var i in handlers) if (handlers[i] == handler) delete handlers[i]; } function handleEvent(e) { var returnValue = true; if (!e) e = fixEvent(event); var handlers = this.eventHandlers[e.type] for (var i in handlers) { this.$$handleEvent = handlers[i]; returnValue = !((returnValue && this.$$handleEvent(e)) === false); } return returnValue; } function fixEvent(event) { // add W3C standard event methods event.preventDefault = fixEvent.preventDefault; event.stopPropagation = fixEvent.stopPropagation; return event; }; fixEvent.preventDefault = function() { this.returnValue = false; }; fixEvent.stopPropagation = function() { this.cancelBubble = true; };