Akxl Labs C# ASP.NET Articles and Tutorials Akxl Labs Web Apps and Tools for Your Website

Handling Events in JavaScript - From Basics to Best-Practices

Tags tagged as   Code: JavaScript, Web
A few simple tips and tricks to create more robust JavaScript event handlers with better performance and fewer interactions. Learn how to subscribe multiple functions to the same event, prevent event flicker, and pass parameters to event handlers.

Posted September 02, 2008    Viewed 3351 times    Add to DiggAdd to del.icio.usAdd to FURLAdd to RedditAdd to YahooAdd to BlinklistAdd to GoogleAdd to ma.gnoliaAdd to ShadowsAdd to Technorati

Event Handler Basics

Before we can get into advanced event handling, here is a basic introduction to JavaScript events. Advanced JavaScript programmers can skip this section to get right to the fun stuff.

Let's define a function called foo:

1 function foo() { 2 alert("called foo"); 3 }

If we want foo to be called when the mouse is moved over a link, we need to have foo called during the onmouseover event of the link. This is called subscribing foo to the onmouseover event. This can be done in the link tag itself:

1 <a id="ATestLink" href="#" onmouseover="foo();">Test MouseOver</a>

Or, we can do this programmatically within JavaScript:

1 document.getElementById("ATestLink").onmouseover = foo;

Note the lack of parenthesis after foo. This is because we want to reference the function here, not call it to execute.

Articles and downloads sponsored by:
Thanks! Amazon commissions help me pay for textbooks.

Extending Event Handling With Closures

Basic event handlers, as shown above, have two limitations:
  1. The functions called by an event handler may have parameters that we need to pass to them, but we have no way to pass these.
  2. Only one event handler can subscribe to an event, and event handlers in different scripts may therefore overwrite each other.
We can solve both of these issues with closures. Closures are nothing more than functions defined within other functions. When a function is defined within another function, the created function has access to the variables in scope within the containing function. Even after the containing function ends, these variables remain accessible to the created function.

As an example, let's look at the following code:

1 function TryClosures() { 2 var message = "This will be displayed"; 3 document.getElementById("ATestLink").onmouseover = function() { alert(message); }; 4 }

This function creates a variable called message, within the scope of TryClosures. Then, it creates a function and assigns this new function to the onmouseover event of a link. Because the function was created within TryClosures, it has access to the message variable created within TryClosures, even after TryClosures ends.

This solves the first problem, by allowing us to pass local variables as parameters to event handler functions. In this case, we passed the local variable message as a parameter to alert. Note that, if we change the value of message after creating the closure, the alert will show the new value.

Our second problem is that we can currently assign only one event handler to each event. For example, in the following code, only foo2 would be called during the onmouseover event, because the assignment of foo2 overwrites the assignment of foo1.

1 document.getElementById("ATestLink").onmouseover = foo1; 2 document.getElementById("ATestLink").onmouseover = foo2;

We can solve this by using a closure to remember the previous value of the event handler, and call it in addition to the new value. We can generalize this in a function called AddEventHandler:

1 function AddEventHandler(e, x) { 2 return function() { if(e) e(); x(); }; 3 }

AddEventHandler is used like this:

1 var testLink = document.getElementById("ATestLink"); 2 testLink.onmouseover = AddEventHandler(testLink.onmouseover, foo1); 3 testLink.onmouseover = AddEventHandler(testLink.onmouseover, foo2); 4 testLink.onmouseover = AddEventHandler(testLink.onmouseover, foo3);

In this example, when the mouse moves over the test link, foo1, foo2, and foo3 are all called. This works because AddEventHandler gets the current function assigned to the event as the parameter e. The function returned by AddEventHandler calls both e, the original event, and x, the added function.

AddEventHandler simply returns a new function that calls the existing and added functions. Using this function ensures that you don't overwrite any previously assigned event handlers, causing a frustrating and hard-to-find bug.

Using Delays To Handle Browser Issues and Prevent Event Flicker

The above functions expand the functionality of JavaScript events, but there are some browser issues to overcome as well. To illustrate the primary issue, let's look at a specific instance:

In Internet Explorer, when a user moves the cursor over a link, the onmouseover event of the link is fired. At the same time, the link text may change to it's hover style (usually a slight change in color). Internet Explorer, at this time, often fires the onmouseout event, immediately followed by the onmouseover event again. Internally, the browser is thinking that you moved over the link, but then moused off of the original link and moused over the hover-styled version of the link.

This can be a common scenario with events. The browser may rapidly fluctuate between opposite events before settling on the actual event. In addition, rapid mouse movement, key presses, and other user actions may cause a rapid deluge of legitimate events.

