Events
If you’re a fan of the absurd, bizarre, and overcomplicated—and you must be if you
write JavaScript—you’re probably familiar with Rube Goldberg machines. Named for
their cartoonist creator, these ad hoc, convoluted contraptions humorously obfuscate
simple processes like flipping a light switch or opening a door. They paint a picture of
a parallel universe where simple tasks are complicated.
I’m drawn to Goldberg machines because they’re the opposite of how things work in
everyday life. Usually we dream of an ideal world where things that
seem simple actually
are simple, without reality stubbornly standing in the way.
Browser-based, event-driven interfaces should be simple. Highlight an element when
the user clicks on it. Disable a submit button after a form has been submitted. But reality is
harsh in the world of web development. Writing event handlers can feel like building an
elaborate set of pulleys to open a window.
In an unyielding quest to help client-side developers manage this complexity, Proto-
type sports a robust event system—one that makes simple things simple and complex
things possible.
State of the Browser (Or, How We Got Here)
I feel like a broken record—browser wars, lack of specifications, Netscape did one thing,
Internet Explorer did another. It bears repeating because we’re still feeling the effects
10 years later.
Rather than subject you to a bland history lesson, though, I’ll recap through code.
Pre-DOM, Part 1
Let’s travel back in time to 1996—the heyday of Netscape 2.0, the first version to imple-
ment JavaScript. Back in the day, this was how you assigned events:
<input type="submit" value="Post" onclick="postBreakfastLogEntry();">
91
CHAPTER 5
The event assignment was just another attribute in the HTML. On its face, this is a
simple and straightforward way to assign events: it makes simple things simple. Unfor-
tunately, it also makes complex things damn near impossible.
First: note that we’re inside a pair of quotation marks. What if we need to use quota-
tion marks in our code?
<input type="submit" value="Post"
onclick="postBreakfastLogEntryWithStatus(\"draft\");">
We could use single quotes instead, but that’s just a band-aid. With sufficiently com-
plex code, we’d be forced to escape quotation marks. By itself it’s not a big deal—just an
illustration of how HTML and JavaScript don’t mix well.
Second, what if we need to assign the event to a bunch of different things?
<input type="submit" value="Save as Draft"
onclick="postBreakfastLogEntryWithStatus('draft');">
<input type="submit" value="Save and Publish"
onclick="postBreakfastLogEntryWithStatus('published');">
<input type="submit" value="Discard"
onclick="postBreakfastLogEntryWithStatus('discarded');">
That’s a lot of typing—and a lot of code duplication for something that should be
much easier. DOM scripting makes it trivial to work with arbitrary collections of ele-
ments. Why, then, are we writing copy-and-paste HTML to solve this problem?
Pre-DOM, Part 2
Such scary times. Let’s jump ahead one year and assign events the way Netscape 3.0 lets
us: in pure JavaScript.
// HTML:
<input type="submit" value="Save and Publish" id="save_and_publish">
// JavaScript:
var submitButton = document.getElementById('save_and_publish');
submitButton.onclick = postBreakfastLogEntry;
Here we’re doing what the previous example only hinted at. Event handlers (onclick,
onmouseover, onfocus, etc.) are treated as properties of the node itself. We can assign a
function to this property—passing it a reference to a named function
or declar
ing an
anonymous function on the spot.
CHAPTER 5 ■ EVENTS92
Now it’s much more elegant to assign the same handler to a bunch of elements:
$('save_and_publish', 'save_as_draft', 'discard').each( function(button) {
button.onclick = postBreakfastLogEntry;
});
But if we assign one function to a bunch of different elements, how do we figure out
which one received the event?
In this example, our
postBreakfastLogEntry function should receive an event object as
its first argument—one that will report useful information about that event’s context. Just
as you can inspect a letter and know its post office of origin, an event handler is able to
inspect an event and know what type it is, where it came from, and what should be done
with it.
function postBreakfastLogEntry(event) {
var element = event.target;
if (element.id === 'save_and_publish')
saveAndPublish();
/* et cetera */
}
Unfortunately, events have never been quite this simple. This is a portrayal of an
ideal simplicity—not on Earth, but in a parallel universe where the adversarial and fast-
moving browser market didn’t make simplicity impossible. The real-world example
would look like this:
function postBreakfastLogEntry(event) {
event = event || window.event;
var element = event.target || event.srcElement;
if (element.id === 'save_and_publish')
saveAndPublish();
/* et cetera */
}
The browser wars were in full swing by 1997. Hasty to add differentiating features
to their own products, and working in an area where a standards body had not yet
claimed authority, Internet Explorer and Netscape developed event models that were
alike enough to seem compatible, but still different enough to cause maximum confu-
sion and headaches.
Ten years later, the landscape is not all that different. But instead of Internet Explorer
versus Netscape, it’s Internet Explorer versus the standards. The other three major
browsers hav
e all adopted DOM Level 2 Events, the 2000 specification that finally
CHAPTER 5 ■ EVENTS 93
brought some authority to the discussion, but as of version 7, Internet Explorer still uses
its proprietary event model.
The strides web applications have made in the last decade only call more attention to
this problem: writing cross-browser event code is just as hard now as it was in 1997. Hav-
ing to reconcile these differences—in property names, in event assignment, and in the
event types themselves—is the mental mildew that makes simple things hard.
Events: The Crash Course
Let’s go back to our site from the previous chapter. So far, we’ve neglected the UI (i.e.,
there is none). Unless we want our breakfast loggers to type all their commands into the
Firebug console, we’ll have to build a simple form for adding entries to the log.
In between chapters, I took the liberty of writing some CSS to make our page a little
less ugly (see Figure 5-1).
Figure 5-1. Lipstick on the pig
Open up
index.html and follow along. (Feel free to style your version in whatever
manner you choose.)
CHAPTER 5 ■ EVENTS94
We’re collecting two pieces of information: what the user ate and how good it was.
The form practically writes itself.
<h1>Log Your Breakfast</h1>
<form id="entry" method="post" action="breakfast.php">
<p>
I just ate <input type="text" id="food_type" name="food_type"
size="15" />.
My meal was <input type="text" id="taste" name="taste" size="15" />.</p>
<input type="submit" name="submit" value="Post Entry" />
</form>
The hardest part has already been done. Remember that we already have a
breakfast.php script, one that expects a form submission containing these two fields.
Insert this markup at the bottom of
index.html, and your page should look something
like Figure 5-2.
Figure 5-2. Our form doesn’t do anything yet, but it looks nice.
CHAPTER 5 ■ EVENTS 95
Now let’s start writing some JavaScript! First we’re going to create a new file called
breakfast.js and include it from index.html. Separating HTML from JavaScript, putting
each in its own file, will stave off the urge to write spaghetti code.
File: index.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
" /><html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Andrew's Breakfast Log</title>
<link rel="stylesheet" href="breakfast.css" type="text/css" />
<script <script src="breakfast.js" type="text/javascript"></script>
</head>
Because we’ll be writing code that uses parts of Prototype, we must include our
new script at the
end. (Remember, Prototype should be the first script you include on
your page.)
There’s nothing in
breakfast.js yet, so let’s fix that. We need to write the function that
will get called when the form is submitted. Then we’ll write the glue to connect it to the
actual event.
function submitEntryForm() {
var updater = new Ajax.Updater({
success: 'breakfast_history', failure: 'error_log'
}, 'breakfast.php',
{ parameters: { food_type: $('food_type').value, taste: $('taste').value } });
}
This code is almost identical to the code we wrote in the last chapter. Only one
thing has changed: instead of specifying the values directly, we look up the values of
the two text boxes. There are many ways to hook into these values, but an ID lookup
is the quickest.
Now the glue. It won’t take much code to connect the function and the event—we
can use Prototype’s
observe method:
$('entry').observe('submit', submitEntryForm);
CHAPTER 5 ■ EVENTS96