addEvent() considered harmful

Back in 2001 Scott Andrew LePera published the cross browser event handler script addEvent(), which was subsequently copied, revised, and used in many, many websites. I never used it, because I felt — and feel — it is wrong to assume that the W3C addEventListener and the Microsoft attachEvent methods are the same. They aren't, and the slight but important difference can trip up the unwary web developer.

Today I found excellent evidence that addEvent() can be harmful if it's used without intimate knowledge of the differences between the W3C and Microsoft event registration models.

Besides, addEvent() doesn't work in Explorer Mac because this browser supports neither event registration model. This used to be a severe problem, but now that Explorer Mac is disappearing fast the argument is losing its validity.

Update: this entry was caused by my work on the KLM site, which is now online.

The function

Scott Andrew's function does the following:

function addEvent(obj, evType, fn, useCapture){
  if (obj.addEventListener){
    obj.addEventListener(evType, fn, useCapture);
    return true;
  } else if (obj.attachEvent){
    var r = obj.attachEvent("on"+evType, fn);
    return r;
  } else {
    alert("Handler could not be attached");
  }
}

It should be called roughly as follows; where obj is an HTML element; for instance a navigation <li>.

addEvent(obj,"mouseover",doSomething)

Now the function is called and first tries to set the mouseover event through the W3C addEventListener method. If that's not possible, because, for instance, the browser is IE, it tries to set the event through the Microsoft attachEvent method. In both cases, if the function succeeds the function doSomething() is called when the uses mouses over the <li> in question.

Unlimited event handlers

A big advantage of this function is that you can set an unlimited amount of mouseover event handlers on the same element. If you'd do

addEvent(obj,"mouseover",doSomething)
addEvent(obj,"mouseover",doSomethingElse)

both functions are executed when the uses mouses over the <li>.

Avoiding memory problems in IE

An additional advantage is that you now have one central function that sets event handlers. This allows you to store all event handlers you've ever set in one array or object.

Why is that important? Because of — surprise! — Explorer Windows. This browser remembers all event handlers you've ever set, so if you load a page with lots and lots of them, and then reload it a few times, its memory starts to become rather full because it keeps on reserving memory for the event handlers in the earlier instances of the pages, and adds a new set of event handlers every time you reload the page. This leads to any number of exciting bugs.

If you store all event handlers you set in one array or object, and remove all of them onunload you can neatly avoid this problem. The memory is emptied on every reload, so it can never fill to overflowing.

The difference between addEventListener and attachEvent

However, the hidden assumption behind addEvent() is that addEventListener and attachEvent work exactly the same. They don't.

The this keyword does not work properly in Microsoft's attachEvent. Instead of referring to the element the event handler is defined on, as it does in the W3C model, it refers to the window object.

Please re-read the paragraph above carefully, and learn it by heart. You might also want to read up on the this keyword in event handling. If you're unaware of this important difference, using addEvent() can cause quite a few problems.

The following example will clarify these problems.

addEvent() and a foldout menu

I was asked to take a look at the (very complicated) JavaScript code of a site that showed some odd bugs in Internet Explorer. After a while I discovered that these bugs disappeared when I turned off the foldout navigation. Further investigation showed that the script sets two events per navigation item, and that there are 112 of them. These 224 event handlers weren't cleared, and right now I assume that this is the cause of the curious Explorer bugs.

These event handlers are set in the traditional way (obj.onmouseover = doSomething), and as part of my solution I tried to port them to addEvent(), which was already in use in other parts of the code.

It turns out, unfortunately, that porting this navigation to Scott Andrew's function is totally impossible without significantly changing the script. To understand why we have to take a closer look at the foldout menu.

Structure and presentation of the foldout menu

The web developer who created the menu sensibly opted for nested lists. Furthermore, he decided that the commands to fold the menu in and out would be given through a simple class change of the <li> the user mouses over. Simplified XHTML/CSS:

