Order of Events
Published Tuesday, February 21, 2006 by Роман Рахман.I use prototype.js as many other developers in the world do.
I guess one of the most useful objects in this framework is Event. It’s a really simple way for observing events without any thoughts about difference between Event Model in Internet Explorer and "Truth Nice Browsers" (like Firefox).
Event.observe(window, "load", myLoadHandler); |
Dead easy!
But there’s a thing which drives me mad sometimes. It’s the order of event firing. And it’s not a problem of Prototype framework, but problem of Internet Explorer Event Model.
Just a small example (don't forget to include prototype.js):var count = 2; |
for(var i= 1 ; i<=count; i++) { |
var handler = new Function('alert(' + i + ')'); |
Event.observe( window , 'load' , handler); |
} |
What order of alerts do you see in Firefox? Bingo! 1, 2
In Internet Explorer? Huh, 2, 1
Firefox? Surely 1, 2, 3
IE? 3, 2, 1? Sorry, actually not! 2, 3, 1. Why? Don’t ask me, please.
Interested? Let’s continue with count = 4
Firefox: 1, 2, 3, 4. If we had got another result I would have eaten my VisiBone
Javascript card
And extra-weird in IE: 2, 4, 3, 1
This software makes me more and more unhappy.
BTW, I checked this code in IE 6.0 (Window 2000) and IE 7 b2 (Windows XP).
As you can suspect, the result was the same.
I think it’d be very nice if Sam Stephenson included algorithm which will correct this IE bug to prototype Event object I mean Event.observe() shouldn’t add event handler directly by calling .addEventListener() or .attachEvent() but store handlers in internal hash and call them in right order.
Roman, I found your post in the middle of Dustin Diaz "Forget About addEvent()" column. And found it intersting immediately...
I found it too late, because I already submitted a bug report to Yahoo about they also using attachEvent and then loose the oreder of event firing.
I am glad somebody else is looking at this problem because I believe is a very serious matter in building Webkits like Google, Yahoo and others are doing.
I haven't tryed Prototype event handling yet, but if you say it uses attachEvent I already know the result.
Event randomly ordered (!?!).
I have been looking to PPK, Scot Andrews and Dean Edwards addEvent() talks and the addEvent recoding but the actual solution, also if reordering events works well, fails if you have different frames with events in the same document so we gain something but loose something else,
I am also looking to a final solution to this debate and if I have time next week I will dig again in the code and see what it will end up with a patch I had for the iframes problem in Dean Edwards attachEvent which passed unnoticed but had it working once...
Well if you are available and would like to discuss this and you have test cases for the ordering or the multiframes environment I would like to test them on
my events environment and share my thought.
Diego
Thanx Diego for your comment.
BTW I'm researching Dojo Toolkit now (http://dojotoolkit.org/) and there isn't this problem here.
You can write
dojo.event.connect(window, 'onload', handler);
and have the same order in IE and FF
Roman,
you may be interested into read this blog about "Cross-Window" event:
http://www.outofhanwell.com/blog/
the test case you will find there will help spot the problem with IE and IFRAMES I talked above.
Dojo also suffer from this problem as many other event wrappers. Prototype (up to 1.4) doesn not have this problem since they use attachEvent() but then loose the event reordering as do YUI.
Diego
I've also noticed this and fixed the calling order with my addEvent (though it uses attachEvent):
http://en.design-noir.de/webdev/JS/addEvent/
Dao
Dao,
the event reordering is just one part of the problem, but I see that you have a nice solution, however it will not be adequate if one need to add/remove events continuously since your array of listeners will grow continuously, and the loops will take forever to go through all the empty elements of these array. The "delete" part you have in your code is not enough for these kind of situations.
However for the majority of the case your addEvent will be more than sufficient since you correct both the order and the scope for the "this" keyword.
There are also good news on Dean site for the "onload" problem. A new property appeared for IE that will come handy, it is the "document.activeElement" and related events.
Diego
Diego, thanks for taking a look at my solution. The problem is that I cannot simply cut the array, because it would break the Event._callListeners loop in this case:
function x() { removeEvent (window, 'load', x) }
function y() { /*whatever*/ }
addEvent (window, 'load', x);
addEvent (window, 'load', y);
Simplified, this is what would happen:
// add the listeners
var stack = [x,y]
// enter the loop
var i = 0
// access listener 0
typeof stack[i] // "function"
// remove listener 0
stack.splice(0,1) // stack: [y]
// continue
i++
// access listener 1
typeof stack[i] // "undefined"
If you know a way to handle this, let me know. :)
Okay, I think I got it ... new version online. I just hope there are no cases I didn't think of.
Thanks again for your feedback.
Dao,
I haven't got the time to look againg in to your code however here is my tip or at least how I do it:
- use a Hash function to add and remove objects and mantain its length by incrementing/decrementing.
Objects properties can be removed with delete, arrays items cannot be removed the same way.
Diego