PDA

View Full Version : order of event execution in IE



pman
06-03-2007, 11:08 PM
Hi,

I think this is one of the issues, on which IE decided to be unique. Here's my problem.

I already have an object that has a click event to do something. I'm using that object in another work of mine, where I needed to do some calculation, when that object was clicked.

So, I added another click event to that object. Now, the object has two click events. Both Firefox and Opera executes the click events in the order they were added. So, the first click event, which was the original event created by the object itself, gets called first and then the second click event, that I needed to add for this particular work, gets called first. This is working fine for me.

But in IE, it's working in LIFO order (last event added gets called first). Because of this I'm getting a totally wrong calculated value.

I don't know if there's any way to fix it, as I couldn't find it while googling. I really hope I'm wrong and may be someone could show me how to get around this. Thank you so much for helping.

Trinithis
06-04-2007, 12:39 AM
If you are adding the events all in one sitting, you could try something like this:


if(window.addEventListener) {
el.addEventListener("click", func1, false);
el.addEventListener("click", func2, false);
}
else{
el.attachEvent("onclick", func2);
el.attachEvent("onclick", func1);
}

But I would guess that this is less than ideal. I can try think tanking a function that adds events and automatically does this for you when adding events later in the code.

Oh, and you are not mistaken. I just tested it too, confirming your worst NIGHTMARE!

Trinithis
06-04-2007, 01:06 AM
This works in FF and IE:


//type has no "on" in it, and bool is optional
function addEvent(el, type, func, bool) {
if(window.addEventListener) el.addEventListener(type, func, !!bool);
else {
type = "on" + type;
if(el.events===undefined) el.events = [];
for(var e=el.events, i=e.length-1; i!=-1; --i) {
el.detachEvent(e[i][0], e[i][1]);
}
el.events.push([type, func]);
for(var e=el.events, i=e.length-1; i!=-1; --i) {
el.attachEvent(e[i][0], e[i][1]);
}
}
}

pman
06-04-2007, 03:00 AM
Oh, and you are not mistaken. I just tested it too, confirming your worst NIGHTMARE!


