This is the original version of my entry to the addEvent() recoding contest; meanwhile I have created a new version that addresses some possible issues from this version. This page is for history purposes only.
addEvent() recoding contest entryThis is my entry for the addEvent() recoding contest on quirksmode.org. This entry afaik meets all the requirements. I even added some extra style to the example list, working around some other IE bugs as well :)
Basically this library consists of 3 functions: addEvent, removeEvent and a helper function for browsers that do not support the DOM level 2 methods addEventListener and removeEventListener (that would generally only be Internet Explorer so I named this function IEEventHandler).
To use this code, simply copy/paste the functions from below into your javascript and call these funtions as follows:
addEvent(object, eventType, function);
The 'object' argument should be a reference to an HTML-element on which you want to attach a handler for the event.
The 'eventType' argument should be the name of the event type (without the 'on' prefix), e.g. 'click', 'mouseover', 'load' etc.
The 'function' argument should be a reference to an existing function, or an anonymous function. The event object will be passed as an argument to this function.
To remove a listener simply call the removeEvent function as follows:
removeEvent(object, eventType, function);
The arguments to this function should exactly match the arguments you used for addEvent to attach the eventlistener.
These functions work around a number of issues that simular functions, usually wrappers around attachEvent, have. I don't use anonymous functions to prevent closures. I use my own function stack to match the execution order of addEventListener. I also check if a function is already registered as a handler for the event to prevent duplicate calls (also matching addEventListener's behavior). Also I added a workaround for IE5.0 that doesn't support the call or apply method on function objects. Last but not least I take into account the fact that some elements may already have a handler attached using inline javascript.
The code seems somewhat chunky, but that's mainly because of the comments I added. Feel free to remove these.
/**
* Crossbrowser event handling functions.
*
* A set of functions to easily attach and detach event handlers to HTML elements.
* These functions work around the shortcomings of the traditional method ( element.onevent = function; )
* where only 1 handler could be attached for a certain event on the object, and mimic the DOM level 2
* event methods addEventListener and removeEventListener for browsers that do not support these
* methods (e.g. Internet Explorer) without resorting to propriety methods such as attachEvent and detachEvent
* that have a whole set of their own shortcomings.
* Created as an entry for the 'contest' at quirksmode.org: http://www.quirksmode.org/blog/archives/2005/09/addevent_recodi.html
*
* @author Tino Zijdel ( crisp@xs4all.nl )
* @version 1.0
* @date 2005-09-09
*/
/**
* addEvent
*
* Generic function to attach event listeners to HTML elements.
* This function does NOT use attachEvent but creates an own stack of function references
* in the DOM space of the element. This prevents closures and therefor possible memory leaks.
* Also because of the way the function references are stored they will get executed in the
* same order as they where attached - matching the behavior of addEventListener.
*
* @param obj The object to which the event should be attached.
* @param evType The eventtype, eg. 'click', 'mousemove' etcetera.
* @param fn The function to be executed when the event fires.
* @param useCapture (optional) Whether to use event capturing, or event bubbling (default).
*/
function addEvent(obj, evType, fn, useCapture)
{
//-- Default to event bubbling
if (!useCapture) useCapture = false;
//-- DOM level 2 method
if (obj.addEventListener)
{
obj.addEventListener(evType, fn, useCapture);
}
else
{
//-- event capturing not supported
if (useCapture)
{
alert('This browser does not support event capturing!');
}
else
{
var evTypeRef = '__' + evType;
//-- create function stack in the DOM space of the element; seperate stacks for each event type
if (!obj[evTypeRef])
{
//-- create the stack if it doesn't exist yet
obj[evTypeRef] = [];
//-- if there is an inline event defined store it in the stack
var orgEvent = obj['on'+evType];
if (orgEvent) obj[evTypeRef][0] = orgEvent;
//-- attach helper function using the DOM level 0 method
obj['on'+evType] = IEEventHandler;
}
else
{
//-- check if handler is not already attached, don't attach the same function twice to match behavior of addEventListener
for (var ref in obj[evTypeRef])
{
if (obj[evTypeRef][ref] === fn) return;
}
}
//-- add reference to the function to the stack
obj[evTypeRef][obj[evTypeRef].length] = fn;
}
}
}
/**
* removeEvent
*
* Generic function to remove previously attached event listeners.
*
* @param obj The object to which the event listener was attached.
* @param evType The eventtype, eg. 'click', 'mousemove' etcetera.
* @param fn The listener function.
* @param useCapture (optional) Whether event capturing, or event bubbling (default) was used.
*/
function removeEvent(obj, evType, fn, useCapture)
{
//-- Default to event bubbling
if (!useCapture) useCapture = false;
//-- DOM level 2 method
if (obj.removeEventListener)
{
obj.removeEventListener(evType, fn, useCapture);
}
else
{
var evTypeRef = '__' + evType;
//-- Check if there is a stack of function references for this event type on the object
if (obj[evTypeRef])
{
//-- iterate through the stack
for (var ref in obj[evTypeRef])
{
//-- if function reference is found, remove it
if (obj[evTypeRef][ref] === fn)
{
try
{
delete obj[evTypeRef][ref];
}
catch(e)
{
obj[evTypeRef][ref] = null;
}
return;
}
}
}
}
}
/**
* IEEventHandler
*
* IE helper function to execute the attached handlers for events.
* Because of the way this helperfunction is attached to the object (using the DOM level 0 method)
* the 'this' keyword will correctely point to the element that the handler was defined on.
*
* @param e (optional) Event object, defaults to window.event object when not passed as argument (IE).
*/
function IEEventHandler(e)
{
e = e || window.event;
var evTypeRef = '__' + e.type;
//-- check if there is a custom function stack defined for this event type on the object
if (this[evTypeRef])
{
//-- iterate through the stack and execute each function in the scope of the object by using function.call
for (var ref in this[evTypeRef])
{
if (Function.call)
{
this[evTypeRef][ref].call(this, e);
}
else
{
//-- IE 5.0 doesn't support call or apply, so use this
this.__fn = this[evTypeRef][ref];
this.__fn(e);
this.__fn = null;
}
}
}
}
This is the original markup and menuhandling javascript of the contest page written bij PPK; I only changed the CSS to make it a more pretty example that behaves and looks pretty much the same in most browsers. This menu doesn't work perfectly and is only intended to demonstrate that the addEvent and removeEvent functions are working properly (here is an example of a better working menu, although this only uses the DOM level 0 event model).
Remove border/background effect.
If you have any questions regarding this page, please feel free to sent me an email.
September 9, 2005 - Tino Zijdel ( crisp@xs4all.nl )
Last changed: September 21, 2005 - naming and minor tweaks
October 19, 2005 - New version (1.1)