<ul id="navigation">
	<li><a href="#">Main item</a></li>
	<li><a href="#">Main item</a>
		<ul>
			<li><a href="#">Subitem</a></li>
			<li><a href="#">Subitem</a></li>
		</ul>
	</li>
</ul>

ul#navigation li ul {
	display: none;
}

ul#navigation li.over ul {
	display: block;
}

Thus, the script's only job is to switch the className of the correct <li> from "over" to "". This is an excellent approach that I often use myself.

The script

Therefore the script is very simple in principle. Using the traditional event registration model, we'd get something like this:

var x = document.getElementById('navigation').getElementsByTagName('li');
for (var i=0;i<x.length;i++)
{
	x[i].onmouseover = function () {
		this.className = 'over';
	}
	x[i].onmouseout = function () {
		this.className = '';
	}
}

Now please take a close look at the this keyword as it is used in this script. The this keyword refers to the "owner" of the event handling function, in this case the <li> the function is defined on. Therefore the function changes the className of the <li> the user mouses over and out, which is exactly what the CSS expects.

The tricky bit

BUT there's a tricky bit: the user doesn't mouse over and out of an <li>. Instead, he moves over an <a>! In most foldout menus, the <li>s are completely filled with the <a>s they contain. There is no separate "<li> space" to mouse over. Besides, users would get totally confused if mousing over the visible link would yield no result. Therefore any foldout menu should definitely work if the user mouses over the <a>.

---------li-------------
|--------a-------------|
||                    ||
||   visible text     ||
||                    ||
|----------------------|
------------------------
No space between li and a

Thus the target of the mouse events is the <a>, and not the <li>! Keep that in mind.

We didn't define any event handler on the <a>. That's no problem, because any event bubbles up to the ancestor elements of the event target. Nothing happens on the <a> because it doesn't have event handlers. Once the event arrives at the <li> the functions kick in and open or close the submenus. After that the event bubbles up all the way to the <html> element, but since no other element has an event handler nothing more happens.

Porting the script to addEvent()

Right now I assume that the 224 event handlers on the 112 links cause the Explorer bug. Therefore my first thought was to port these event handlers from the traditional model to Scott Andrew's addEvent() model, which is already included in the code.

Once I'd done that I could add a bit to addEvent() that keeps track of which event handlers have been defined where, write a little onunload function that goes through all event handlers and removes them, and thus solve the bug.

var x = document.getElementById('navigation').getElementsByTagName('li');
for (var i=0;i<x.length;i++)
{
	x[i].onmouseover = function () {
		this.className = 'over';
	}				
	addEvent(x[i],"mouseover",function () {
		this.className = 'over';
	});
	x[i].onmouseout = function () {
		this.className = '';
	}				
	addEvent(x[i],"mouseout",function () {
		this.className = '';
	});
}

This script works fine in Mozilla and all other standards-compliant browsers, but not in Internet Explorer. The cause is IE's faulty interpretation of the this keyword when you use the attachEvent method.

The script expects this to mean "the <li> the user moused over". Unfortunately IE takes it to mean "the window". It faithfully changes the className of the window, which of course has no effect at all.

Event target? No go

So I'd have to point out the element whose className IE should change more forcefully. I tried to explicitly use the event target:

var x = document.getElementById('navigation').getElementsByTagName('li');
for (var i=0;i<x.length;i++)
{
	addEvent(x[i],"mouseover",function (e) {
		this.className = 'over';
		var node = (e) ? e.target : window.event.srcElement;
		node.className = 'over';
	});
	addEvent(x[i],"mouseout",function (e) {
		this.className = '';
		var node = (e) ? e.target : window.event.srcElement;
		node.className = '';
	});
}

Did that work? No, of course it didn't. The target of the event is the <a>, and not the <li>! Therefore all browsers now switch the className of the <a>, and that has absolutely no effect since the CSS expects the class of the <li> to change.

addEvent() considered harmful