If you try to handle all of these events, they are handled at almost exactly the same time, occasionally overwriting each other and using many times the processor power. In the end, your script can end up in an unexpected state, and the page performance can suffer.

To correct this, we may want to make sure we handle only the last event fired within a rapid series of events. This way, we wait for the page state to settle before we update the state of our script. We can accomplish this by holding all events for a short amount of time, and canceling pending events if a contradictory or superseding event occurs within the delay.

To start, let's see the JavaScript for a simple event handler for the onmouseover and onmouseout events of a link. After we see the JavaScript for a standard solution, we'll look at how we can trivially alter this code to implement delays.

1 function HandleMouseOver() { 2 alert("handled mouseover"); 3 } 4 5 function HandleMouseOut() { 6 alert("handled mouseout"); 7 } 8 9 var testLink = document.getElementById("ATestLink"); 10 testLink.onmouseover = AddEventHandler(testLink.onmouseover, HandleMouseOver); 11 testLink.onmouseout = AddEventHandler(testLink.onmouseout, HandleMouseOut);

We want to add a delay to insulate HandleMouseOver and HandleMouseOut from bad event calls. So we simply add a set of additional functions to handle this delay. Here is the revised code:

1 var mouseEventTimer; 2 3 function HandleMouseOver() { 4 alert("handled mouseover"); 5 } 6 7 function HandleMouseOut() { 8 alert("handled mouseout"); 9 } 10 11 function HandleMouseOverAfterDelay() { 12 clearTimeout(mouseEventTimer); 13 mouseEventTimer = setTimeout(function() { HandleMouseOver(); }, 150); 14 } 15 16 function HandleMouseOutAfterDelay() { 17 clearTimeout(mouseEventTimer); 18 mouseEventTimer = setTimeout(function() { HandleMouseOut(); }, 150); 19 } 20 21 var testLink = document.getElementById("ATestLink"); 22 testLink.onmouseover = AddEventHandler(testLink.onmouseover, HandleMouseOverAfterDelay); 23 testLink.onmouseout = AddEventHandler(testLink.onmouseout, HandleMouseOutAfterDelay);

Any event handler for the onmouseover and onmouseout events that wants to run has to first survive a 150 millisecond delay. If the event is superseded by another onmouseover or onmouseout event within 150 milliseconds, it is canceled, but if the event holds uncontested for the entire delay, it continues. 150 milliseconds is still an extremely quick response time, so we don't hold up the user noticeably. And, since the delay is so short, we can assume that any events that occur together within this timeout likely fall into the buggy category described above and can be ignored.

This handling is annoying because it adds another layer of code that must be written and maintained. However, it makes scripts more robust and can improve performance and reliability, so i generally consider it a best-practice to insulate events this way. This is especially true for mouse and key events, because of how rapidly users can move the mouse and press keys on a page.

Note that the setTimeout calls in this new code also use closures. This allows us to pass parameters through the setTimeout calls. So, if there were parameters to HandleMouseOver, we could give HandleMouseOverAfterDelay the same parameters, and pass the parameters through the closure.

Conclusion

Altogether, this article has presented three best-practices techniques for JavaScript event handlers:
  1. Use AddEventHandler to add event handlers, instead of directly assigning event handlers and overwriting earlier assignments.
  2. Use closures to call event handlers when you need to pass parameters.
  3. Insulate event handlers behind a delay, so that calls can be cancelled if the event is superseded by another rapid firing of the same or a contradictory event.
Comments are appreciated. If there are any additional topics you would like information on, or other best-practices that I missed, feel free to let me know and I will expand this article.

Acknowledgements

  • firtree.net — firtree's WindowOnload script was the inspiration for AddEventHandler, which extends this concept to any event.

Comments & Feedback


Anthony Bouch says:
Friday, December 26, 2008 @ 1:51 PM
Thanks - very helpful - the delay to protect from rapid firing of the same event was just what I needed for my 'hover captions' on my photo gallery component.
Leave this field blank:
Comment on this Entry
This work is licensed under a Creative Commons Attribution 3.0 United States License.
Please link to this article in your source code comments if you use this content.

Labs

Blog

The blog has moved.
Non-technical articles are now on a seperate site.
Contact me for the new address.

Apps

Real-Time Coffee Counter
add it to your website!
Golden Ratio Visualizer
a tool for design

Coffee Counter

Current Count:
Akxl Coffee Meter
Current Coffee:
 Peet's Malawi Songwe River

The Real-Time Coffee Meter is a free Website App from Akxl Labs. Text-only and badge versions available.