with Imagination: by Dustin Diaz

./with Imagination

A JavaScript, CSS, XHTML web log focusing on usability and accessibility by Dustin Diaz

Rock Solid addEvent()

Monday, November 28th, 2005

In the aftermath of the addEvent() recoding contest, a winner was announced. It was also further discussed in the particleTree November issue with another method provided (I won’t discuss it since it’s copyrighted content and it’s for paying customers only ;)). However even with all the fuss going on, I’ve taken an innovative approach with some existing methods and combined them into a very powerful function. It also accomplishes these three core issues.

  • Degrades on older browsers such as NS4 and IE5 mac
  • The this keyword remains in-tact
  • Avoids memory leaks in Microsoft Internet Explorer

Since none of this is entirely revolutionary or actually originally written by me, I’m not going to take any further credit other than piecing together what was already out there.

Introducing, my addEvent()

Here for your copy and pasting pleasure, it consists of three parts. 1) The addEvent() function itself which programatically adds to the 2) EventCache (originally written by Mark Wubben), and 3) an unload event is added to the window to run the EventCache ‘flush’ method.

function addEvent(): under CC-GNU LGPL license

function addEvent( obj, type, fn ) {
	if (obj.addEventListener) {
		obj.addEventListener( type, fn, false );
		EventCache.add(obj, type, fn);
	}
	else if (obj.attachEvent) {
		obj["e"+type+fn] = fn;
		obj[type+fn] = function() { obj["e"+type+fn]( window.event ); }
		obj.attachEvent( "on"+type, obj[type+fn] );
		EventCache.add(obj, type, fn);
	}
	else {
		obj["on"+type] = obj["e"+type+fn];
	}
}

var EventCache = function(){
	var listEvents = [];
	return {
		listEvents : listEvents,
		add : function(node, sEventName, fHandler){
			listEvents.push(arguments);
		},
		flush : function(){
			var i, item;
			for(i = listEvents.length - 1; i >= 0; i = i - 1){
				item = listEvents[i];
				if(item[0].removeEventListener){
					item[0].removeEventListener(item[1], item[2], item[3]);
				};
				if(item[1].substring(0, 2) != "on"){
					item[1] = "on" + item[1];
				};
				if(item[0].detachEvent){
					item[0].detachEvent(item[1], item[2]);
				};
				item[0][item[1]] = null;
			};
		}
	};
}();
addEvent(window,'unload',EventCache.flush);

Place this in common.js as a must have tool and you’ll be equipped for years to come. Eventually we can ditch the support for NS4 and IE5 (some already have), but the IE6 support is no doubt going to stick around for quite a few more years.