So that's where I'm stuck right now. Obviously, the effect can still be ported succesfully to addEvent(), for instance by changing the className of the target's parentNode. Unfortunately the code I gave above is a very simplified version of the actual code being used in the site, and inserting a simple parentNode is not really possible. I'll find a solution in due time, but it won't be an easy, quickly written one.

This is an excellent illustration of the kind of problems addEvent() can lead to when used by web developers who don't have an intimate knowledge of the various event registration models. Personally I think the web developer started out by using addEvent(), found out that it didn't work in Explorer and then wrote a new routine to accomodate Explorer, without, unfortunately, understanding the reasons why Explorer balked. As a side effect he unwittingly added the curious bugs. His script has become far, far more complicated than it needs to be, and I'm stuck with revising it back towards simplicity, and getting rid of the bugs.

More in general this problem serves as a warning that addEventListener and attachEvent should not be considered equal. They are subtly but annoyingly different, and this difference can form a stumbling block for the unwary web developer. Therefore Scott Andrew's addEvent() function can backfire in unexpected ways.

If you don't really understand the difference between addEventListener and attachEvent, stick to the traditional event registration model:

x[i].onmouseover = doSomething;

Don't experiment with methods you don't understand. You'll do yourself a favour in the long run.

Documentation

Documentation you should know by heart to use addEvent() right:

This is the blog of Peter-Paul Koch, mobile platform strategist, consultant, and trainer. You can also follow him on Twitter.
Atom RSS

I’m around at the following conferences:

(Data from Lanyrd)

Categories:

Monthlies:

Comments

Comments are closed.

1 Posted by Robert Nyman on 30 August 2005 | Permalink

Thanks, interesting article.

"Now the function is called and first tries to set the unload event through the W3C addEventListener method."

I guess you mean the mouseover event, which is the code example attached to above sentence.

Not to be cocky, but personally I think I have a good grip on the event models and how they work. However, it's very good that you explain this; It's a good (and surprising, maybe?) read for many.

Also very good that you touch on the memory leaks in IE and how to handle them, if that becomes necessary.

I know it's a simplified example, so I don't know how it looks in real life. But when it comes to the loop that attaches all the anonymous functions, I guess it would be better to just point to one existing function, and then take care of finding out the target of the event etc there.

