Chapter 4. Multiple Event Handlers: Two’s company

image with no caption

A single event handler isn’t always enough.

Sometimes you’ve got more than one event handler that needs to be called by an event. Maybe you’ve got some event-specific actions, as well as some generic code, and stuffing everything into a single event handler function won’t cut it. Or maybe you’re just trying to build clean, reusable code, and you’ve got two bits of functionality triggered by the same event. Fortunately, we can use some DOM Level 2 methods to assign multiple handler functions to a single event.

An event can have only one event handler attached to it (or so it seems)

Marcy’s page has a problem. We’ve assigned two event handlers to the onmouseover property of her image buttons:

image with no caption

Only the LAST event handler assigned gets run

When you assign two event handlers to the same event, only the last event handler that’s assigned gets run. So on Marcy’s page, mousing over a button triggers onmouseover. Then, that event runs the last handler assigned to it: buttonOver().

image with no caption

Event handlers are just properties

When you assign an event handler to an event on an XHTML element, the handler becomes a property of the element, just like the id or title properties of an <a> element:

image with no caption

A property can have only ONE value

If you assign a value to a property, that property has that single value. So what happens when you assign another value to that property? The property then has the new value, and the old value is gone:

image with no caption

Assign multiple event handlers with addEventListener()

So far, we’ve been adding event handlers to elements by setting the event property directly. That’s called the DOM Level 0 model. DOM stands for the Document Object Model, and it’s how elements on a web page get turned into objects our JavaScript code can work with.

But DOM Level 0 isn’t cutting it anymore. We need a way to assign more than one handler to an event, which means we can’t just assign a handler to an event property. That’s where DOM Level 2 comes in. DOM Level 2 gives us a new method, called addEventListener(), that lets us assign more than one event handler to an event.

Here’s what the addEventListener() method looks like:

image with no caption

Brain Power

In what order do you think the browser will call your handlers? Do you think the ordering in which the handlers are run will affect how you write your code?

You can assign as many handlers as you want to an event using addEventListener().

addEventListener() works in any web browser that supports DOM Level 2.

Your objects can have multiple event handlers assigned to a single event in DOM Level 2

The most important thing that DOM Level 2 added to events is the ability for an event to have more than one handler registered. You’ve already seen how addEventListener() adds a handler to an event:

image with no caption

The browser runs every handler for an event when that event is triggered

When an event is triggered by a mouse movement, the browser looks up the right event. Then, the browser runs every event handler function registered to that event:

image with no caption

Watch it!

Event listeners aren’t called in any particular order.

You might think that the browser calls the event handlers in the order they’re added, but that’s not guaranteed. Make sure that your handlers don’t depend on the order in which they’re called.

What’s going on with Internet Explorer?

The yoga page works great on Firefox, Safari, and a lot of other browsers... but something’s definitely wrong on Internet Explorer:

image with no caption

You can’t control what browsers your users are working with.

It’s your job to build cross-browser applications... and always test your code in LOTS of browsers.

Internet Explorer uses a totally different event model

Remember that addEventListener() only works on browsers that support DOM Level 2? Well, Internet Explorer isn’t one of those browsers. IE has its own event model and doesn’t support addEventListener(). That’s why Marcy got an error trying the yoga page out on IE.

Fortunately, IE provides a method that does the same thing as addEventListener(). It’s called attachEvent():

image with no caption

attachEvent() and addEventListener() are functionally equivalent

Even though the syntax is different, these functions do exactly the same thing. So you just need to use the right one for your users’ browsers.

image with no caption
image with no caption

The browser wars are just part of web development.

Like it or not, not all browsers are the same. Besides, when Microsoft came up with their own event model, it wasn’t that obvious that the DOM Level 2 would take off like it did.

And no matter how it all happened, you can’t write off people who use IE... or those who don’t.

In IE, event names have “on” in front, for example, “onclick” and “onmouseover.”

In Firefox, Safari, and Opera, event names DON’T have “on” in front: “click” and “mouseover.”

Hint

The expression (document.someFunction) returns true if a browser supports running someFunction(), and returns false if that function isn’t supported.

image with no caption

addEventHandler() works for ALL apps, not just Marcy’s yoga page

So where should you put your code for addEventHandler()? We’ll use it in Marcy’s yoga page, but it’s really a utility function. It will work for all our apps and in any browser. So go ahead and add your new code to utils.js, so we can reuse it in later web apps we build.

image with no caption

Anytime you build cross-browser utility functions, store those methods in scripts that you can easily reuse in your other web applications.

Let’s update initPage() to use our new utility function

Now we need to change initPage(), in schedule.js, to use addEventHandler() instead of addEventListener(). Go ahead and make the following changes to your copy of schedule.js:

image with no caption