51 Responses to “Rock Solid addEvent()”

  1. Mark Wubben

    You’ll also want to use EventCache for Mozilla, as per this bugreport.

    Also, could you add the CC-GNU LGPL license to this code snippet? Thats the license the Event Cache script is released under.

  2. Dustin Diaz

    No problem mark! And thanks for stopping by. As I recall, the last time you were here was quite some time ago. Some how, some way or another, every time I mention somebody here, they always end up dropping by.

    I’ll modify the above functions and add the reference to the lisence. I was unaware of the extra leak from mozilla.

  3. Mark Wubben

    Yes, well, what can I say? ;-)

  4. Sam Chrisp

    With this method, I was under the impression there will still be memory leaks if elements (which have have event handlers set) are removed prior to the window being unloaded?

  5. Mark Wubben

    Sam, not really, no. The elements are still referenced, so removing them won’t clear up memory, but the events are also removed.

    Or are you saying that the events won’t be removed because the element is no longer part of the document? I wouldn’t know about that, do you have some resource for this?

  6. Konstantin Pelepelin

    In 3rd branch right part seems to be undefined.


    else {
    obj["on"+type] = obj["e"+type+fn];
    }

    Moreover, if you want it “rock solid”, you can use an approach from mentioned “function addLoadEvent”


    else if (typeof obj['on'+type] != ‘function’) {
    obj['on'+type] = fn;
    }
    else {
    var oldonload = obj['on'+type];
    obj['on'+type] = function() {
    oldonload();
    fn();
    }
    }

    What do you think?

  7. Allan Bush

    This is very useful, thank you very much.

    I’ve added a removeEvent function and in doing so I noticed that IE wasn’t really removing the events using the code from EventCache.flush().

    Instead of calling

    item[0].detachEvent(item[1], item[2]);

    I used

    item[0].detachEvent(item[1], item[0][eventtype+item[2]]);

    where eventtype is the unmodifed item[1] (without the “on” in front of the event), which seems to work properly.

    Here is the function the way I’m using it:

    function addEvent( obj, type, fn )
    {
    if (obj.addEventListener) {
    // standard
    obj.addEventListener( type, fn, false );
    EventCache.add(obj, type, fn); // bugzilla bug #241518
    } else if (obj.attachEvent) {
    // IE
    obj['e'+type+fn] = fn;
    obj[type+fn] = function() { obj['e'+type+fn]( window.event ); }
    obj.attachEvent( “on”+type, obj[type+fn] );
    EventCache.add(obj, type, fn);
    } else {
    // really old
    obj["on"+type] = obj["e"+type+fn];
    }
    }

    function removeEvent( obj, type, fn )
    {
    EventCache.remove(obj, type, fn);
    }

    var EventCache = function()
    {
    var listEvents = [];
    return {
    listEvents : listEvents,
    add : function(node, sEventName, fHandler)
    {
    listEvents.push(arguments);
    },
    remove : function(node, sEventName, fHandler)
    {
    var i, item;
    for(i = listEvents.length - 1; i >= 0; i = i - 1) {
    if(node == listEvents[i][0] && sEventName == listEvents[i][1] && fHandler == listEvents[i][2]) {
    item = listEvents[i];
    if(item[0].removeEventListener) {
    item[0].removeEventListener(item[1], item[2], item[3]);
    }
    if(item[1].substring(0, 2) != “on”) {
    item[1] = “on” + item[1];
    }
    if(item[0].detachEvent) {
    item[0].detachEvent(item[1], item[0][sEventName+fHandler]);

    }
    item[0][item[1]] = null;
    }
    }
    },
    flush : function()
    {
    var i, item, eventtype;
    for(i = listEvents.length - 1; i >= 0; i = i - 1) {
    item = listEvents[i];
    if(item[0].removeEventListener) {
    item[0].removeEventListener(item[1], item[2], item[3]);
    }
    eventtype = item[1];
    if(item[1].substring(0, 2) != “on”) {
    item[1] = ‘on’ + item[1];
    }
    if(item[0].detachEvent) {
    item[0].detachEvent(item[1], item[2]);
    item[0].detachEvent(item[1], item[0][eventtype+item[2]]);
    }
    item[0][item[1]] = null;
    }
    }
    }
    }();

    Konstantin Pelepelin’s idea sounds good to me I may need to add that in as well.

  8. JavaScript searchPlay in Object Notation

    [...] form> Function Prerequisites First you’ll need a copy of addEvent. It doesn’t have to be the one I put together as t [...]

  9. Mario

    I dont get why you are


    else if (obj.attachEvent) {
    obj["e"+type+fn] = fn;
    obj[type+fn] = function() { obj["e"+type+fn]( window.event ); }
    obj.attachEvent( “on”+type, obj[type+fn] );
    EventCache.add(obj, type, fn);
    }

    I mean it will work if you just use obj.attachEvent( “on”+type,fn );

    without all that obj garbage

  10. Dustin Diaz

    mario. that preserves the ‘this’ keyword when using addEvent on the attached event. That’s its sole purpose

  11. Paul Davey

    Dustin, as far as I can tell (and from a quick test I ran) this does not prevent the default action for events in Gecko browsers like Firefox. For example, if you have a link with a default href attribute for non-js users, assigning a function as the onclick event handler for that link will not stop Firefox from navigating to that default page. Apart from putting in something like

    if (e.preventDefault) {
    e.preventDefault();
    }

    inside every event handling function, is there a better way of doing this?

    P.S. you need to add in a <pre> button for all these poor people who post multi-line code ;) since the tags do work very nicely.

  12. Hide that sidebar

    [...] ipt) /* used in conjuction with both the dollar $() function and addEvent */ // addEvent() found @ http://www.dustindiaz.com/rock-solid-addevent/ // $() found @ http://www.dustindiaz.com/top-ten-javascript var togBar = { bar : null, nest : null, [...]

  13. Mario

    Thank you for your responce Dustin,

    That method seems to cluttered. I am not sure I see what you mean by defining of ‘this’.

    I know that the ‘this’ keyword is a tricky bugger and a lot of times people forget which scope they are in when using the ‘this’ keyword. Is their an example you can give were using the ‘this’ keyword would cause a problem in the way I choose to use it (without the xtra hoops to jump through).

  14. Dustin Diaz

    Hi Mario,
    an example of ‘this’ being used correctly is when attaching events to a particular element object. With this example of addEvent it preserves it to when you reference the object from the function that is attached to it.

    For example if you attach the ‘click’ event to a link, and make it fire off a function called showTitle(), and within the function you write alert(this.title), The ‘this’ keyword is referencing the link on which you’ve clicked….and with other addEvent functions, ‘this’ will reference the window object - and not the link you clicked on - in Internet Explorer. Got it?

  15. Mario

    having the function arugment being passed as a string can help with the ‘this’ problem and not pollute the dom:

    //this is just a simple innerds to show ya what I mean
    addEvent = function(obj,type,fn)
    {
    var f = ((typeof fn == ’string’)? function(){eval(fn);} : fn);
    obj.attachEvent(’on’+type, f);
    }

  16. Mark

    Hi Guys,

    This is all great and well, but I’m no javascript guru, so an example of how to use this lovely function would be appreciated. Also, I see there have been some recommendations made ~ has the script at the top of the page been modified to accommodate for this, or is it the old one? ~ like the Mozilla “leak” or whatever…

    Thanks,

  17. Mark

    Another thing, how do you check what the target is?

  18. » JSON for the masses

    [...] Assuming you have an addEvent() function handy, this is a simple way to get things started. You’ll notice at the bottom the inializer which fires upon the window ‘load’ event which will in return fire any init() methods we have from different Objects. Then within the ‘obj’ Object literal, there’s a, b, c, and d which all represent possible values for these name value pairs. [...]

  19. DHTML expand and collapse div menu

    [...] If you’re wondering how to collapse any given amount of particular elements, you can simply use this function in combination with the prototype $() dollar function and addEvent: [...]

  20. Forget addEvent, use Yahoo!’s Event Utility

    [...] Furthermore, when I say forget addEvent, I mean any and every version of addEvent you can think of. This goes for Scott Andrew’s original addEvent function, every entry from the addEvent recoding contest, even Prototype’s Event observer and its many properties and extensions. I even had a stab at it myself and was pretty impressed with the outcome. [...]

  21. Forget Yahoo’s Event Utility, Use Dojo’s Event System » Web 2.0 Blog

    [...] According to Dustin Diaz Yahoo’s new Event Utility is “is the dopest, sweetest, most tight, most sexiest event utility on the planet”. Furthermore, when I say “forget addEvent”, I mean any and every version of addEvent you can think of. This goes for Scott Andrew’s original addEvent function, every entry from the addEvent recoding contest, even Prototype’s Event observer and its many properties and extensions. I even had a stab at it myself and was pretty impressed with the outcome. [...]

  22. Eric Larson

    According to Drip, Allen’s removeEvent still leaks in IE. The following lines leave properties in the object. I’ve tried several things (like setting values to null and using the delete statement), but I can’t seem to get Drip to sign off.

    obj[’e'+type+fn] = fn;
    obj[type+fn] = function() { obj[’e'+type+fn]( window.event ); }

    I was concerned about leaving the cleanup to the flush because I’m going to be calling addEvent on dynamic objects that get added and deleted constantly. Plus, users will stay on a single page for a long time, so I was worried about this gradual memory leak.

    Anybody have suggestions?

  23. Top 10 custom JavaScript functions of all time — Kiwi Archive

    [...] Surely a staple to event attachment! Regardless to what version you use written by whatever developer, it does what it says it does. And of course as you might of known, I’ve put together quite a handy version myself recently of addEvent() with some help from the contest winner and Mark Wubben along with a few minor syntax adjustments. But just to be fair to Scott Andrew, here is the original that started it all. [...]

  24. Adam Messinger

    I’m putting this to use on a client’s site, and it works like a charm. Thanks, Dustin!

  25. Josh Rowley

    Hi!

    This code is under the LGPL Licence. I’d just like to check that I have your permission to use it on a commercial website, and have the floowing four freedoms, as per http://creativecommons.org/licenses/LGPL/2.1/

    The freedom to run the program for any purpose.
    The freedom to study how the program works and adapt it to your needs.
    The freedom to redistribute copies so you can help your neighbour.
    The freedom to improve the program and release your improvements to the public, so that the whole community benefits.

    Many Thanks,

    Josh.

  26. Andi

    How I see, new code examples are not welcome ,(

  27. Alex

    Hi Dustin,

    Thanks for your work here! Am just using this for a personal project and it looks like IE 5.0 is tripping up on the line ‘listEvents.push(arguments);’ saying that ‘Object does not support this property or method’. Any thoughts? Maybe I am getting it wrong :S

    Or maybe the yahoo libraries are where it’s at right now?

    Thanks in advance,
    Alex

  28. Stefan Van Reeth

    Hi everyone,

    One more go at the this problem. Personally I find the solution from Dustin & co a bit elaborate, just as Mark did. Off course I have my own version of this, and reading the comments I couldn’t help but think I solved this ages ago. After a quick look, I found this:

    
    else if (obj.attachEvent) {
    	var func = function() {
    		fn.apply(window.event.srcElement);
    	};
    	obj.attachEvent( "on" type, func );
    	EventCache.add(obj, type, func);
    }
    

    There you go, even formatted with same variable names as Dustin’s version!! Works as expected, and is much nicer to see if you ask me. It works in IE 6, but I don’t see why lower/higher versions shouldn’t do it as well. If I’m correct, I made this on a version 5.5.

    Aaaaah, that warm nice feeling of helping people out :)

  29. Stefan Van Reeth

    Aye, twas not Mark but Mario. Sorry dude ;)

  30. Stefan Van Reeth

    Oh and to Mark:

    
    function getTarget(event)
    {
    	var element;
    
    	if (!event)
    	{
    		event = window.event;
    	}
    
    	if (event.target)
    	{
    		if (event.target.nodeType == 3)
    		{
    			element = event.target.parentNode;
    		}
    		else
    		{
    			element = event.target;
    		}
    	}
    	else
    	{
    		element = event.srcElement;
    	}
    	return element;
    }
    

    Just call from the event handler function.

  31. Stefan Van Reeth

    Grrrrr, even postable doesn’t help in all cases. Where did the ‘+’ go between “on” and type? Or did I bork up?

    So should be: obj.attachEvent( “on”+type, func );

    And that’s the last post of today :)

  32. Robert Turtle

    Is this a test. Yes, I believe it is.

  33. Douglas

    Dustin,

    I have, what I feel, is a valuable comment to add to this page, unfortunately, whenever I submit a reply, my comment simply does not show up. Since I was having no luck submitting my post, I figured I would email you and let you know. No luck finding that email address. Alas, you have a contact page (http://www.dustindiaz.com/contact/)! It redirects to the front page. I know, I’ll check the sitemap! Same problem. So, I apologize for littering the comments of this page, but, what are the limitations I could possible be running into when trying to submit a comment on this page? My reply has JS and HTML and before you ask, yes, I used the pre & code tags and yes, I used postable to transform my content before attempting to post it.

  34. Questions from Tim Haines, Part I « Rowan Simpson

    [...] With the exception of addEvent we also don’t currently use any third-party AJAX or JavaScript libraries. To date none of the AJAX functionality we’ve implemented has required the complex behaviours included in these libraries, so we’ve been able to get away with rolling our own simple asynchronous post/call-back logic. [...]

  35. JW Tech Repository » Top 10 Custom JavaScript Functions of All Time

    [...] Surely a staple to event attachment! Regardless to what version you use written by whatever developer, it does what it says it does. And of course as you might of known, I’ve put together quite a handy version myself recently of addEvent() with some help from the contest winner and Mark Wubben along with a few minor syntax adjustments. But just to be fair to Scott Andrew, here is the original that started it all. [...]

  36. Mona

    Awesome script! Ive been on the web looking for something like this for a while now and this is the easiest and the best one ive come across and the only thing i know about javascript is how to spell it, so thanks!

  37. Mona

    Oh sorry, i posted this in the wrong window, i meant for the expand/collapse script as a whole!

  38. Lila

    This is so good. Finally something that works.

  39. RMWChaos

    This code is precisely what I am looking for; however, I am running into a problem (or two…or three).

    I am initiating the code in as an external javascript file. On page load, I receive the following errors:

    IE7 throws errors:

    expected “;”

    then

    conditional compilation is turned off

    Debugging points to line:

    
    add : function(node, sEventName, fHandler)
    

    FF2 & NN9 throw error “invalid label”, and both Firebug and NN Error Console point to the same line of code above.

    Any ideas about what’s going on here? I suspect it is an ID-10-T / operator error. :-)

    I did not see any explicit examples of how to add an event, but I did see how to flush the cache. Based on that example, I think this is the proper syntax:

    
    addEvent("../scripts/myScript.js", "load", EventCache.add);
    

    If not, please straighten me out. The same error occurs when only the EventCache.flush event is listed.

  40. RMWChaos

    I’ve done some research on the javascript “invalid label” error. Interestingly enough, all the information I find relates to JSON. All seem to point to the need to add an additional set of parenthesis “(” and “)” around the code particularly when code like “someLabel : ’some code’” is used.

    I have played around with this trying to find where to include the additional set of parenthesis, but have not had any success. I thought that the “();” at the end of the “var EventCache = function()” code took care of this problem, but I guess not.

    I will keep researching, but any information anyone might be able to provide would be greatly appreciated.

    By the way, I have tested on both WinVista and WinXP SP2, and the problem occurs on both OSes.

  41. RMWChaos

    Well, I suppose if I keep posting enough, I will eventually solve the problem myself. =D

    The problem, as it turns out, was due to my having “broken out the code” into a “more readable format”. More readable for me, perhaps, but not javascript. In var EventCache I had split the brace after “return” onto a new line; js did not like that. So now it is working.

    Additionally, I figured out (duh!) the format for adding events:
    [code=javascript]
    addEvent(window, “load”, myFunction);
    [/code]
    For those of us (i.e. me) less savvy, we need explicit instructions, particularly when we start swimming in the deep end.

    Finally, I still have a question. This is more a matter of preference, but I want to keep my code broken up into seperate files, which do not download to the client until called (on-demand javascript). So I do not want all my code in the same file as the addEvent code. How then do I use addEvent to load an external javascript? Apparently, it’s not as easy as pointing to the file. Do I need to use xmlhttprequest?

    Thanks.

  42. Dustin Diaz

    Hi RMW:
    You can call in code via xmlHTTPRequest, then eval your code. Or perhaps a better method would be to create new script elements, and set their src attribute. You could even run it through a function:

    function scriptRequest(url) {
      var element = document.createElement('script');
      element.src = url;
      document.body.appendChild(element);
    }
    ...
    scriptRequest('ext/file.js');
    
  43. RMWChaos

    Thanks Dustin! So is there a benefit to using the DOM method (your code) over the AJAX method (xmlhttprequest)? I know that I can use the DOM method to remove the script as well say when another is initiated. So many different ways of accomplishing the same task…sigh. =D

  44. RMWChaos

    Dustin,

    I’ve posted two messages now that have not shown up here. Both included code wrapped in the “pre” and “code” html entities. When I attempted to repost one, I was informed that I had “already said that”. :\

  45. Dustin Diaz

    Hi RMW:
    Yes, there are indeed many ways. It would definitely be a good idea to bring in code dynamically and then remove them as they aren’t needed anymore.

    As for the twice posted, I’m not exactly sure what has happened. If you’re strictly talking about posting code, simply use ‘<pre>’ immediately followed by <code>, then within the body of code, run your code through the postable website (or simply just escape your code). The most forgotten messed up piece of code is within ‘for’ loops when someone uses the < “less than” symbol… so I see a lot of this…

    for ( var i = 0, len = ar.length; i 
    
    

    When it should be this:

    for ( var i = 0, len = ar.length; i < len; ++i ) {
      // do something...
    }
  46. RMWChaos

    I took your advice and wrote a script to create and remove “script”, “div”, “span”, and “img” elements; so thank you for that.

    I have two final questions, and I swear I’ll leave you alone (but don’t hold me to it). =D

    1. Does Rock Solid addEvent() call functions in the order they are listed?

    
    addEvent(window, "load", myFunc1); // this is called first
    addEvent(window, "load", myFunc2); // this is called second
    

    or is it asynchronously loaded? The reason is, some of my functions are dependent upon others loading first–my loadDOM function mentioned above must load before my other scripts, which require DOM element creation. I am having trouble with my loadDOM functions not being able to be accessed by other functions if I call them with addEvent.

    Which leads me to the second question…

    2. Does addEvent() in any way effect how vars and functions are loaded into the global space? In other words, once a function is loaded using addEvent, I should be able to access it at any later time, correct?

    Thanks for your time and responses!

  47. Dustin Diaz

    Hi RMW,
    never count on your functions being run in the order you want when they’re assigned as event listeners. Their sole job is to listen, and be run when called. Nothing in the W3 DOM spec says that their order of execution is guaranteed. For example:

    <div id="el">Hello world</div>
    <script>
    var element = document.getElementById('el');
    addEvent(element, 'click', function(e) {
      this.style.color = 'green';
    });
    addEvent(element, 'click', makeRed);
    addEvent(element, 'click', makeBlue);
    function makeRed() {
      this.style.color = 'red';
    }
    function makeBlue() {
      this.style.color = 'blue';
    }
    </script>

    Try running this script in various browsers, see what color the element turns. Try even running it multiple times in the same browser… Then try expanding the what each of the functions do - then you’ll find that you end up running into a race condition, and the only way you can get guaranteed order of execution (for when it matters) is to pile up your logic into a single event listener (then again, only for the cases when it will matter).

    cheers.

  48. RMWChaos

    See? Told you not to hold me to it. =D

    So how about initiating each addEvent() in an ordered fashion…say with

    
    setTimeout("addEvent(window, 'load', myFunc1)", 0)
    setTimeout("addEvent(window, 'load', myFunc2)", 5)
    

    Or something like that? I suppose the easist way of all is to have a single addEvent() “main” function, which calls the next addEvent() at the end, which calls the next…etc. A little clunky though.

    Maybe there is a more eloquent way of ordering the addEvent()s firing off in a particular order. How could that be done? Like modifying function addEvent(obj, type, fn, order) where “order” = [a unique whole number] and including some code that fires each off starting with 0, waits for it to initiate, then fires the next?

    Just thinking out loud here.

  49. RMWChaos

    What about this:

    
    var EventOrder = new array();
    EventOrder[0] = "window, 'unload', myFunc1";
    EventOrder[1] = "window, 'click', myFunc2";
    EventOrder[2] = "window, 'load', myFunc3";
    ...
    
    for (x in EventOrder)
       {
        addEvent(x);
       };
    

    Actually, I think there is a way to do multiple variables per value in the Array, like x(1), x(2), x(3)…instead of using quotes around the data. I saw something like that on a website today. I will have to go back an find it.

    Anyway, what do you think?

  50. Better ways to writing JavaScript

    [...] Get an addEvent function. There are literally hundreds. Pick one. I wrote one a few years back. Or even use an event utility. But really. It comes down to [...]

  51. spudly.shuoink.com » my version of the popular addEvent()

    [...] Inspired by and/or based on the following: Aaron Moore’s addEvent() Dustin Diaz’s addEvent() [...]

Get "JavaScript Design Patterns"

"As a web developer, you'll already know that JavaScript™ is a powerful language, allowing you to add an impressive array of dynamic functionality to otherwise static web sites. But there is more power waiting to be unlocked--JavaScript is capable of full object-oriented capabilities, and by applying OOP principles, best practices, and design patterns to your code, you can make it more powerful, more efficient, and easier to work with alone or as part of a team."

Buy JS Design Patterns from Amazon.com Buy JS Design Patterns from Apress

Submit a Prototype

All content copyright © 2003 - 2007 under the Creative Commons License. Wanna know something? Just ask.

About | Archives | Blog Search

[x] close

Loading...

Submit a prototype

By checking this prototype I agree that I am not submitting false credentials, pornography, or a hate crime website. I also understand that by submitting my entry I may or may not be accepted, and if accepted, my entry may be taken down at any given time if I violate these terms.