Conclusively, working your way up to the parentNode (or a while loop that traverses the node tree till you find the element you're looking for?) sounds like the obvious way, but it's hard to tell without seeing the actual code

2 Posted by Nethanel Vilensky on 30 August 2005 | Permalink

Thank you for yet another great article.
One possible solution to the event handling problem may be to create a custom object that would hold references to all the event handlers and call them appropriately. See the quick and dirty code below.

function mEvent(elem, ename) //Parameters: element and the event type (i.e. "onmouseover")
{
if (!(this instanceof mEvent)) {return new mEvent(elem, ename);}

var el = elem; //Element
var en = ename; //Event
var events = new Array(1); //This array will hold the refs to all the event handlers for this element and event

var oldevent = eval('el.' + en); //Let's save the legacy event handler for this element
if (oldevent != null) {events[0] = oldevent;}

this.addEvent = function(newevent) { //Inserts a new event into the stack of handlers
events[events.length] = newevent;
}
//This function calls all the event handlers in order and passes to them the correct element as "this" and the event
this.doEvents = function(ev) {
for (var i = 0; i < events.length; i++) {
if (events[i] != null) {events[i](el, ev);}
}
}

eval('el.'+en+'=this.doEvents'); //Attach our event handler stack to the element
return this;
}

To attach an event do this:
mEvent(x[i], 'onmouseover').addEvent(function (el, ev) {
el.className = 'over';
});
The mEvent class could be expanded to allow for event handler removal, swapping etc.

3 Posted by Daniel Parks on 30 August 2005 | Permalink

Couldn't you just change addEvent to do something like:

obj.attachEvent("on"+evType, function () { fn.apply(obj, arguments); });

I haven't tested this code.

You could also fix it so that it passed window.event as the parameter. I don't think IE ever passes anything to an event handler, so you would just replace arguments with [ window.event ].

4 Posted by Tino Zijdel on 30 August 2005 | Permalink

It's easy to work around the scope problem using Scott's function, just change

var r = obj.attachEvent("on"+evType, fn);

into

var r = obj.attachEvent("on"+evType, function() { fn.apply(obj); });

however, this uses an anonymous function so detaching the event will probably be impossible. I'm not sure if that will cause any memory-leaks...

5 Posted by Rowan Nairn on 30 August 2005 | Permalink

Or how about:

function addEvent(el,type,func){
  var oldHandler = eval("el.on"+type);

  if(oldHandler)
    eval("el.on"+ type +" = function(e){oldHandler(e);func(e,el);}");
  else
    eval("el.on"+ type +" = function(e){func(e,el);}");

  // Save the handler to clean up later
}

You still can't use 'this' though...

6 Posted by Peter SIewrt on 30 August 2005 | Permalink

I think that comments 2 and 3 are on the right path. There needs to be an intermediary helper function that will standardize everything. Take a look at http://www.geocities.com/petersiewertweb/standardizeEvents.html for an example. This fixes the problems with "this" and "e". I'm unsure if it adds a memory leak.

7 Posted by Tino Zijdel on 30 August 2005 | Permalink

I created an example that also can detach the events in IE by storing a reference to the anonymous function in the DOM of the object itself: http://therealcrisp.xs4all.nl/meuk/addevent.html

8 Posted by Jesse Millikan on 30 August 2005 | Permalink

I never read about addEvent, but I ended up with a similar function in trying to build an XmlHttpRequest-heavy, postback-less application. (Because of a sumo ActiveX control required, the app is only being tested for IE6(+), Firefox.)

I have never run into the 'this' problem so far because I do not use 'this' inside event handlers. I use variables or objects in the scope of the function or method which is adding the event. That is not directly by concession to the problem, either.

Maybe someone else has touched this, but I define the function with
-- CODE --
junk_element = document.creatElement('p');

if( junk_element.addEventListener )
my_handler_adder_function = function(/* args */){/* W3C handling */};
else if( junk_element.attachEvent )
my_handler_adder_function = function(/* args */){/* MS-style handling */};
else /* Ignore or die or whatever */
-- END CODE --
which IMO is more graceful. I feel that the other way is inside-out, but that's a matter of opinion. :)

I have built a separate, odd callback system for parts of the system not directly connected with gui events.

And, I have used the trick with looping up to the right node in the past, but I haven't needed it so far in this app. Lucky me. :)

9 Posted by Tino Zijdel on 30 August 2005 | Permalink

As for most examples posted here: the use of eval is totally unnecessary and will only add to the overhead of your scripts since eval is a slow performer.

Jesse: using anonymous functions for everything will certainly introduce memory-leaks because of closures. Using references is much more convenient and using the 'this' keyword will save you from doing slow lookups in the DOM-tree.

IMO writing your code such that a single function can handle both IE and other browsers (there are actually not so many situations where you should write completely different code for different browsers) is much more transparent. You also should not decide that a browser is IE because of the lack of addEventListener and the presence of attachEvent - you should decide what to do in your code by checking the presence of the methods you *actually* want to use.

10 Posted by Michael Schuerig on 30 August 2005 | Permalink

prototype.js, http://prototype.conio.net/ , has pretty good support for dealing with the issues: Event.observe is similar to addEvent, but remembers added listeners and removes them on unload. Event.findElement finds an element with a given tagName upward from the clicked element.

11 Posted by Jesse Millikan on 31 August 2005 | Permalink

Agree on the use of 'eval', but there are interesting potential uses in creating interactive Javascript tutorials. (Plagiarized foxes? :)

"using anonymous functions for everything will certainly introduce memory-leaks because of closures."

I will use the clearest or easiest way unless performance is a problem, and it is not so far. Sometime, though, I'll test the loss of memory for myself.

Also, the app is only loaded once, and uses asynchronous requests for everything until the user is done. So, the IE memory leak is not as relevant unless I add repeatedly add handlers at runtime.

"You also should not decide that a browser is IE because of the lack of addEventListener and the presence of attachEvent - you should decide what to do in your code by checking the presence of the methods you *actually* want to use."

The script detects which method is present on a sample element, and assumes the same method will be present on others. The comment "MS-Style" comment indicates the use of attachEvent, not that other parts of the IE event model are used. (Sorry if this was the confusion.) I cannot foresee where this approach would fail.

I would post full code, but this is for work.

12 Posted by Tim Connor on 31 August 2005 | Permalink

Tino, there isn't any way to consistently (since, srcElement can be thrown off by nesting) add "this" functionality to event handlers added with attachEvent without using closures (and it's cleanest via anon's in this case), though, right?

Don't you basically have to buckle down and add registration for properly decoupling circular references on unload to avoid memory leaks in IE if you want a nice xbrowser wrapped handler that handles "this".

I've been thinking doing something not too different from what ppk played with here. Getting "this" to work by modifying http://weblogs.asp.net/asmith/archive/2003/10/06/30744.aspx to use "apply" in a closure, instead of adding the event handler as a method and adding in some form of event registration ala http://novemberborn.net/javascript/event-cache and calling it good. Am I missing anything?

13 Posted by Tim Connor on 31 August 2005 | Permalink

I mean, wouldn't this pretty much do it for handling "this" (cursorily tested in IE and Firefox, commenting out the top part to test "legacy" branches)

function ISXBAddHandler(target,eventName,handler){
  if (target.addEventListener){
    target.addEventListener(eventName, function(e){handler.apply(target,[e])}, false);
  }else if(target.attachEvent){
    target.attachEvent("on" + eventName, function(e){handler.apply(target,[e]);});
  }else{
    var originalHandler = target["on" + eventName];
    if(originalHandler){
      target["on" + eventName] = function(e){originalHandler(e);handler.apply(target,[e]);};
    }else{
      target["on" + eventName] = handler;
    }
  }
}

(thanks for deleting the extraneous comments, ppk)

14 Posted by Angus Turnbull on 31 August 2005 | Permalink

Those points are all very valid. So, may I humbly suggest my own event manager script?

http://www.twinhelix.com/javascript/addevent/

Not only does it allow multiple events to be attached to one object in both IE/Mac and IE/Win as well as DOM-Events-compliant browsers, but the "this" keyword operates correctly in all the aforementioned. Plus, it tracks and removes event ONUNLOAD to stop IE memory leaks. And is only several hundred bytes in size, as I've aggressively hand-optimised it.

Hope you guys find it handy! It's free software, too, so please link to, use, tweak and redistribute it as you see fit.

15 Posted by Maian on 31 August 2005 | Permalink

I've been working on a comprehensive event compatability script for academic reasons (it's currently too heavy and too experimental to use for real websites). Here are some more interesting tidbits I've picked up on addEventListener/attachEvent:

1) addEventListener cannot add the same function multiple times, while attachEvent can. For example, calling node.addEventListener('click', foo, false) twice results in adding only 1 listener, while calling node.attachEvent('onclick', foo) twice results in adding 2.

2) Listeners added via attachEvent run from last-added to first-added, while those added via addEventListener run from first-added to last-added. For example:

node.addEventListener('click', foo, false); node.addEventListener('click', bar, false);

When node is clicked, foo is called first, then bar.

node.attachEvent('onclick', foo);
node.attachEvent('onclick', bar);

Here, bar is called first, then foo.

16 Posted by Maian on 31 August 2005 | Permalink

Forgot to mention: listeners added via attachEvent DO get passed an event object, just like those added via addEventListener or traditional listeners in non-IE browsers.

So, there's no need to have code like this:

function (e) {
e = e || window.event;
}

The argument e is guaranteed to be passed if you use attachEvent, so you don't need to check window.event (and in fact, getting window.event is impractical for iframes).

17 Posted by Tino Zijdel on 31 August 2005 | Permalink

There is a way to attach multiple eventhandlers to an object in IE without using closures by stacking the eventhandlers in some object and looping through these in a custom eventhandler that gets attached using the DOM level 1 method: obj.onevent = handler;
This means completely avoiding attachEvent altogether.
This also solves the problem of the order in which the functions are called (for attachEvent MSDN clearly states that the execution order is 'random').
I created an example that can be found at http://therealcrisp.xs4all.nl/upload/addevent2.html

