There are several ways to add events to elements on a web page but the unobtrusive one is the recomended among others, wich leads to incompatibilities between mainstrem browsers. This article shows the way to overcome those difficulties.
- Notice
- This article was originally posted as my entry to PPK’s addEvent() recoding contest. The final winner (John Resig) was announced in this entry. Click here to open the sample page with my implementation.
The problem
If you want to addere to current standars in web design, you must have heard of “unobtrusive JavaScript”, which basically means to incorporate all your code in external files and that your page will work in environments where javascript is disabled (of course if much more than this, but I’ve tried to simplify). This leads to a problem: how do you attach your code to the events you want? The traditional approach is something like this:
obj.onclick = myfunction
Althought it works, it has a serious drawback: if you need to attach more than a function to the same event/object pair, you have to implement your own mechanism. To overcome this problem, the W3C DOM defines the function addEventListener() wich just does what we need but… Internet Explorer implements a different function attachEvent(), that its similar but not the same and give us another bag of problems. One o the solutions to this problem was given by Scott Andrew LePera becoming a “classic” workaround to the problem, but as Peter-Paul Koch described in his blog, it isn’t perfect.
So here is my solution: a pair of functions (addEvent/removeEvent) that gives addEventListener() similar functionality to IE. The rest (Opera, Firefox/Mozilla, Safari and other W3C DOM compatible browsers) will still use the originaladdEventListener() function. The code has been tested succesfully in Explorer 5/5.5/6, Mozilla 1.6, Firefox 1.1 and Opera 8. As I don’t have possibilities of getting a Mac for testing, I must asume it may work in Safari, as the code is just a path for IE.
Code using the traditional model for event handling won’t be afected by the use of these functions, and old browsers which don’t have support for addEventListener() or attachEvent() will be unsuported. Using these functions, you can only catch events in the bubbling phase of the event life cycle, but this is the common way of doing things.
Features supported:
- the attached functions get the correct
thisbehavior - the
addEvent()function is able to set several event handlers for the same event on the same element - the
removeEvent()function is able to remove one event handler and leave the others intact - event handlers can only be attached once per event/object pair
- the functions takes care of IE memory leaks
The code
var r = false;
if (obj.addEventListener){
obj.addEventListener(evType, fn, false);
r = true;
}
else if (obj.attachEvent) {
var id = obj.sourceIndex || -1;
if (!fn[evType + id]) {
var f = fn[evType + id] = function(e) {
var o = document.all[id] || document;
o._f = fn;
var s = o._f(e);
o._f = null;
return s;
};
r = obj.attachEvent("on" + evType, f);
obj = null;
}
}
return r;
};
function removeEvent(obj, evType, fn){
var r = false
if (obj.removeEventListener){
obj.removeEventListener(evType, fn, false);
r = true;
} else if (obj.detachEvent) {
r = obj.detachEvent("on" + evType, fn[evType + (obj.sourceIndex || -1)]);
}
return r;
};
Before explaining the inner details of the functions above, you must be aware that in some circunstances, Internet Explorer may leak memory when using closures. This code takes care of these issues, but if you feel unfamiliar with these concepts you can read more about then in this article by Mishoo. A modified version of his test page using the functions above can be found here.
How it works - Overview
Instead of replacing IE’s attachEvent() we will use it in a special way: we’ll pass it a closure as the function that will handle the event. This closure will in turn call the actual handler passed to addEvent() when the event is fired.
To have an index to pass later to removeEvent() (and to prevent duplicate funcion assignements), we assing to the actual fuction a new property(remember: functions are objects) that will identify it for the object/event pair being processed. To get this index, we beat IE with their own weapons: we use its propietary sourceIndex property, which holds the index of the object in the also IE propietary collection document.all.
Finally, removeEvent() can do its job by finding in the function the property mentioned above that matchs the even/object pair being processed.
How it works - The details
Why do we have to use document.all/sourceIndex? Answer: to prevent IE memory leaks. If you take a look at the closure, it only retrieves the object and then apply the function to the object (BTW: As it has to be compatible with IE5.0, we can’t use Function.apply()). We could have used obj rigth into the closure, but if you look ahead in the code, obj is deleted to prevent the memory leak, so when the closure gets called later obj==null and the function fails. The solutions to this are:
- a) forget about memory leaks, don’t nullify obj at the end of the code and use it in the closure
-
b) Build an array that maps objects
uniqueId’s with thenselves
For me, the right solution is b but wait, in IE there’s such a map done yet! So, instead of building an array we’ll use IE’s one: document.all. The only drawback I’ve found is that document and window are not mapped, but with a little work this can also be overcomed, as the code shows. The advantage of this approach is that you don’t need to attach to window.unload in order to nullify the handlers in the objects as other implementations do.
Once we have our closure ready, we assign it to the function being passed to addEvent() as a property. The name of the property is formed by the event name and the object sourceIndex. This name mapping has two consequences:
- for every object/event/function, only one function is alowed, thus preventing repeated functions. This doesn’t mean that we can’t have diferent handlers for the same event, only that those handlers must be unique.
- the funtion will hold as many properties as objects use it, which meas that the same handler can be used in diferent events for the same object.
The work left for removeEvent() is really easy: it only has to find the index of the object and remove the property from the function. That’s it!
The rest of the code is self-explaining, but if someone need more details about it, just drop a comment!
Licensing
This code can be used for free in personal or comercial sites as long as you keep a comment in the code giving me the credit and a link to this page, something like this:
// http://www.ismaelj.com/articulos/addevent-recoding-contest/
Hola a todos, me parece muy interesante el articulo, es bastante util pero no consigo
pasar el objeto a la funcion manejadora del evento con attachEvent, hay os dejo el codigo por si le quereis echar un vistazo:
function addEvent(obj,evType,evHandler){
if(obj.addEventListener){
obj.addEventListener(evType,evHandler,false)
return true
}
else if(obj.attachEvent){
var r = obj.attachEvent(”on” + evType,evHandler)
return r;
}
else{
return false;
}
}
function prueba(e){
this.id //Funciona en mozilla…
//Aqui no consigo hacer referencia al objeto
}
var my_obj = document.getElementById(”my_id”)
addEvent(my_obj,’onmouseover’,prueba);
Estaria muy agradecido si me pudieseis ayudar gracias.
Precisamente ese es unos de los problemas a los que te enfrentas usando el codigo que muestras y una de las motivaciones de este artículo, que propone una implementación alternativa de la función addEvent
ok muchas gracias
Hi, attaching an onclick to a form button won’t correctly “return false” to prevent the form submitting……what would you do to achieve this using addEvent() ?
thx
That’s simple: return “false” in the function you pass to
addEvent. The implementationt ofaddEventcan’t return a default value either true or false because is up to the developer to define the behavior of each event.