49 - Handling events with JavaScript
11th October 2012: Material moved to webplatform.org
The Opera web standards curriculum has now been moved to the docs section of the W3C webplatform.org site. Go there to find updated versions of these docs, and much more besides!
12th April 2012: This article is obsolete
The web standards curriculum has been donated to the W3C web education community group, to become part of a much bigger educational resource. It is constantly being updated so that it remains current with modern web design practices and technologies. To find the most up-to-date web standards curriculum, visit the web education community group Wiki. Please make changes to this Wiki yourself, or suggest changes to Chris Mills, who is also the chair of the web education community group.
- Previous article—Dynamic style - manipulating CSS with JavaScript
- Next article—JavaScript animation
- Table of contents
Introduction
Now you are comfortable with using CSS for styling and layout, and have taken your first stumbling steps with understanding variables, functions, methods, etc. in JavaScript, it is time to start using that knowledge to provide your site visitors with interactivity and dynamic behavior (such as dragging and dropping, animation, etc). Controlling events with JavaScript allows you to step into the role as Doctor Frankenstein and really give life to your creations!
But enough about the joys of JavaScript—this article will get practical, telling you what events are and how to make use of them on your pages. The table of contents is as follows:
- What are events?
- How events work
- The evolution of events
- Events and accessibility
- Controlling events
- Event object references
- Event defaults and event bubbling
- Complete event handling example
- Summary
- Exercise questions
Bear in mind that you can download the code example for this article and try it out for yourself.
What are events?
Events occur when some sort of interaction takes place in a web page. This can be the end user clicking on something, moving the mouse over a certain element or pressing down certain keys on the keyboard. An event can also be something that happens in the web browser, such as the web page completing the loading of a page, or the user scrolling or resizing the window.
Through the use of JavaScript, you can detect when certain events happen, and cause things to occur in response to those events.
How events work
When events happen to an HTML element in a web page, it checks to see if any event handlers are attached to it. If the answer is yes, it calls them in respective order, while sending along references and further information for each event that occurred. The event handlers then act upon the event.
There are two types of event order: event capturing and event bubbling.
Event capturing starts with the outer most element in the DOM and works inwards to the HTML element the event took place on and then out again. For example, a click in a web page would first check the HTML
element for onclick
event handlers, then the body
element, and so on, until it reaches the target of the event.
Event bubbling works in exactly the opposite manner: it begins by checking the target of the event for any attached event handlers, then bubbles up through each respective parent element until it reaches the HTML element.
The evolution of events
In the early days of JavaScripting, we used event handlers directly within the HTML element, like this:
<a href="http://www.opera.com/" onclick="alert('Hello')">Say hello</a>
The problem with this approach is that it resulted in event handlers spread throughout the code, no central control and missing out on web browsers' caching features when it comes to external JavaScript file includes.
The next step in event evolution was to apply events from within a JavaScript block, for example:
<script type="text/javascript">
document.getElementById("my-link").onclick = waveToAudience;
function waveToAudience() {
alert("Waving like I've never waved before!");
}
</script>
<a id="my-link" href="http://www.opera.com/">My link</a>
Note the clean HTML in the last example. This is generally what’s referred to as unobtrusive JavaScript. The benefit of this, besides JavaScript caching and code control, is code separation: you have all your content in one location and your interaction code in another. This also allows for a more accessible approach where the link will work perfectly fine with JavaScript disabled; it is also something that will please search engines.
DOM Level 2 Events
Back in November in 2000, the Document Object Model (DOM) Level 2 Events Specification was released by the W3C, offering a more detailed and granular way to control events in a web page. The new way to apply events to HTML elements looked like this:
document.getElementById("my-link").addEventListener("click", myFunction, false);
The first parameter of the addEventListener method
is the name of the event, and you should note that it no longer uses the “on” prefix. The second parameter is a reference to the function we want to call when the event occurs. The third parameter controls the so-called useCapture
of the event, ie if event capturing or event bubbling should be used.
The counterpart of addEventListener
is removeEventListener
, which removes any applied event from an HTML element.
Internet Explorer event model exception
Unfortunately, Internet Explorer has so far not implemented the DOM Level 2 event model, and instead has its own proprietary attachEvent
method. It looks like this in action:
document.getElementById("my-link").attachEvent("onclick", myFunction);
Note that the attachEvent
still uses the "on" prefix before the name of the actual event, and it doesn't include any support for deciding the capture phase.
The counterpart of attachEvent
is detachEvent
, to remove any applied event from an HTML element.
Applying events cross-browser
With the inconsistencies between web browsers in event handling implementations, there have been numerous attempts from web developers to offer a good solution for applying events sucessfully across all major browsers. These solutions have different pros and cons, and are usually referred to as addEvent
functions.
Most major JavaScript libraries have these built in, and there are also a number of stand-alone solutions available online. One suggestion is to use addEvent
by Dean Edwards; you should also consider looking at something like event handling options with the jQuery JavaScript library.
Events and accessibility
Before we delve deeper into explaining how to control and call events, I just want to emphasize accessibility. While it’s normally a broad term for most people, I use it here to convey that what you want to do through the usage of events really should work when JavaScript is disabled or for other reasons blocked in the web browser.
Some people do turn off JavaScript in their web browsers, but more commonly proxy servers, firewalls and overzealous antivirus programs stop JavaScript from behaving as expected. Don’t let this discourage you; my aim is to guide you through creating events that have an accessible fallback in case of JavaScript not being available.
In general, never apply events to HTML elements that don’t already have a built-in behavior for that certain event. You should only apply onclick
events to elements like a
, which already have a fallback behavior for click events (eg browsing to the location specified in the link, or submitting a form).
Controlling events
Let’s start out with a simple example of an event, and how you can react to it. For the sake of simplicity, I will be using the addEvent
solution referred to above, to avoid delving into the intricacies of cross-browser workarounds in each example.
Our first example is the onload
event, which belongs to the window
object. Generally, any events that affect the browser window (like onload
, onresize
and onscroll
) are available through the window
object.
The onload
event takes place when everything in the web page has completely loaded. This includes the HTML code itself as well as external dependencies such as images, CSS files and JavaScript files. When all of them have finished loading, window.onload
gets called, and you can trigger web page functionality to occur. The following very simple example makes an alert message appear when the page has loaded:
addEvent(window, "load", sayHi);
function sayHi() {
alert("Hello there, stranger!");
}
That wasn’t too bad, right? If you want to, you can use so-called anonymous functions instead, eliminating the need for a name for your function. Like this:
addEvent(window, "load", function () {
alert("Hello there, stranger!");
});
Applying events to certain elements
To take this further, we should start by looking into adding events to some other elements on the page. For the sake of argument, let’s suppose you want to have an event happen every time a link is clicked. Combining this with what we learned above, this would be the way to go about it:
addEvent(window, "load", function () {
var links = document.getElementsByTagName("a");
for (var i=0; i<links.length; i++) {
addEvent(links[i], "click", function () {
alert("NOPE! I won't take you there!");
// This line's support added through the addEvent function. See below.
evt.preventDefault();
});
}
});
Ok, what just happened? First we used the onload
event to check when the web page had completely loaded. Then we found all the links in the page by using the getElementsByTagName
method of the document
object. With an established reference to them, we looped through all links and applied an event to them to cause an action to occur once they were clicked.
But what about the cheeky “won’t take you there” part? After the alert
has been shown, the line below reads return false
. This means that within that context, returning false prevents the default action. We’ll get into other ways to dictate how events behave in the last section of this article.
Event object references
To add more detail to your event handling, you can take different actions depending on certain properties of the event that took place. For instance, if you are dealing with an onkeypress
, you might want the event to occur only if the user presses the enter key, but no other keys.
As with the event model, Internet Explorer has decided to use a global event object called event
for handling objects, while the W3C-recommended way implemented by all other web browsers is passing event objects belonging just to that specific event. The most common problem with implementing such functionality across browsers is getting a reference to the event itself, and a reference to the element that the event is targeting. This code solves that for you:
addEvent(document.getElementById("check-it-out"), "click", eventCheck);
function eventCheck (evt) {
var eventReference = (typeof evt !== "undefined")? evt : event;
var eventTarget = (typeof eventReference.target !== "undefined")? eventReference.target : eventReference.srcElement;
}
The first line in the eventCheck
function checks if there’s an event object passed along to the function. If yes, it automatically becomes the first parameter of the function, hence getting the name evt
in this example. If it doesn’t exist, meaning that the current web browser is Internet Explorer, it refers to a global property of the window
object named event
.
The second line looks for a target
property on the established event reference. If it doesn’t exist, it falls back to the srcElement
property implemented by Internet Explorer.
Note: this control and behavior is also addressed with the above referenced addEvent
function, where the event object has been normalized to work the same in all web browsers. The above code is written out as if this is not the case, though, to give you an insight into web browser differences.
Checking an event-specific property
Let’s put this into action. The following example executes a different code block depending on what key was pressed:
addEvent(document.getElementById("user-name"), "keyup", whatKey);
function whatKey (evt) {
var eventReference = (typeof evt !== "undefined")? evt : event;
var keyCode = eventReference.keyCode;
if (keyCode === 13) {
// The Enter key was pressed
// Code to validate the form and then submit it
}
else if (keyCode === 9) {
// The Tab key was pressed
// Code to, perhaps, clear the field
}
}
The code inside the whatKey
function checks a property on the event that took place, namely keyCode
, to see which key was actually pressed on the keyboard. The number 13 means the Enter key and the number 9 means the Tab key.
Event defaults and event bubbling
There are a number of cases where you would be interested in stopping the default behavior of an event. For instance, you might want to prevent the user from submitting a form if certain fields aren’t filled out. The same goes for event bubbling, and this part will explain how you can take control of such situations.
Preventing the default behavior of events
Just as with event model and event object differences, there are two ways to go about this to support IE, and all other browsers. Building on the previous code for getting an event object reference, the next listing includes code to stop the default link behaviour occuring when links are clicked:
addEvent(document.getElementById("stop-default"), "click", stopDefaultBehavior);
function stopDefaultBehavior (evt) {
var eventReference = (typeof evt !== "undefined")? evt : event;
if (eventReference.preventDefault) {
eventReference.preventDefault();
}
else {
eventReference.returnValue = false;
}
}
This approach uses something called object detection, to confirm that a method is actually available before it is called, which helps prevent possible errors. The preventDefault
method is available in every web browser but Internet Explorer, and it prevents the default action of an event from happening.
If that method isn’t supported, it falls back to setting the returnValue
of the global event object to false
, thus stopping the default behaviour in Internet Explorer.
Stopping event bubbling
Consider the following HTML hierarchy:
<div>
<ul>
<li>
<a href="http://www.opera.com/" id="stop-default">Opera</a>
</li>
<li>
<a href="http://www.opera.com/products/dragonfly/" id="stop-default">Opera Dragonfly</a>
</li>
</ul>
</div>
Suppose you had applied an onclick
event to all the a
elements, li
elements and the ul
element. The onclick
event would first call the event handler of the link, then the list items, and finally the event handler of the unordered list.
If the user clicks the link, most likely, you don’t want to call any possible event handler for the parent li
element, but instead just let the user navigate to the corresponding page. However, if the user clicks the li
item beside the link, you might want to trigger an event handler for the li
as well as the ul
element.
Note that with the DOM level 2 Event Model and useCapture
enabled, ie using event capturing, it would start with the unordered list, then the list item and finally the link. However, since event capturing isn’t an option in Internet Explorer, this functionality is very seldom used in real practice.
Here’s how to write code to stop the bubbling of an event:
addEvent(document.getElementById("stop-default"), "click", cancelEventBubbling);
function cancelEventBubbling (evt) {
var eventReference = (typeof evt !== "undefined")? evt : event;
if (eventReference.stopPropagation) {
eventReference.stopPropagation();
}
else {
eventReference.cancelBubble = true;
}
}
Complete event handling example
I have put together an example page showcasing adding an event handler and preventing that event’s default action, depending on certain criteria. The event handler checks whether a form is allowed to be submitted or not depending on if the user has filled out all fields. The JavaScript code is as follows:
addEvent(window, "load", function () {
var contactForm = document.getElementById("contact-form");
if (contactForm) {
addEvent(contactForm, "submit", function (evt) {
var firstName = document.getElementById("first-name");
var lastName = document.getElementById("last-name");
if (firstName && lastName) {
if (firstName.value.length === 0 || lastName.value.length === 0) {
alert("You have to fill in all fields, please.");
evt.preventDefault();
}
}
});
}
});
Summary
I have merely scratched the surface of event handling in this article, but I hope you have gained a good understanding of how events work. I might have been a little hard on you with web browser inconsistencies, but my belief is that it’s very important to know these issues from the start.
Once you have accepted these issues and learned to master the solutions above, there’s no end to the possibilities you can achieve with JavaScript and event handling!
Exercise questions
- What is an event?
- What’s the difference between event capture and event bubbling?
- Is it possible to control the execution of an event, ie stopping the default behavior. How?
- What’s the main problem with the attachEvent and scope, which triggered a JavaScript web community contest?
- Previous article—Dynamic style - manipulating CSS with JavaScript
- Next article—JavaScript animation
- Table of contents
About the author
Robert Nyman has worked with web interface development for a decade, where JavaScript has always been his main interest. He blogs passionately at Robert’s talk about web development.
He lives with his wonderful family in Sweden and stays up at night writing while they’re asleep. Additionally, he nurtures a secret dream to one day get stinking rich, and to have the opportunity write a book about real things, beyond the confines of the web world.
This article is licensed under a Creative Commons Attribution, Non Commercial - Share Alike 2.5 license.
Comments
The forum archive of this article is still available on My Opera.
Ben.Li
Thursday, April 5, 2012
Ben.Li
Thursday, April 5, 2012