18 Posted by Mark Wubben on 31 August 2005 | Permalink

By those memory problems, do you mean the memory leaks as we know them, or something else?

19 Posted by Jo on 31 August 2005 | Permalink

This link has some great ideas regarding managing events.
http://talideon.com/weblog/2005/03/js-memory-leaks.cfm

20 Posted by Jesse Millikan on 31 August 2005 | Permalink

Tino: A couple things. I stuck a counter in my handler adding function, and it looks like I'll be dealing with thousands of separate closures by the time I'm done. So, you're right, I will probably need to look into memory. :)

Also, why not replace _both_ event models with your own registration system if you are going to take the time to build it? That way, you have the opportunity to customize it for your own use. (Your way looks similar to what prototype.js is doing, by the way.)

21 Posted by Stefan Gössner on 31 August 2005 | Permalink

Maian: As an addendum to your post section 15-1 I want to point to W3C DOM , which states, that addEventListener *will not* add the same function multiple times.

http://www.w3.org/TR/DOM-Level-3-Events/events.html#Events-EventTarget

In this context it should be noted, that anonymous functions are problematic, since

* they cannot get unregistered reliably
* they *can* be registered multiple times according to ECMA 262, section 13.1.

http://goessner.net/articles/domevents/

Thanks for your hint with

e = e || window.event;

being unnecessary.

22 Posted by Ben on 31 August 2005 | Permalink

I'm not saying anything about the spec, but I think I know IE's logic here. Consider the snippet that Daniel posted:

obj.attachEvent("on"+evType, function () { fn.apply(obj, arguments); });

And the one that Tino posted:

var r = obj.attachEvent("on"+evType, function() { fn.apply(obj); });

In both of these cases, the "obj" variable is used inside the anonymous function, but it refers to something *outside* the function. It is stored as a reference at the time the anonymous function is created, and by the time that function is called, the outer code is of course done and the outer "obj" is not necessarily defined... but when the anonymous function was "instantiated" it kept a reference to whatever "obj" referred to at the time.

So then back to the code from the article:

addEvent(x[i],"mouseover",function () { this.className = 'over'; });

I'm fairly certain that the same thing is occuring with the "this" reference. Since it isn't a function parameter and it isn't declared with "var" first, it uses the outer scope, and it refers to the value of "this" at the time the anonymous function gets instantiated. I suspect that it doesn't always refer to "window", that just happens to be the context within which that loop was run.

23 Posted by Emrah BASKAYA on 1 September 2005 | Permalink

I really don't understand what the big deal is not giving the event handling to the anchors themselves. On hesido.com, my navigation looks for span's whose immediate parent is a LI (not UL, that's the magic), yes I use span but the idea is the same. My navigation is not crowded and I found no probs with IE so I use the old event model, but there's no reason not to use the new one. Then you can use e.target and e.srcElement if you attach events to Anchors instead of LI. This will *definitely* solve your problem, as you can stuff information to the anchor element too, and read that from the attached function using e.target and e.srcElement.

24 Posted by Emrah BASKAYA on 1 September 2005 | Permalink

I should correct myself, sorry for double posting; on my navigation system, I look for spans whose parentElement has child UL's. (Well it is actually the same thing, but I didn't want to pass wrong information about my own script :) ) I attach the span some events, no bubbling required. I stuff my element with necessary info, like which ul it is 'tied to', and some other animation parameters. I repeat I use the old event model, but I could have used the attachEvent for IE, as the element could be grabbed using event.srcElement, and I have all the info I need right tied to that element. On hesido.com, I use that single sub-level list, but I also have programmed an infinite level side-menu collapse/expand list using the same principles, which can easily be ported right to that site. Please contact me if you are interested at all, but my explanation here should suffice.