LOL... Tell me about it :( . I did knew about this before, but never thought I would need to use multiple events of the same type on the same object.

Thanks for the last post though. Could you explain the code a little? Not sure why you have a detach event inside an addEvent function. The for loop looks wired too.

Thanks again.

Trinithis
06-04-2007, 04:07 AM
First, the usage syntax of the code is something like this:


var element = document.getElementById("myID");
function ABCs() {
alert("Now I know my ABC's!!!");
}
addEvent(element, "click", ABCs)
//Or if you want to capture the event with non-IE browsers: addEvent(element, "click", ABCs, true);

As for the code itself, I'll try walking you through it with comments:


function addEvent(el, type, func, bool) {
if(window.addEventListener) el.addEventListener(type, func, !!bool);
//Checks to see if the method of adding events is through addEventListener or attachEvent.
//The !!bool is to turn bool into false if undefined or false, or into true if set to true.
//Note that I don't have to do fancy legwork with reordering events here, as it is already FIFO.
else {
//IE way of adding events and making LIFO into FIFO
type = "on" + type;
//IE wants an argument like "onclick" vs "click"
if(el.events===undefined) el.events = [];
//Gives the element a property to record what events have been assigned to it because you can't tell otherwise (or at least I don't think so).
for(var e=el.events, i=e.length-1; i!=-1; --i) {
//Looks through the element's event property that I had created and sets a loop to get rid of all its events.
//Note that this loop could easily look like for(var i=0, e=el.events; i<e.length; i++) and do the same thing.
//Also, if this is element currently has no events, this is bypassed.
el.detachEvent(e[i][0], e[i][1]);
//(Remember e=el.event.) e is an array that has length equal to the number of events the element has. e[i] stores the event type (e[i][0]) and the function (e[i][1]), so el.detachEvent() can remove the event.
//The event is removed in the first place to restack the event pile later in the code by readding them in the "correct" order.
}
el.events.push([type, func]);
//Records the new event into el.events
for(var e=el.events, i=e.length-1; i!=-1; --i) {
//With the previous loop it did not matter whether it incremented or decremented. In this case, it has to decrement because we have to reverse LIFO into FIFO
el.attachEvent(e[i][0], e[i][1]);
//Adds the events in "reverse" order to make it FIFO
}
}
}

Trinithis
06-04-2007, 04:35 AM
I have an edit to make with my addEvent to make it mimic addEventListener even better.


function addEvent(el, type, func, bool) {
if(window.addEventListener) el.addEventListener(type, func, !!bool);
else {
type = "on" + type;
var hasEvent = false;
if(el.events===undefined) el.events = [];
for(var e=el.events, i=e.length-1; i!=-1; --i) {
el.detachEvent(e[i][0], e[i][1]);
if(e[i][0]==type && e[i][1]==func) hasEvent = true;
}
if(!hasEvent) el.events.push([type, func]);
for(var e=el.events, i=e.length-1; i!=-1; --i) {
el.attachEvent(e[i][0], e[i][1]);
}
}
}


Oh, and you will need this to remove events in a fashion so that addEvent won't provide bugs.


function removeEvent(el, type, func, bool) {
if(window.addEventListener) el.removeEventListener(type, func, !!bool);
else if(el.events) {
type = "on" + type;
for(var e=el.events, i=e.length-1; i!=-1; --i) {
if(e[i][0]==type && e[i][1]==func) {
el.detachEvent(type, func);
el.events.splice(i, 1);
break;
}
}
}
}

pman
06-05-2007, 01:50 AM
Hey Trinithis,

I just tried the last addEvent function that you posted, but it still seems to be doing the same thing in IE. Executing events in LIFO order

pman
06-05-2007, 02:04 AM
Okay. Never mind. It's working now. It didn't work before because I wasn't using the actual "addEvent" function. doh...:rolleyes:

Anyways... In the mean time while I didn't realize why that code wasn't working, I started to write my own, which was following the exact same algorithm and this one is working too. There's not much difference between mine and Trinithis except that I'm revarsing the array of events and then re-adding them. Here's mine:



this.addEvent = function(eventTarget, eventType, eventHandler, eventBubble)
{
if(eventTarget.addEventListener)
{
//for true web browsers
eventTarget.addEventListener(eventType, eventHandler, eventBubble) ;
}
else
{
//for IE

eventType = "on" + eventType ;

if(!eventTarget.events)
{
/**
* No events was registered for this object.
* Create an array to store the events
*/
eventTarget.events = new Array() ;
}
else
{
/**
* Events were registered on this object before.
* Detach them now, so that we can sort the event
* order after registereing the new event.
*/
for(var i = 0 ; i < eventTarget.events.length ; i++)
{
eventTarget.detachEvent(eventTarget.events[i][0], eventTarget.events[i][1]) ;
}
}

//store the new event in the array
eventTarget.events.push([eventType, eventHandler]) ;

//Copy the event array and revarse the order
var tempEventList ;
tempEventList = eventTarget.events ;
tempEventList.reverse() ;

//Register the events from the reversed order array
for(var i = 0 ; i < tempEventList.length ; i++)
{
eventTarget.attachEvent(tempEventList[i][0], tempEventList[i][1]) ;
}
}

}



I know mine is working too, but do you see anything wrong with mine in terms of the performance? Thanks again for your code Trinithis.

Trinithis
06-05-2007, 06:13 AM
eventBubble would be more aptly named eventCapture. But then again, it might be best removing that whole paramater and default it to false because I don't know how to mimic it in IE, meaning that if you wanted to capture the event, things would behave differently for IE users. That is, unless right after you add the event, you have a condition check for (window.addEvent) and do your magic there.

Unless you have a special reason to, I would use


function addEvent()

over


this.addEvent = function() //or window.addEvent = function()

As for your performance specs it adds a miniscule (though negligible) time to execution. My code runs neglibly faster than yours (without checking to see if the event is already attached. I check because the code would otherwise fail if you attach a duplicate event in IE. I think yours would fail too. FF handles it automatically.), and I revamped it to make it even more neglibly faster just for you :D



function addEvent(el, type, func, bool) {
if(el.addEventListener) el.addEventListener(type, func, bool);
else {
type = "on" + type;
if(!el.events) el.events = [];
var i = 0, e = el.events, len = e.length, unique = true;
while(i<len) {
el.detachEvent(e[i][0], e[i][1]);
if(e[i][0]===type && e[i][1]===func) {
unique = false;
break;
}
++i;
}
if(unique) e.push([type, func]);
do {
el.attachEvent(e[i][0], e[i][1]);
} while(i--)
}
}


function removeEvent(el, type, func, bool) {
if(el.addEventListener) el.removeEventListener(type, func, bool);
else if(el.events) {
type = "on" + type;
var e = el.events, i = e.length - 1;
do {
if(e[i][0]===type && e[i][1]===func) {
el.detachEvent(type, func);
e.splice(i, 1);
break;
}
} while(i--)
}
}

Oh, and btw, I noticed when not using reverse() you say "revarse". Is that the Canadian way of saying it?

pman
06-07-2007, 01:22 AM
Unless you have a special reason to, I would use
Code:

function addEvent()

over
Code:

this.addEvent = function() //or window.addEvent = function()

Actually I was writing this addEvent function for an object / class. That's why I had this.addEvent . I should probably make it public instead of privileged. Will work on it later.


Oh, and btw, I noticed when not using reverse() you say "revarse". Is that the Canadian way of saying it?

Nope, not that I know of. But for some reason, when typing I always type "revarse". I guess that make more sense. lol... now we're getting into grammer from javascript.

pman
06-07-2007, 01:56 AM
Hey Trinithis,

This is a very basic question, but since I never used it or never seen in any tutorial, may be you could explain this.

Anyways... when you compare values using "===" what exactly does it tell you? How is it different than "==" ?

May be I should've created a new thread for this.

Trinithis
06-07-2007, 04:19 AM
The difference is that == will return true for an equality where two things are similar values, but are of different data types, whereas with === they have to be the same data type. Here are some examples:

"abc" == "abc"; (true)
"abc" === "abc"; (true)
1 == true; (true)
0 == false; (true)
null == false; (true)
undefined == "undefined"; (true)
true == "true"; (true)
1 == "true"; (true)
1 == "1"; (true)
1 === "1"; (false)
1 === "true" (false)
1 === true; (false)
undefined === false; (false)
undefined === "undefined"; (false)

jmarvan
06-08-2007, 05:54 PM
I am working on a framework (JSX Toolkit) that will solve this issue for you.

It basically introduces a notion of EventRouter that invokes event handlers attached to one DOM element. EventRouter guarantees that different handlers get called in same order on any browser.

All you do is create a class that inherits from EventHandler and instantiate it for the element you want it to handle events for. Instatiating it gets it automatically bound to receive events. You can create as many instances as you need, and of course as many different handlers as required.


jsxClass("MyCustomEventHandler", jsx.utils.events.EventHandler, function(superClass){

this.instantiate = function(handledElement){
//Call the superclass constructor ...
superClass.instantiate.call(this);
//attach this handler to handle handledElement's events
this.addHandledElement(handledElement);
}

//By defining onclick etc. browser events our custom event handler will
//automatically receive those events too.
this.onclick = function(event, targetElement){
alert("Handled element was clicked!");
//by not returning anything we let the event bubble to parents of targetElement
}
});


Check out this and more here :
http://synapticpath.dyndns.org:8080/JSXShowcase/extensions/eventrouting.jsf

turkey1605
06-09-2007, 06:15 PM
Instead of modifying the actual button, could you add something like this:


function click_button()
{
function_1(); //Call first function
function_2(); //Call second function
}

And have the onclick event point to this function. That way, in all browsers they are executed in this order.
Isn't that just a whole load simpler?

pman
06-09-2007, 06:53 PM
Instead of modifying the actual button, could you add something like this:


function click_button()
{
function_1(); //Call first function
function_2(); //Call second function
}

And have the onclick event point to this function. That way, in all browsers they are executed in this order.
Isn't that just a whole load simpler?

Yes... but how would you accomplish this dynamically

turkey1605
06-09-2007, 07:18 PM
What do you mean by 'dynamically'?

Trinithis
06-09-2007, 10:23 PM
(Untested)


var clickHandlerFunctions = [];
function clickHandler(e) {
for(var i=0; i<clickHandlerFunctions.length. ++i) {
(clickHandlerFunctions[i])(e);
}
}
document.addEventListener("click", clickHandler, false);
clickHandlerFunctions.push(function(e){alert(e)});
clickHandlerFunctions.push(function(e){alert(e+e)});

pman
06-10-2007, 01:06 AM
What do you mean by 'dynamically'?

By dynamically, I meant, well in my case, I have a class that has several objects and one of them already has a click event by default. I also wanted to let the user of that class to be able to add any events that they like, to any of those objects. Do you see the picture now?

I didn't have chance to test Trinithis's code. But I think, even if it works and I was to go this way, I will have to have an array of event handlers and a function to execute those events created already ( the first 6 lines ) for every possible events, such as: mouseover, mouseout, etc. since I don't know which one the user might be using. There might be a way around it, but I haven't given it any thought yet. We could probably use 2 dimensional array to eliminate extra work for other event types, as Trinithis did before.

Also remember, for cross browser scripting, you will have to use both attachEvent and addEventListener. That's another conditional check. So, in the end, I think the solution will end up getting bigger if not smaller than what Trinithis initially suggested.

korisu
07-12-2007, 07:54 PM
Sorry for bumping the thread, but I had encountered the same predicament a few months ago, and rather than slogging through the browser incompatibilities, I rethought it and started from the ground up, using Javascript closures to rebuild this from DOM level 0. I bring it up because it's very similar to Trinithis's suggestion. (I'm using it in several of my own scripts, and I've refactored what's there, but it certainly has room to grow. Capturing is possible with this and would be somewhat straightforward to implement, if I weren't distracted with other things at the moment. :P)

It's the Events object about two-thirds of the way down in here:
http://scott.trenda.net/js/common.js