Use an alert() to troubleshoot

Without any error messages, it’s hard to know exactly what’s going on with Internet Explorer. Try putting in a few alert() statements in the event handlers, though, and you’ll see they’re getting called correctly.

function buttonOver() {
  alert("buttonOver() called.");
  this.className = "active";
}

function buttonOut() {
  alert("buttonOut() called.");
  this.className = "";
}

So what else could be going wrong?

The event handlers are getting called, so that means that addEventHandler() is working like it should. And we’ve already seen that the code in the handlers worked before we added the rollovers. So what else could be the problem?

image with no caption

What do you think the problem could be? Can you figure out why the code isn’t working like it should?

image with no caption

“this” refers to the owner of the executing function.

The this keyword in JavaScript always refers to the owner of the function that’s executing. So if the method bark() was called by an object called Dog, then this in bark() would refer to the Dog object.

When you assign an event handler using DOM Level 0, the element with the event is the owner. So if you did tab.onclick = showTab;, then in showTab(), this would refer to the tab element. That’s why showHint() and hideHint() worked great in the last chapter.

Note

“owner” and “caller” aren’t the same. In a web environment, the browser calls all the functions, but objects representing elements on the page are the owners of those functions.

image with no caption

In DOM Level 2, an event is still the owner of its handlers

When you’re using DOM Level 2 browsers like Firefox, Safari, or Opera, the event handling framework sets the owner of a handler to the object that handler is responding to an event on. So you get the same behavior as with DOM Level 1. That’s why our handlers still work with DOM Level 2 browsers.

But what about IE?

Event handlers in IE are owned by IE’s event framework, NOT the active page object

You already know that IE doesn’t implement DOM Level 2. IE has its own event handling framework. So in IE, the event framework owns the handler functions, not the object on the XHTML page that was activated with a click or mouse over. In other words, this in showTab() refers to the IE event framework, not to a tab element on Marcy’s yoga web page.

Note

Framework just means a set of objects or code that performs some task, like handling events on a web page.

image with no caption

attachEvent() and addEventListener() supply another argument to our handlers

One of the cooler things about JavaScript is that you don’t need to list all the arguments that your functions take when you declare that function. So even if your function declaration is showTab(), you can pass arguments to showTab() when you call it.

image with no caption

The bad thing about that is sometimes you miss out on arguments that are passed to your function.

Your event handlers get an Event object from attachEvent() and addEventListener()

When you register an event handler using DOM Level 2 and addEventListener(), or attachEvent() and IE, both frameworks pass your event handlers an object of the Event type.

Your handlers can then use this object to figure out what object on a page was activated by an event, and which actual event was triggered.

There are two properties in particular that are really helpful to know about. The first is type, which gives the name of the event that was triggered, like “mouseover” or “click.” The second is target, which gives you the target of the event: the object on the page that was activated.

Event objects know what object triggered them and what type of event they are.

Note

So we need to get access to the Event object in our handler functions.

image with no caption

We need to name the Event argument, so our handlers can work with it

You don’t have to list all the arguments a JavaScript function gets. But if you want to actually use those arguments in the function, you do need to list the arguments. First, we need to get access to the Event object in our handlers, so we can figure out what object on a page triggered a call to our handler. Then, we need to list the argument for that Event object:

image with no caption

Brain Power

With DOM Level 2 browsers, you can use either this or the Event object passed into an event handler to find out what element was activated. Do you think one approach is better than the other? Why?

You say target tomato, I say srcElement tomato...

The good news is that both IE and DOM Level 2 browsers make the object that triggered an event available. The bad news is that DOM Level 2 and IE use different versions of the Event object, each with different properties.

In some cases, the Event object properties refer to the same thing, but the property names are different. And to make matters worse, modern versions of IE pass in an Event object, but earlier versions of IE make the Event object available as a property of the window object.

Browsers that support DOM Level 2, like Firefox, Safari, and Opera, pass an Event object to event handlers. The Event object has a property named “target” that refers to the object that triggered the event.

image with no caption

Internet Explorer 7 passes an Event object to event handlers. The Event object has a property named “srcElement” that refers to the object that triggered the event.

image with no caption

Earlier versions of Internet Explorer provide the object that triggered an event in a property named “srcElement,” available on the window object.

image with no caption

So how do we actually GET the object that triggered the event?

The best way to deal with differences in how IE and DOM Level 2 browsers handle events is another utility function. Our handler functions are now getting Event objects, but what we really need is the activated object: the object representation of the element on the page that the event occurred on.

So let’s build a utility function to take the event argument we get from those browsers, and figure out and return the activated object:

image with no caption

Get Head First Ajax now with O’Reilly online learning.

O’Reilly members experience live online training, plus books, videos, and digital content from 200+ publishers.