25 Posted by Greg Wogan-Browne on 1 September 2005 | Permalink

If you want the element that the event handler is registered on (instead of the element that triggered the event), in browsers that support DOM 2 Events you use event.currentTarget.

Unfortunately I do not know of a way to get the same information out of IE's event object.

26 Posted by Dustin on 1 September 2005 | Permalink

Fortunately I already knew about the problems you discussed with 'this' and have continued to use the getEventSrc function (also commonly used). It's as simple (as you already know) to say var element = getEventSrc(e); which in fact will replace. Sure it's still sad we have to do it this way, but it works and gets the job done. The bubbling issue though is still another problem as attachEvent() does not even address it.

27 Posted by Justin P on 1 September 2005 | Permalink

As far as using the "this" keyword in referencing the calling object, I've never relied on it. Granted, I've never worked on a page with 224 event registrations.

I usually declare an array of custom objects and I perform event registration on page-level elements which are accessed through this custom object. Since it's an array, I can access the custom object and it's elements easily.

Although this only one small problem discussed here, I figured I'd share it:

function CustomThing(elem){
this.myThing = elem;
}

var myCustomThing = new Array();

function clickHandler(indx){
var activeThing = myCustomThing[indx];
// do stuff with object
}

for (var i=0;i<someParent.children.length;i++){
myCustomThing[i] = new CustomThing(someParent.child[i]);
addEvent(myCustomThing[i].myThing, 'click', new Function('', 'clickHandler(' + i + ');'));
}

28 Posted by scottandrew on 1 September 2005 | Permalink

After reading this, I spent some time trying to make sure I didn't overlook a way to get a handle on the object that fired the handler (not the event source) in IE. But no dice.

It strikes me as a huge oversight. Why would they think they only want the source? How hard would it have been to provide event.currentTarget?

Meh.

Interesting thread full of ideas, very cool, but a shame they're necessary at all.

ps: I like Justin P's idea the best.

29 Posted by justin c. on 2 September 2005 | Permalink

I recently put an event manager of my own together, and I think it works pretty well (granted, it hasn't seen much real production use yet). It works by wrapping the event handler inside a function that hides most of the important differences between the two models, and lets you write your event handlers without much branching. For example, you can just use e.currentTarget to get the element with the handlers registered, and you can call e.stopPropagation() to prevent bubbling. (Apparently I wasn't the first to think of this, because I found a couple other scripts that do pretty much the same thing after I had written mine.)

Anyway, here it is. Maybe it'll be useful:
http://openjsan.org/doc/g/go/goflyapig/DOM/Events/

30 Posted by Tim Connor on 2 September 2005 | Permalink

Hmm, so much for me getting famous for finally taking care of this problem. ;) Now the choice is just which one to go with. I'm actually liking what you have there justin c - enough so to see no reason to spend anymore time on it myself.

31 Posted by Maian on 2 September 2005 | Permalink

Heh, there are plenty of event fixing scripts out there. They just tend to either not fix enough (e.g. most such scripts) or are too heavyweight (e.g. my script).

I haven't touched the script in a while, but it does support most DOM2 event and some DOM3 event properties/methods, normalizes button property support, and adds a couple extensions. In one version of the script, I add addEventListener as a method to all elements (involving some CSS behavior hackery for IE, and some form of Element.prototype for the other browsers), but I backed that part of the code out in order for debugging reasons (and right now, that code is pretty bitrotted).

32 Posted by Emrah BASKAYA on 3 September 2005 | Permalink

