IE is Annoying!
I just spent the last little bit creating a Javascript EventManager to take care of event listeners cross browser. Using the traditional method of attaching listeners to events is just “okay”. There are a couple of big problems, the biggest being you cannot easily add more then one listener to a single event.
For example:
[code lang="javascript"]buttonElm.onclick = sayHello;
buttonElm.onclick = sayGoodbye;[/code]
Clicking that button would only fire the sayGoodbye function. The second assignment will overwrite the first. So to have both fire upon a click, you must create an anonymous function (aka closure aka inline function) that fires both of them:
[code lang="javascript"]buttonElm.onclick = sayHello;
// If we don’t know, we have to test
if(buttonElm.onclick)
{
var old_func = buttonElm.onclick;
// The new listener is a closure that
// fires both functions
buttonElm.onclick = function() { old_func(); sayGoodbye(); };
}
else
buttonElm.onclick = sayGoodbye;[/code]
Testing for a previous listener is annoying in itself, but creating a function to take care of that is easy as well…
[code lang="javascript"]function addEvent(elm, eventType, func)
{
if(elm[eventType])
{
var old_func = elm[eventType];
elm[eventType] = function() { old_func(); func(); }
}
else
elm[eventType] = func;
}
addEvent(buttonElm, ‘onclick’, sayHello); // onclick=sayHello();
addEvent(buttonElm, ‘onclick’, sayGoodbye); // onclick=sayHello(); sayGoodbye();[/code]
So what’s the problem with that? That works across browsers and is fairly simple. But, how do you remove listeners if you have more the one assigned? There is no way to simply remove one if it is part of a closure.
So the fix to that is to use methods part of the DOM2 spec, using addEventListener and removeEventListener is exactly what we need! But IE doesn’t follow the
W3C, so they have their own methods: attachEvent and detatchEvent. This would work wonderfully, all we’d have to do is test to see which methods to use; the W3C methods or the IE methods. That only works to an extent…
Usually when you attach an event to an object, this (the reference that points the “owner” of a function) will point to the object. For example:
[code lang="javascript"]buttonElm.onclick = changeBG;
function changeBG()
{
this.style.backgroundColor = ‘#EEEEFF’;
}[/code]
this points to the button, thus changing it’s background color. I could attach that event to any element and it would change it’s background color to blue because this always points to the “owner”. For most [all?] W3C-following browsers (Mozilla, Opera etc), the addEventListener copies the function over to the object so the this reference works how we expect it to (I say “most” because making that reference is not part of the W3C DOM2 spec, so it’s not required). IE with its attachEvent method does not copy the function over to the object, so this will always point to the window! This code in IE would be utterly useless:
[code lang="javascript"]buttonElm.attachEvent(’onclick’, changeBG);
function changeBG()
{
this.style.backgroundColor = ‘#EEEEFF’;
}[/code]
… because this doesn’t point to the button, like we’d expect.
Each event has certain “event information” that we can use, the type of the event for example. This information, according to the W3C, should be passed to the event listener. The W3C gives us two references that let us work with the objects firing events (which is why the this reference is not so much of a problem). Those two properties are target and currentTarget. For example, these two functions will do the exact same thing using Mozilla:
[code lang="javascript"]buttonElm.addEventListener(’click’, changeBG);
buttonElm.addEventListener(’onclick’, changeBG2);
function changeBG(e)
{
this.style.backgroundColor = ‘#EEEEFF’;
}
function changeBG(e)
{
obj = e.currentTarget;
obj.style.backgroundColor = ‘#EEEEFF’;
}[/code]
IE has one property for us called srcElement that acts the same as the W3C currentTarget. We could rewrite the functions like this to work in IE as well:
[code lang="javascript"]if(buttonElm.addEventListener) // W3C method
buttonElm.addEventListener(’click’, changeBG);
else // IE method
buttonElm.attachEvent(’onclick’, changeBG);
function changeBG(e)
{
if(e.currentTarget) // W3C method
obj = e.currentTarget;
else if(window.event.srcElement)
obj = window.event.srcElement; // IE method
else
return false;
obj.style.backgroundColor = ‘#EEEEFF’;
}[/code]
The problem comes with event bubbling. Event bubbling is when one event “bubbles” up to fire other events. For example, if we had a button situated inside of a div and both elements had click events, a click on the button would “bubble” up to fire the the click event on the div.
+-----------------+ | mydiv | | +----------+ | | | mybutotn | | | +----------+ | +-----------------+
Let’s try applying the same click event listener to the div:
[code lang="javascript"]// Div
if(divElm.addEventListener)
divElm.addEventListener(’click’, changeBG);
else
divElm.attachEvent(’onclick’, changeBG);
// Button
if(buttonElm.addEventListener)
buttonElm.addEventListener(’click’, changeBG);
else
buttonElm.attachEvent(’onclick’, changeBG);
function changeBG(e)
{
if(e.currentTarget)
obj = e.currentTarget;
else if(window.event.srcElement)
obj = window.event.srcElement;
else
return false;
obj.style.backgroundColor = ‘#EEEEFF’;
}[/code]
In Mozilla, this works as we’d expect: it turns the button background to blue, then bubbles through and turns the div background to blue. This is because of the currentTarget property, it is a reference to the current target. When the button click event was fired, the current target was the button, when the div click was fired, the current target was the div. IE’s srcElement is anagulous to the W3C target. That is, it always points to the object that started the chain of firing. If you try the above code in IE, it will only change the color of the button but not the div.
So the problem is that there is no way to get the object that the event is actually fired from with IE. The fix to that? We get right back to closures…
In Javascript we can call a function like myfunction.call(someobj) and it allows us to call that function in the context of another object. In other words, if we can fire the event listener function in the context of the object that it is attached to — we can once again use this!
[code lang="javascript"]// Div
if(divElm.addEventListener)
divElm.addEventListener(’click’, changeBG, false);
else
{
closure = function() { changeBG.call(divElm); };
divElm.attachEvent(’onclick’, closure);
}
// Button
if(buttonElm.addEventListener)
buttonElm.addEventListener(’click’, changeBG, false);
else
{
closure = function() { changeBG.call(buttonElm); };
buttonElm.attachEvent(’onclick’, closure);
}
function changeBG(e)
{
this.style.backgroundColor = ‘#EEEEFF’;
}[/code]
This way we can still take advantage of the this reference cross browser. This slightly complicates removing listeners because to remove a listener, you need to know the function you want to remove. While you might think the function you want to remove would be the changeBG function. That would work with W3C browsers, but with IE, we didn’t use the changeBG function, we used a closure. So to remove a listener, you’d need to keep track of all the closures you used. That isn’t as hard as it would seem, it was what I had been working on before I went on this little… er… rampage.
But on top of having to keep track of the closures you use, IE has problems with garbage collection with closures — so to prevent stack overflow errors, you have to detatch all event handlers on page unload!
So because IE not supporting the W3C:
- We have to use alternate functions to retain cross-browser accessibility
- We have to access different event objects to get event information to retain cross-browser accessibility
- We have to access different properties of the event objects to retain cross-browser accessibility
- We cannot access the object that fired the event if we require it during event bubbling
All of this made me try to create a custom event manager that “kinda works”. IE’s problems with memory management is breaking it when I try to create a closure. I’m not going to try and debug it tonight, if ever. I think I might just use generic functions (where simply srcElement is okay) and use the tradition model when/if I require event bubbling.
See what I’ve done so far HERE, maybe you can debug it and send me the result
Okay, thats it for tonight. I’ve typed that all up and haven’t read it over, or tried any of my code samples — so if it doesn’t make sense, you can just go and… just go away
… I have to make a new style tomorrow. I hate this one…