I will keep on saying what people seem to miss here, whatever method is being used to attach events, attaching events to every LI is overkill. Just look for anchors whose parent has a child UL, and attach the event to anchor. Simple as pie, obviously, you get much less attaching to do.

33 Posted by Tim Connor on 3 September 2005 | Permalink

Emrah, whoever questioned anything about that? I think we more just ignored it as tangential and went on with the discussion. ;)

Many of us have a diverse set of needs of exactly what we'll be attaching events (I have an entirely different situation that does require EVERY li in a given list to have events attached) to and how exactly we'll be determining that (personally I like Dean Edwards cssquery engine for that). Most any discussion about determining what to attach events to here seems to be solely for example usage, it seems to me.

It doesn't matter if you're only attaching to every third li that as two anchors and an image in it, we're still interested in how to best attach to that.

34 Posted by Emrah BASKAYA on 3 September 2005 | Permalink

Tim, you are right! By saying "overkill", it seemed like I strayed away from the main discussion. But I didn't mean we should use the old event attaching.

What we have at hand:
*We need to be use more friendly DOM methods, because we want to properly detach events.
*attachEvent on LI's is not very feasible, as "this" keyword always returns window. But we need it desperately.
*e.srcElement would have worked, if that anchor or span or whatever wasn't obscuring the LI.
*It is pretty obvious, with the market share of IE, we have to make this work.

So what have to do:
Do the attaching to the LI childs, as they can receive the mouse event properly with e.srcElement.

Whether we need an event attach for every third li, or every li, this is what we should do then. If need a reference to the li, just stuff that info in its child Element(s) so we could say: e.srcElement.targetLI, or simly use 'e.srcElement.parentNode'.

I guess this should work...

35 Posted by Hallvord R. M. Steen on 4 September 2005 | Permalink

The addEvent function that defaults to "true" as the third argument to addEventListener causes serious problems.
http://my.opera.com/hallvors/journal/47

36 Posted by Sebastian Redl on 6 September 2005 | Permalink

It should, without too much difficulty, be possible to store all elements that have handlers set in an array by some ID, then tell the wrapper function this ID. This way, you avoid the dreaded closure and can set currentTarget correctly. An excerpt from my own wrapper script:
--------------------------------
function ie_EventWrap(target, eventName, wrapped)
{
if(!target.elementUID) {
target.elementUID = ++ie_EventWrap.nextElementId;
ie_EventWrap.containers[target.elementUID] = target;
}

var fn = new Function("e",
"e.currentTarget = ie_EventWrap.containers["+target.elementUID+"]; "+
// Code that calls real handler here.
"}");

return fn;
}
ie_EventWrap.nextElementId = 0;
ie_EventWrap.containers = new Array();

---------------------------------

I don't know about possible performance issues with the Function constructor. Also, the code is untested as of yet. But I believe it should work.

37 Posted by Tino Zijdel on 12 September 2005 | Permalink

As to the original problem with the memoryleaks in the sampled menuscript; I believe the problem lies in the fact that anonymous function are being used, and that that somehow creates a circular reference in IE because of the use of the 'this' keyword in that function (somehow the function becomes a closure).
This should have solved the problem:

var x = document.getElementById('navigation').getElementsByTagName('li');
for (var i=0;i<x.length;i++)
{
x[i].onmouseover = li_mouseover;
x[i].onmouseout = li_mouseout;
}

function li_mouseover() { this.className = 'over'; }
function li_mouseout() { this.className = ''; }

38 Posted by Andrey Tarantsov on 24 September 2005 | Permalink

Hello! I found this page useful and wanted to print it to have a closer look. Unfortunately, all those scrolling comments still remain scrolling when I try to print. Could you please add something like a "print view" link, or just disable that scroll stuff for print media?

39 Posted by Jonathan on 28 September 2005 | Permalink

This is actually pretty easy once you play around with it, but I'll only give you a hint.

Keep your memory buckets... good idea.

Realize that not only can you have anonymous functions, but functions can be instantiated.

I have "this" ;) working.