Chapter 4. Events are where it happens!

This chapter covers

  • The event models as implemented by the browsers
  • Using jQuery to bind event handlers to elements
  • The Event object instance
  • Triggering event handlers under script control

Anyone familiar with the Broadway show Cabaret, or its subsequent Hollywood film, probably remembers the song “Money Makes the World Go Around.” Although this cynical view might be applicable to the physical world, in the virtual realm of the World Wide Web, it’s events that make it all happen!

Like many other GUI management systems, the interfaces presented by HTML web pages are asynchronous and event-driven (even if the HTTP protocol used to deliver them to the browser is wholly synchronous in nature). Whether a GUI is implemented as a desktop program using Java Swing, X11, the .NET framework, or a page in a web application using HTML and JavaScript, the procedure is pretty much the same:

  1. Set up the user interface.
  2. Wait for something interesting to happen.
  3. React accordingly.
  4. Repeat.

The first step sets up the display of the user interface; the others define its behavior. In web pages, the browser handles the setup of the display in response to the markup (HTML and CSS) that we send to it. The script we include in the page defines the behavior of the interface.

This script takes the form of event handlers, also known as listeners, that react to the various events that occur while the page is displayed. These events could be generated by the system (such as timers or the completion of asynchronous requests) but are most often the result of some user action (such as moving or clicking the mouse or entering text via the keyboard). Without the ability to react to these events, the World Wide Web’s greatest use might be limited to showing pictures of kittens.

Although HTML itself does define a small set of built-in semantic actions that require no script on our part (such as reloading pages as the result of clicking an anchor tag or submitting a form via a submit button), any other behaviors that we wish our pages to exhibit require us to handle the various events that occur as our users interact with those pages.

In this chapter, we examine the various ways that the browsers expose these events, how they allow us to establish handlers to control what happens when these events occur, and the challenges that we face due to the multitude of differences between the browser event models. We then see how jQuery cuts through the browser-induced fog to relieve us of these burdens.

Let’s start off by examining how browsers implement their events models.


JavaScript you need to know!

One of the great benefits that jQuery brings to web applications is the ability to implement a great deal of scripting-enabled behavior without having to write a whole lot of script ourselves. jQuery handles the nuts-and-bolts details so that we can concentrate on the job of making our applications do what it is that our applications need to do!

Up to this point, the ride has been almost free. You only needed rudimentary JavaScript skills to code and understand the jQuery examples we introduced in the previous chapters; in this chapter and the chapters that follow, you must understand a handful of fundamental JavaScript concepts to make effective use of the jQuery library.

Depending on your background, you may already be familiar with these concepts, but some page authors can write a lot of JavaScript without a firm grasp on exactly what’s going on under the covers—the flexibility of JavaScript makes such a situation possible. Before we proceed, it’s time to make sure that you’ve wrapped your head around these core concepts.

If you’re already comfortable with the workings of the JavaScript Object and Function classes and have a good handle on concepts like function contexts and closures, you may want to continue reading this and the upcoming chapters. If these concepts are unfamiliar or hazy, we strongly urge you to turn to appendix A to help you get up to speed on these necessary concepts.


4.1. Understanding the browser event models

Long before anyone considered standardizing how browsers would handle events, Netscape Communications Corporation introduced an event-handling model in its Netscape Navigator browser; all modern browsers still support this model, which is probably the best understood and most employed by the majority of page authors.

This model is known by a few names. You may have heard it termed the Netscape Event Model, the Basic Event Model, or even the rather vague Browser Event Model; but most people have come to call it the DOM Level 0 Event Model.


Note

The term DOM Level is used to indicate what level of requirements an implementation of the W3C DOM Specification meets. There isn’t a DOM Level 0, but that term is used to informally describe what was implemented prior to the DOM Level 1.


The W3C didn’t create a standardized model for event handling until DOM Level 2, introduced in November 2000. This model enjoys support from all modern standards-compliant browsers such as Firefox, Camino (as well as other Mozilla browsers), Safari, and Opera. Internet Explorer continues to go its own way and supports a subset of the DOM Level 2 Event Model functionality, albeit using a proprietary interface.

Before we see how jQuery makes that irritating fact a non-issue, let’s spend time getting to know how the event models operate.

4.1.1. The DOM Level 0 Event Model

The DOM Level 0 Event Model is probably the event model that most web developers employ on their pages. In addition to being somewhat browser-independent, it’s fairly easy to use.

Under this event model, event handlers are declared by assigning a reference to a function instance to properties of the DOM elements. These properties are defined to handle a specific event type; for example, a click event is handled by assigning a function to the onclick property, and a mouseover event by assigning a function to the onmouseover property of elements that support these event types.

The browsers allow us to specify the body of an event handler function as attribute values in the DOM elements’ HTML, providing a shorthand for creating event handlers. An example of defining such handlers is shown in listing 4.1. This page can be found in the downloadable code for this book in the file chapter4/dom.0.events.html.

Listing 4.1. Declaring DOM Level 0 event handlers

In this example, we employ both styles of event handler declaration: declaring under script control and declaring in a markup attribute.

The page first declares a ready handler in which a reference to the image element with the id of vstar is obtained (using jQuery), and its onmouseover property is set to a function instance that we declare inline. This function becomes the event handler for the element when a mouseover event is triggered on it. Note that this function expects a single parameter to be passed to it. We’ll learn more about this parameter shortly.

We also declare a small utility function, say() , that we use to emit text messages to a <div> element on the page . This will save us the trouble of nuisance alerts to indicate when things happen on our page.

In the body of the page (along with the console element), we define an image element on which we’ll define event handlers. We’ve already seen how to define one under script control in the ready handler , but here we declare a handler for a click event using the onclick attribute of the <img> element.

Loading this page into a browser (found in the file chapter4/dom.0.events.html), waving the mouse pointer over the image a few times, and then clicking the image result in a display similar to that shown in figure 4.1.

Figure 4.1. Waving the mouse over the image and clicking it result in the event handlers firing and emitting their messages to the console.

We declare the click event handler in the <img> element markup using the following attribute:

onclick="say('Vroom vroom!'),"

This might lead us to believe that the say() function becomes the click event handler for the element, but that’s not the case. When handlers are declared via markup attributes, an anonymous function is automatically created using the value of the attribute as the function body. The action taken as a result of the attribute declaration is equivalent to (assuming that imageElement is a reference to the image element) the following:

imageElement.onclick = function(event) {
say('Vroom vroom!'),
}

Note how the value of the attribute is used as the body of the generated function, and note that the function is created so that the event parameter is available within the generated function.

Before we move on to examining what that parameter is all about, we should note that using the attribute mechanism of declaring DOM Level 0 event handlers violates the precepts of Unobtrusive JavaScript that we explored in section 1.2. When using jQuery in our pages, we should adhere to the principles of Unobtrusive JavaScript and avoid mixing behavior defined by JavaScript with display markup. We’ll see that jQuery provides a better way to declare event handlers than either of these means before the end of this chapter.

But first, let’s examine what that event parameter is all about.

The Event instance

When an event handler is fired, an instance of a class named Event is passed to the handler as its first parameter in most browsers. Internet Explorer, always the life of the party, does things in its own proprietary way by tacking the Event instance onto a window property named event.

In order to deal with this discrepancy we’ll often see the following used as the first statement in an event handler:

if (!event) event = window.event;

This levels the playing field by using object detection to check if the event parameter is undefined (or null) and assigning the value of the window’s event property to it if so. After this statement, the event parameter can be referenced regardless of how it was made available to the handler.

The properties of the Event instance provide a great deal of information regarding the event that has been fired and is currently being handled. This includes details such as which element the event was triggered on, the coordinates of mouse events, and which key was clicked for keyboard events.

But not so fast. Not only does Internet Explorer use a proprietary means to get the Event instance to the handler, but it also uses a proprietary definition of the Event class in place of the W3C-defined standard—we’re not out of the object-detection woods yet.

For example, to get a reference to the target element—the element on which the event was triggered—we access the target property in standards-compliant browsers and the srcElement property in Internet Explorer. We deal with this inconsistency by employing object detection with a statement such as the following:

var target = (event.target) ? event.target : event.srcElement;

This statement tests if event.target is defined and, if so, assigns its value to the local target variable; otherwise, it assigns event.srcElement. We take similar steps for other Event properties of interest.

Up until this point, we’ve acted as if event handlers are only pertinent to the elements that serve as the trigger to an event—the image element of listing 4.1, for example. But events propagate throughout the DOM tree. Let’s find out about that.

Event bubbling

When an event is triggered on an element in the DOM tree, the event-handling mechanism of the browser checks to see if a handler has been established for that particular event on that element and, if so, invokes it. But that’s hardly the end of the story.

After the target element has had its chance to handle the event, the event model checks with the parent of that element to see if it has established a handler for the event type, and if so, it’s also invoked—after which its parent is checked, then its parent, then its parent, and on and on, all the way up to the top of the DOM tree. Because the event handling propagates upward like the bubbles in a champagne flute (assuming we view the DOM tree with its root at the top), this process is termed event bubbling.

Let’s modify the example of listing 4.1 so that we can see this process in action. Consider the code in listing 4.2.

Listing 4.2. Events propagating from the point of origin to the top of the DOM tree

We do a lot of interesting things in the changes to this page. First, we remove any handling for the mouseover event so that we can concentrate on the click event. We also embed the image element that will serve as the target for our event experiment in a couple of nested <div> elements to place the image element deeper within the DOM hierarchy. We also give almost every element in the page a specific and unique id—even the <body> and <html> tags!

We retain the console and its say() utility function for the same reporting purposes used in the previous example.

Now let’s look at even more interesting changes.

In the ready handler for the page, we use jQuery to select all elements on the page and to iterate over each one with the each() method . For each matched element, we record its instance in the local variable current and establish an onclick handler . This handler first employs the browser-dependent tricks that we discussed in the previous section to locate the Event instance and identify the event target, and then emits a console message. This message is the most interesting part of this example.

It displays the tag name and id of the current element, putting closures to work (please read section A.2.4 in appendix A if closures are a subject that gives you heartburn), followed by the id of the target. By doing so, each message that’s logged to the console displays the information about the current element of the bubble process, as well as the target element that started the whole shebang.

Loading the page (located in the file chapter4/dom.0.propagation.html) and clicking the image result in the display of figure 4.2.

Figure 4.2. The console messages clearly show the propagation of the event as it bubbles up the DOM tree from the target element to the tree root.

This clearly illustrates that, when the event is fired, it’s delivered first to the target element and then to each of its ancestors in turn, all the way up to the <html> element itself.

This is a powerful ability because it allows us to establish handlers on elements at any level to handle events occurring on its descendents. Consider a handler on a <form> element that reacts to any change event on its child elements to effect dynamic changes to the display based upon the elements’ new values.

But what if we don’t want the event to propagate? Can we stop it?

Affecting event propagation and semantics

There may be occasions where we want to prevent an event from bubbling any further up the DOM tree. This might be because we’re fastidious and we know that we’ve already accomplished any processing necessary to handle the event, or we may want to forestall unwanted handling that might occur higher up in the chain.

Regardless of the reason, we can prevent an event from propagating any higher via mechanisms provided on the Event instance. For standards-compliant browsers, we call the stopPropagation() method of the Event instance to halt the propagation of the event further up the ancestor hierarchy. In Internet Explorer, we set a property named cancelBubble to true in the Event instance. Interestingly, many modern standards-compliant browsers support the cancelBubble mechanism even though it’s not part of any W3C standard.

Some events have default semantics associated with them. As examples, a click event on an anchor element will cause the browser to navigate to the element’s href, and a submit event on a <form> element will cause the form to be submitted. Should we wish to cancel these semantics—sometimes termed the default actions—of the event, we set the return value for the handler to false.

A frequent use for such an action is in the realm of form validation. In the handler for the form’s submit event, we can make validation checks on the form’s <input> elements and return false if any problems with the data entry are detected.

We may also see the following on <form> elements:

<form name="myForm" onsubmit="return false;" ...

This effectively prevents the form from being submitted under any circumstances except under script control (via form.submit(), which doesn’t trigger a submit event)—a common trick used in many Ajax applications where asynchronous requests will be made in lieu of form submissions.

Under the DOM Level 0 Event Model, almost every step we take in an event handler involves using browser-specific detection in order to figure out what action to take. What a headache! But don’t put away the aspirin yet—it doesn’t get any easier when we consider the more advanced event model.

4.1.2. The DOM Level 2 Event Model

One severe shortcoming of the DOM Level 0 Event Model is that, because a property is used to store a reference to a function that’s to serve as an event handler, only one event handler per element can be registered for any specific event type at a time. If we have two things that we want to do when an element is clicked, the following statements aren’t going to let that happen:

someElement.onclick = doFirstThing;
someElement.onclick = doSecondThing;

Because the second assignment replaces the previous value of the onclick property, only doSecondThing is invoked when the event is triggered. Sure, we could wrap both functions in another single function that calls both; but as pages get more complicated, as is highly likely in Rich Internet Applications, it becomes increasingly difficult to keep track of such things. Moreover, if we use multiple reusable components or libraries in a page, they may have no idea of the event-handling needs of the other components.

We could employ other solutions: implementing the Observable pattern that establishes a publish/subscribe scheme for the handlers, or even tricks using closures. But all of these add complexity to pages that are already complex enough.

Besides the establishment of a standard event model, the DOM Level 2 Event Model was designed to address these types of problems. Let’s see how event handlers, even multiple handlers, are established on DOM elements under this more advanced model.

Establishing events

Rather than assigning a function reference to an element property, DOM Level 2 event handlers—also termed listeners—are established via an element method. Each DOM element defines a method named addEventListener() that’s used to attach event handlers (listeners) to the element. The format of this method is as follows:

addEventListener(eventType,listener,useCapture)

The eventType parameter is a string that identifies the type of event to be handled. This string is, generally, the same event names we used in the DOM Level 0 Event Model without the on prefix: for example, click, mouseover, keydown, and so on.

The listener parameter is a reference to the function (or inline function) that’s to be established as the handler for the named event type on the element. As in the basic event model, the Event instance is passed to this function as its first parameter.

The final parameter, useCapture, is a Boolean whose operation we’ll explore in a few moments when we discuss event propagation in the Level 2 Model. For now, leave it set to false.

Let’s once again change the example of listing 4.1 to use the more advanced event model. We’ll concentrate only on the click event type; this time, we’ll establish three click event handlers on the image element. The new example code can be found in the file chapter4/dom.2.events.html and is shown in listing 4.3.

Listing 4.3. Establishing event handlers with the DOM Level 2 Model

This code is simple but clearly shows how we have the ability to establish multiple event handlers on the same element for the same event type—something we were not able to do easily with the Basic Event Model. In the ready handler for the page, we grab a reference to the image element and then establish three event handlers for the click event.

Loading this page into a standards-compliant browser (not Internet Explorer) and clicking the image result in the display shown in figure 4.3.

Figure 4.3. Clicking the image once demonstrates that all three handlers established for the click event are triggered.

Note that even though the handlers fire in the order in which they were established, this order isn’t guaranteed by the standard! Testers of this code never observed an order other than the order of establishment, but it would be foolish to write code that relies on this order. Always be aware that multiple handlers established on an element may fire in random order.

Now, what’s up with that useCapture parameter?

Event propagation

We saw earlier that, with the Basic Event Model, once an event was triggered on an element the event propagated from the target element upwards in the DOM tree to all the target’s ancestors. The advanced Level 2 Model also provides this bubbling phase but ups the ante with an additional phase: capture phase.

Under the DOM Level 2 Event Model, when an event is triggered, the event first propagates from the root of the DOM tree down to the target element and then propagates again from the target element up to the DOM root. The former phase (root to target) is called capture phase, and the latter (target to root) is called bubble phase.

When a function is established as an event handler, it can be flagged as a capture handler in which case it will be triggered during capture phase, or as a bubble handler to be triggered during bubble phase. As you might have guessed by this time, the useCapture parameter to addEventListener() identifies which type of handler is established. A value of false for this parameter establishes a bubble handler, whereas a value of true registers a capture handler.

Think back a moment to the example of listing 4.2 where we explored the propagation of the Basic Model events through a DOM hierarchy. In that example we embedded an image element within two layers of <div> elements. Within such a hierarchy, the propagation of a click event with the <img> element as its target would move through the DOM tree as shown in figure 4.4.

Figure 4.4. Propagation in the DOM Level 2 Event Model traverses the DOM hierarchy twice: once from top to target during capture phase and once from target to top during bubble phase.

Let’s put that to the test, shall we? Listing 4.4 shows the code for a page containing the element hierarchy of figure 4.4 (chapter4/dom.2.propagation.html).

Listing 4.4. Tracking event propagation with bubble and capture handlers

This code changes the example of listing 4.2 to use the DOM Level 2 Event Model API to establish the event handlers. In the ready handler , we use jQuery’s powerful abilities to run through every element of the DOM tree. On each, we establish two handlers: one capture handler and one bubble handler. Each handler emits a message to the console identifying which type of handler it is, the current element, and the id of the target element.

With the page loaded into a standards-compliant browser, clicking the image results in the display in figure 4.5, showing the progression of the event through the handling phases and the DOM tree.

Figure 4.5. Clicking the image results in each handler emitting a console message that identifies the path of the event.

Note that, because we defined both capture and bubble handlers for the target, two handlers were executed for the target and all its ancestor nodes.

Well, now that we’ve gone through all the trouble to understand that, we should know that capture handlers are hardly ever used in web pages. The simple reason for that is that Internet Explorer (still inexplicably the most dominant browser) doesn’t support the DOM Level 2 Event Model. Although it does have a proprietary model corresponding to the bubble phase of the Level 2 standard, it doesn’t support any semblance of a capture phase.

Before we look at how jQuery is going to help sort all this mess out, let’s briefly examine the Internet Explorer Model.

4.1.3. The Internet Explorer Event Model

Internet Explorer (both IE6 and, most disappointingly, IE7) doesn’t provide support for the DOM Level 2 Event Model. Both these versions of Microsoft’s browser provide a proprietary interface that closely resembles the bubble phase of the standard model.

Rather than addEventListener(), the Internet Explorer Model defines a method named attachEvent() for each DOM element. This method, as follows, accepts two parameters similar to those of the standard model:

attachEvent(eventName,handler)

The first parameter is a string that names the event type to be attached. The standard event names aren’t used; the name of the corresponding element property from the DOM Level 0 Model is used—onclick, onmouseover, onkeydown, and so on.

The second parameter is the function to be established as the handler, and as in the Basic Model, the Event instance must be fetched from the window.event property.

What a mess! Even when using the relatively browser-independent DOM Level 0 Model, we’re faced with a tangle of browser-dependent choices to make at each stage of event handling. And when using the more capable DOM Level 2 or Internet Explorer Model, we even have to diverge our code when establishing the handlers in the first place.

Well, jQuery is going to make our lives simpler by hiding these browser disparities from us as much as it possibly can. Let’s see how!

4.2. The jQuery Event Model

Although it’s true that the creation of Rich Internet Applications requires a hefty reliance on event handling, the thought of writing event-handling code on a large scale while dealing with the browser differences is enough to daunt even the most intrepid of page authors.

We could hide the differences behind an API that abstracts the differences away from our page code, but why bother when jQuery has already done it for us?

jQuery’s event implementation, which we’ll refer to informally as the jQuery Event Model, exhibits the following features:

  • Provides a unified method for establishing event handlers
  • Allows multiple handlers for each event type on each element
  • Uses standard event-type names: for example, click or mouseover
  • Makes the Event instance available as a parameter to the handlers
  • Normalizes the Event instance for the most often used properties
  • Provides unified methods for event canceling and default action blocking

With the notable exception of support for a capture phase, the feature set of the jQuery Event Model closely resembles that of the Level 2 Model while supporting both standards-compliant browsers and Internet Explorer with a single API. The omission of capture phase should not be an issue for the vast majority of page authors who never use it (or even know it exists) due to its lack of support in IE.

Is it really that simple? Let’s find out.

4.2.1. Binding event handlers using jQuery

Using the jQuery Event Model, we can establish event handlers on DOM elements with the bind() command. Consider the following simple example:

$('img').bind('click',function(event){alert('Hi there!'),});

This statement binds the supplied inline function as the click event handler for every image on a page. The full syntax of the bind() command is as follows:


Command syntax: bind

bind(eventType,data,listener)

Establishes a function as the event handler for the specified event type on all elements in the matched set.

Parameters

eventType

(String) Specifies the name of the event type for which the handler is to be established. This event type can be namespaced with a suffix separated from the event name with a period character. See the remainder of this section for details.

data

(Object) Caller-supplied data that’s attached to the Event instance as a property named data for availability to the handler functions. If omitted, the handler function can be specified as the second parameter.

listener

(Function) The function that’s to be established as the event handler.

Returns

The wrapped set.


Let’s put bind into action. Taking the example of listing 4.3 and converting it from the DOM Level 2 Model to the jQuery Model, we end up with the code shown in listing 4.5 and found in the file chapter4/jquery.events.html.

Listing 4.5. Establishing event handlers without the need for browser-specific code

The changes to this code, limited to the body of the ready handler, are minor but significant . We create a wrapped set consisting of the target <img> element and apply three bind() commands to it—remember, jQuery chaining lets us apply multiple commands in a single statement—each of which establishes a click event handler on the element.

Loading the page into a standards-compliant browser and clicking the image result in the display of figure 4.6, which not surprisingly, is the exact same result we saw in figure 4.3 (except for the URL and window caption).

Figure 4.6. Using the jQuery Event Model allows us to specify multiple event handlers as in the DOM Level 2 Model.

Perhaps more importantly, when loaded into Internet Explorer, it also works as shown in figure 4.7. This was not possible using the code from listing 4.3 without adding a lot of browser-specific testing and branching code to use the correct event model for the current browser.

Figure 4.7. The jQuery Event Model allows us to use a unified code base to support events in Internet Explorer.

At this point, page authors who have wrestled with mountains of browser-specific event-handling code in their pages are no doubt singing “Happy Days Are Here Again” and spinning in their office chairs. Who could blame them?

Another little nifty event handling extra that jQuery provides for us is the ability to group event handlers by assigning them to a namespace. Unlike conventional namespacing (which assigns namespaces via a prefix), the event names are namespaced by adding a suffix to the event name separated by a period character.

By grouping event bindings in this way, we can easily act upon them later as a unit.

Take, for example, a page that has two modes: a display mode and an edit mode. When in edit mode, event listeners are placed on many of the page elements, but these listeners are not appropriate for display mode and need to be removed when the page transitions out of edit mode. We could namespace the edit mode events with code such as

$('#thing1').bind('click.editMode',someListener);
$('#thing2').bind('click.editMode',someOtherListener);
...
$('#thingN').bind('click.editMode',stillAnotherListener);

By grouping all these bindings into a namespace named editMode, we can later operate upon them as a whole. For example, when the page leaves edit mode and it comes time to remove all the bindings we could do this easily with

$('*').unbind('click.editMode'),

This will remove all click bindings (the explanation of the unbind() method is coming up in the next section) in the namespace editMode for all elements on the page.

In addition to the bind() command, jQuery provides a handful of shortcut commands to establish specific event handlers. Because the syntax of each of these commands is identical except for the method name of the command, we’ll save some space and present them all in the following single syntax descriptor:


Command syntax: specific event binding

eventTypeName(listener)

Establishes the specified function as the event handler for the event type named by the method’s name. The supported commands are as follows:

  • blur
  • change
  • click
  • dblclick
  • error
  • focus
  • keydown
  • keypress
  • keyup
  • load
  • mousedown
  • mousemove
  • mouseout
  • mouseover
  • mouseup
  • resize
  • scroll
  • select
  • submit
  • unload

Note that when using these shortcut methods, we cannot specify a data value to be placed in the event.data property.

Parameters

listener

(Function) The function that’s to be established as the event handler.

Returns

The wrapped set.


jQuery also provides a specialized version of the bind() command, named one(), that establishes an event handler as a one-shot deal. Once the event handler executes the first time, it’s automatically removed as an event handler. Its syntax is similar to the bind() command and is as follows:


Command syntax: one

one(eventType,data,listener)

Establishes a function as the event handler for the specified event type on all elements in the matched set. Once executed, the handler is automatically removed.

Parameters

eventType

(String) Specifies the name of the event type for which the handler is to be established.

data

(Object) Caller-supplied data that’s attached to the Event instance for availability to the handler functions. If omitted, the handler function can be specified as the second parameter.

listener

(Function) The function that’s to be established as the event handler.

Returns

The wrapped set.


These commands give us many choices to bind an event handler to matched elements. And once a handler is bound, we may eventually need to remove it.

4.2.2. Removing event handlers

Typically, once an event handler is established, it remains in effect for the remainder of the life of the page. Particular interactions may dictate that handlers be removed based on certain criteria. Consider, for example, a page where multiple steps are presented, and once a step has been completed, its controls revert to read-only.

For such cases, it would be advantageous to remove event handlers under script control. We’ve seen that the one() command can automatically remove a handler after it has completed its first (and only) execution, but for the more general case where we’d like to remove event handlers under our own control, jQuery provides the unbind() command.

The syntax of unbind() is as follows:


Command syntax: unbind

unbind(eventType,listener)

unbind(event)

Removes events handlers from all elements of the wrapped set as specified by the optional passed parameters. If no parameters are provided, all listeners are removed from the elements.

Parameters

eventType

(String) If provided, specifies that only listeners established for the specified event type are to be removed.

listener

(Function) If provided, identifies the specific listener that’s to be removed.

event

(Event) Removes the listener that triggered the event described by this Event instance.

Returns

The wrapped set.


This command can be used to remove event handlers from the elements of the matched set at various levels of granularity. All listeners can be removed by omitting parameters, or listeners of a specific type can be removed by providing that event type.

Specific handlers can be removed by providing a reference to the function originally established as the listener. For this to be possible, a reference to the function must be retained when binding the function as an event listener in the first place. For this reason, when a function that’s eventually to be removed as a handler is originally established as a listener, it’s either defined as a top-level function (so that it can be referred to by its top-level variable name) or a reference to it is retained by some other means. Supplying the function as an anonymous inline reference would make it impossible to later reference the function in a call to unbind().

So far, we’ve seen that the jQuery Event Model makes it easy to establish (as well as remove) event handlers without worries about browser differences, but what about writing the event handlers themselves?

4.2.3. Inspecting the Event instance

When an event handler established with the bind() command is invoked, the Event instance is passed to it as the first parameter to the function. This eliminates the need to worry about the window.event property under Internet Explorer, but what about accessing the divergent properties of the Event instance?

Even when using jQuery to establish handlers, the Event instance passed to the event handler is a clone of the native object as defined by the browser. That means that in standards-compliant browsers, the Event instance will follow the standardized layout of properties, and under Internet Explorer, the instance will use the proprietary layout. Before the proprietary instance is passed to the event handler, jQuery does its best to fix up the object so that the most commonly accessed properties and methods of that object follow the standardized format. So once again, except for the most obscure of Event properties, we can write the code for our event handlers without regard for browser platform.

Table 4.1 shows the Event properties that are safe to access in a platform-independent manner.

Table 4.1. Safe Event instance properties

Property

Description

altKey

Set to true if the Alt key was pressed when the event was triggered, false if not. The Alt key is labeled Option on most Mac keyboards.

ctrlKey

Set to true if the Ctrl key was pressed when the event was triggered, false if not.

data

The value, if any, passed as the second parameter to the bind() command when the handler was established.

keyCode

For keyup and keydown events, this returns the key that was pressed. Note that for alphabetic characters, the uppercase version of the letter will be returned, regardless of whether the user typed an uppercase or lowercase letter. For example, both a and A will return 65. You can use shiftKey to determine which case was entered. For keypress events, use the which property, which is reliable across browsers.

metaKey

Set to true if the Meta key was pressed when the event was triggered, false if not. The Meta key is the Ctrl key on PCs and the Command key on Macs.

pageX

For mouse events, specifies the horizontal coordinate of the event relative from the page origin.

pageY

For mouse events, specifies the vertical coordinate of the event relative from the page origin.

relatedTarget

For some mouse events, identifies the element that the cursor left or entered when the event was triggered.

screenX

For mouse events, specifies the horizontal coordinate of the event relative from the screen origin.

screenY

For mouse events, specifies the vertical coordinate of the event relative from the screen origin.

shiftKey

Set to true if the Shift key was pressed when the event was triggered, false if not.

target

Identifies the element for which the event was triggered.

type

For all events, specifies the type of event that was triggered (for example, click). This can be useful if you’re using one event handler function for multiple events.

which

For keyboard events, specifies the numeric code for the key that caused the event, and for mouse events, specifies which button was pressed (1 for left, 2 for middle, 3 for right). This should be used instead of button, which can’t be relied on to function consistently across browsers.

Importantly, the keypress property isn’t reliable cross-browser for non-alphabetic characters. For instance, the left arrow key has a code of 37, which works reliably on keyup and keydown events. Safari returns nonstandard results for these keys on a keypress event.

We can get a reliable, case-sensitive character code in the which property of keypress events. During keyup and keydown events, we can only get a case-insensitive key code (so a and A both return 65), but we can determine case by checking shiftKey.

The Event instance contains not only properties that give us information regarding the event that’s handled, but also possesses a handful of methods that lets us control the propagation of the event. Let’s dig into those.

4.2.4. Affecting the event propagation

In addition to standardizing the most-used properties of the Event instance, jQuery provides the same benefit for the standard methods used to affect event propagation.

The stopPropagation() method will prevent the event from bubbling further up the DOM tree (if needed, refer back to figure 4.4 for a reminder of how events propagate), and the preventDefault() method will cancel any semantic action that the event might cause. Some examples of such semantic actions are link traversal for <a> elements, forms submissions, and toggling the state of check boxes on a click event.

If we want to both stop the propagation of the event, as well as cancel its default behavior, we can return false as the return value of the listener function.

In addition to allowing us to set up event handling in a browser-independent manner, jQuery provides a set of commands that gives us the ability to trigger event handlers under script control. Let’s look at those.

4.2.5. Triggering event handlers

Event handlers are designed to be invoked when their associated event triggers the propagation of the event through the DOM hierarchy. But there may be times when we want to trigger the execution of a handler under script control. We could define such event handlers as top-level functions so that we can invoke them by name, but as we’ve seen, defining event handlers as inline anonymous functions is much more common and so darned convenient!

jQuery has provided means to assist us in avoiding top-level functions by defining a series of methods that will automatically trigger event handlers on our behalf under script control. The most general of these commands is trigger(), whose syntax is as follows:


Command syntax: trigger

trigger(eventType)

Invokes any event handlers established for the passed event type for all matched elements

Parameters

eventType

(String) Specifies the name of the event type for handlers which are to be invoked

Returns

The wrapped set


Note that the trigger() command (as well as the convenience commands that we’ll introduce in a moment) does not cause an event to be triggered and to propagate through the DOM hierarchy. As there’s no dependable cross-browser means to generate an event, jQuery calls the handlers as normal functions.

Each handler called is passed a minimally populated instance of Event. Because there’s no event, properties that report values, such as the location of a mouse event, have no value. The target property is set to reference the element of the matched set to which the handler was bound.

Also because there’s no event, no event propagation takes place. The handlers bound to the matched elements will be called, but no handlers on the ancestors of those elements will be invoked. Remember, these commands are convenient ways to call an event handler, not to try and emulate an event.

In addition to the trigger() command, jQuery provides convenience commands for most of the event types. The syntax for all these commands is exactly the same except for the command name, and that syntax is described as follows:


Command syntax: eventName

eventName()

Invokes any event handlers established for the named event type for all matched elements. The supported commands are as follows:

  • blur
  • click
  • focus
  • select
  • submit

Parameters

  • none

Returns

The wrapped set.


In addition to binding, unbinding, and triggering event handlers, jQuery offers high-level functions that further make dealing with events on our pages as easy as possible.

4.2.6. Other event-related commands

There are often interaction styles that are commonly applied to pages in Rich Internet Applications and are implemented using combinations of behaviors. jQuery provides a few event-related convenience commands that make it easier to use these interaction behaviors on our pages. Let’s look at them.

Toggling listeners

The first of these is the toggle() command, which establishes a pair of click event handlers that swap off with each other on every other click event. Its syntax is as follows:


Command syntax: toggle

toggle(listenerOdd,listenerEven)

Establishes the passed functions as click event handlers on all elements of the wrapped set that toggle between each other with every other trigger of a click event

Parameters

listenerOdd

(Function) A function that serves as the click event handler for all odd-numbered clicks (the first, the third, the fifth, and so on)

listenerEven

(Function) A function that serves as the click event handler for all even-numbered clicks (the second, the fourth, the sixth, and so on)

Returns

The wrapped set


A common use for this convenience command is to toggle the enabled state of an element based upon how many times it has been clicked. We can simulate this using the image element of our previous examples, changing its opacity to reflect whether it’s enabled (fully opaque) or disabled (partially transparent). We could do this example for real by toggling the read-only state of a text input control, but that would not make as clear a visual statement for demonstration purposes. Let’s fake it with the image example.

Consider figure 4.8, which shows a page containing the image in a time-lapse display of the page in three states:

  1. On initial display
  2. After clicking the image once
  3. After clicking the image again
Figure 4.8. The toggle() command makes it easy to toggle the visual state of the image.

The code for this example is shown in listing 4.6 and can be found in the file chapter4/toggle.html.

Listing 4.6. Invoking complementary listeners on every other click event

In this example, we apply the toggle() command to the images supplying an odd listener that reduces the opacity value to 0.4 (graying out the image, a common term for indicating disablement) and an even listener that restores the opacity to its full value of 1.0 . Because the toggle() command handles all the swapping out for us, we don’t need to bother keeping track of whether the current click is odd or even. How convenient.

All we accomplished in this example was the toggling of the image from full to partial opacity, but it’s easy to imagine supplying listeners that would toggle any complementary states: enabled versus disabled, for example.

Another common multi-event scenario that’s frequently employed in Rich Internet Applications involves mousing into and out of elements.

Hovering over elements

Events that inform us when the mouse pointer has entered an area, as well as when it has left that area, are essential to building many of the user interface elements that are commonly presented to users on our pages. Among these element types, the menus used as navigation systems for web applications are a common example.

A vexing behavior of the mouseover and mouseout event types often hinders the easy creation of such elements when a mouseout event fires as the mouse is moved over an area and its children. Consider the display in figure 4.9 (available in the file chapter4/hover.html).

Figure 4.9. This page helps demonstrate when mouse events fire as the mouse pointer is moved over an area and its children.

This page displays two identical (except for naming) sets of areas: an outer area and an inner area. Load this page into your browser as you follow the rest of this section.

For the top set, the following script in the ready handler establishes handlers for the mouse events:

$('#outer1')
.bind('mouseover',report)
.bind('mouseout',report);

This statement establishes a function named report as the event handler for both the mouseover and mouseout events. The report() function is defined as follows:

function report(event) {
$('#console').append('<div>'+event.type+'</div>'),
}

This listener merely adds a <div> element containing the name of the event that fired to a <div> named console that’s defined at the bottom of the page, allowing us to see when each event fires.

Now, let’s move the mouse pointer into the area labeled Outer 1 (being careful not to enter Inner 1). We’ll see (from looking at the bottom of the page) that a mouseover event has fired. Move the pointer back out of the area. As expected, we’ll see that a mouseout event has fired.

Let’s refresh the page to start over, clearing the console.

Now, move the mouse pointer into Outer 1 (noting the event), but this time continue inward until the pointer enters Inner 1. As the mouse enters Inner 1, a mouseout event fires for Outer 1. If we wave our pointer over the inner area, we’ll see a flurry of mouseout and mouseover events. This is the expected behavior. Even though the pointer is still within the bounds of Outer 1, when the pointer enters a contained element, the event model considers the transition from the area of Outer 1 for its contained element to be leaving the outer area.

Expected or not, we don’t always want that behavior. Often, we want to be informed when the pointer leaves the bounds of the outer area and don’t care whether the pointer is over a contained area or not.

We could write our handlers to detect when a mouse event is the result of leaving the area or the result of merely entering a contained element, but luckily we won’t have to. jQuery comes to our aid with the hover() command.

The syntax of this command is as follows:


Command syntax: hover

hover(overListener,outListener)

Establishes handlers for the mouseover and mouseout events for matched elements. These handlers only fire when the area covered by the elements is entered and exited, ignoring transitions to child elements.

Parameters

overListener

(Function) The function to become the mouseover handler.

outListener

(Function) The function to become the mouseout handler.

Returns

The wrapped set.


We use the following script to establish mouse event handlers for the second set of areas (Outer 2 and its Inner 2 child) on the hover.html example page:

$('#outer2').hover(report,report);

As with the first set of areas, the report() function is established as the mouseover and mouseout handlers for Outer 2. But unlike the first set of areas, when we pass the mouse pointer over the boundaries between Outer 2 and Inner 2, neither of these handlers is invoked. This is useful for those situations where we have no need to react when the mouse pointer passes over child elements.

With all these event-handling tools under our belts, let’s use what we’ve learned so far and look at an example page that makes use of them, as well as some of the other jQuery techniques that we’ve learned from previous chapters!

4.3. Putting events (and more) to work

Now that we’ve covered how jQuery brings order to the chaos of dealing with disparate event models across browsers, let’s work an example page that puts the knowledge that we’ve gained so far to use. This page uses not only events but also some jQuery techniques that we’ve explored in earlier chapters, including some heavy-weight jQuery selectors.

The page that we’ll create in this section is a small part of an online order form for an Asian restaurant named Bamboo Asian Grille. For brevity, we’re going to restrict ourselves to the appetizer section of the menu, but you can apply the lessons learned to the remainder of the menu form, which you can complete to practice your jQuery skills.

The goal for this example seems simple: to allow users to select the type and number of appetizers they would like added to their order. No problem, right? A series of check boxes and text boxes will do nicely as the expected GUI element for making multiple choices and specifying the quantities.

But there’s a small catch: for each appetizer, other options must be presented. For example, when ordering Crab Rangoon, diners can choose sweet-and-sour sauce, hot mustard, or (for those who can’t decide) both. Again, this shouldn’t be a problem because we can associate a series of radio buttons representing the options with each appetizer entry.

But as it turns out, this does lead to a small problem. With a little HTML coding and some CSS magic, we create the layout shown in figure 4.10.

Figure 4.10. All our appetizers and options are displayed, but the screen is a jumbled mess!

Even with only five appetizer choices and their corresponding options, the number of controls is overwhelming; it may even be difficult to see the choices the diner has made so far. The form works as required, but its usability leaves much to be desired.

We can solve this usability dilemma by applying a principle known as progressive disclosure. We don’t need to present options for an appetizer the user isn’t ordering, so we’ll hide the radio button options until the user needs to see them.

Progressively disclosing information as it’s needed will vastly improve the usability of the form by reducing the confusing clutter, as shown in figure 4.11.

Figure 4.11. By hiding the options until they’re needed, we reduce the confusion and clutter, and the user isn’t overwhelmed by controls that aren’t relevant.

As a bonus, we’ll also instrument the controls so that when a quantity is entered by the hungry user, the displayed dollar amount will reflect the price for the quantity chosen. Let’s see what it takes to make all that happen.

The full page code for this example is available in the file chapter4/bamboo/bamboo.html, but let’s start by examining the structure of the HTML that’s used to implement the display of one appetizer entry as shown in listing 4.7.

Listing 4.7. HTML structure for a single appetizer entry

We repeat this HTML structure for each appetizer entry. Note that this snippet contains no visual rendition information; such information is factored out to CSS definitions (which can be found in the file bamboo.css in the same folder as the HTML file).

Similarly, note that there’s no script embedded within the HTML markup. The behavior of our page will be defined following the principles of Unobtrusive JavaScript with all script properly sequestered from the HTML markup.

We should emphasize some aspects of this structure because they will become important when adding the behaviors to these elements. First, note that the check box elements (as well as the radio elements further into the markup) are contained within <label> elements that also hold the text that corresponds to the controls. This makes clicking the text of the label flip the checked state of the contained control as if the user clicked the control itself. This is a handy usability enhancement that makes it easier for people to use our pages (especially for so-called sloppy clickers, a group to which at least one of your authors belongs).

Another notable feature is the <span> element that contains the quantity text box and price display . This element is adorned with a custom attribute named price that we use to store the price value for the appetizer. We’ll need this value to calculate the price when the quantity is entered, and the attribute will also serve as a useful selector handle in our jQuery selectors. (The use of custom attributes in this fashion is considered controversial by some; please see the sidebar for more information.)

Note also that the <span> element created to contain the computed amount is initially empty . We could just fill it in statically, but that means we’d have price information in two places—generally considered not a best practice. Later, we’ll see how we fill this in as part of the setup behavior of the page.

The final major construct in our markup is the <div> element that contains the radio buttons representing the appetizer options. This is the element that will be hidden until an appetizer is checked.

With the markup all settled, let’s develop the behavior of the page step by step, starting with hiding the container element for the radio button options.


Custom attributes: heroic or heinous?

The use of custom attributes to adorn DOM elements with attributes not defined by the HTML or XHTML Specifications is a practice that has both its supporters and detractors. To some, it’s a useful leveraging of the tools that HTML and the browsers make available to us; to others, it’s an affront to all that is good because using custom attributes can prevent the page from passing validation testing.

Your authors take no sides on this issue and leave it to you, the reader, to determine whether you think that using custom attributes is a useful mechanism or a wart on the face of a page.

Without the use of the attribute, the price data could be stored in a JavaScript variable containing an object hash that associates an appetizer name (imperial, for example) with its price.

The custom attribute tactic can be said to be advantageous over the JavaScript variable mechanism because adding new appetizer entries means adding a new, self-contained construct to the page without having to remember to update the object hash with the price of added appetizers.

Again, we leave it to you to determine which approach you feel suits you best.


Inspecting the HTML structure of each appetizer entry allows us to concoct a selector that matches the <div> elements and to use the hide() command on them as follows:

$('fieldset div div').hide();

Note

We could initially hide these elements with CSS, but doing so in script ensures that users who turn off JavaScript (yes, there are still people who do that) will get a usable interface, albeit at the price of some computation load when the page is displayed. There are other reasons to do the hiding in the ready handler that we’ll discuss in chapter 5 when we examine wrapper methods like hide() in greater detail.


Having tucked the appetizer options away for later display, we now turn our attention to enabling the usability behaviors that we want the elements to exhibit. Let’s start by tying the display of the radio button options associated with an appetizer to whether that appetizer is checked or not.

To react to a change in the state of an appetizer check box, which should trigger the change in visibility of the <div> element containing its options, we establish a listener for a click event on the check boxes in which we can adjust the visibility of the options based upon the state of the check box. Let’s look at the following statement, which establishes this listener:

$(':checkbox').click(function(){
var checked = this.checked;
$('div',$(this).parents('div:first'))
.css('display',checked ? 'block':'none'),
$('input[type=text]',$(this).parents('div:first'))
.attr('disabled',!checked)
.css('color',checked ? 'black' : '#f0f0f0')
.val(1)
.change()
.each(function(){ if (checked) this.focus();});
});

All that just to hide and show a <div>?

Well, no. Hiding and showing the options is just one of the things that we need to do when the state of one of the check boxes changes. Let’s look at each step in this fragment to see what it does for us.

First, the click handler stores the checked state of the check box in a variable named checked. This makes for easy reference in the code, and it establishes a local variable that we can use in any closures that we create.

Next, the handler locates the <div> containing the appetizer options and sets its CSS display value to hide those options when the check box is unchecked or to show them when the check box is checked. The jQuery expression that we use to locate the element to be shown or hidden is $('div',$(this).parents('div:first')), which equates to “find the <div> elements in the first ancestor element of this that is a <div>.” Because we know from our HTML structure that there will be only one match, we don’t need to differentiate any further.

Being astute, you’ll have noted that, because we know the initial state of the check box is unchecked and the options are hidden, we could have written less code by using the toggle() command without needing to query the state of the check box. That type of assumptive code can be fragile and break easily when its assumptions change, so it’s generally better to make explicitly certain that the visibility of our options matches the state of their respective check boxes.

Our handler isn’t done yet; it still needs to adjust the state of the quantity text boxes. These boxes are initially disabled and will only be enabled when an appetizer is checked. We locate the text box with $('input[type=text]',$(this).parents('div:first')), a similar selector to the one we just employed that says “locate the <input> element of type text in my first parent <div>.”

To this element, we do the following:

  • Use the attr() command to set its disabled state to correspond to the inverse of the check box state.
  • Apply a CSS color value so that the text is invisible when the control is disabled. (Note that this doesn’t work on all browsers—some like Opera and Internet Explorer don’t allow us to override the color of disabled fields.)
  • Set the value to 1. If we’re enabling the control, this is the default value we want to use; when disabling the field, we want to revert to this default.
  • Call the change handler of the text box (which we haven’t defined yet, but don’t worry because that’s next). This change handler will compute the price for the appetizer and display it. Because we changed the value under the covers (to 1), we need to call this handler to ensure that the price display is accurate.
  • Employ the each() method to obtain a reference to the element and assign focus to that element if the check box is in checked state. Don’t you just love closures that give us access to the checked local variable?

Note

When pondering what type of event to handle for the check boxes, you may initially have thought of capturing change events as opposed to click events. For our scheme to work, we need to be immediately notified when the state of a check box changes. Immediate notification occurs within Safari and the Mozilla-based browsers, but Internet Explorer does not trigger change events until after focus has been blurred from the control, making the change event unsuitable for this use; instead, we rely on the click event.


Now let’s turn our attention to the change handler for the text box. When the value in the text box changes, we want to recompute and display the cost of the appetizer—a simple calculation made by multiplying the price of one appetizer by the quantity.

The statement to add the change handler is as follows:

$('span[price] input[type=text]').change(function(){
$('~ span:first',this).text(
$(this).val() *
$(this).parents("span[price]:first").attr('price')
);
});

After locating the text boxes (with a selector that reads “find all <input> elements of type text that are within <span> elements possessing a price attribute”), we assign a change handler that finds the <span> to be updated and sets its text content to the computed value; the expression $('~ span:first',this) locates the first sibling of this that’s a <span> element. The computation is made by obtaining the value of the text box and multiplying it by the value of the price attribute on the parent <span>.

If any of these rather advanced selector expressions has you scratching your head, it might be a good time to review the selector syntax presented in chapter 2.

Before we let the user interact with our page, we have one more thing that we need to do. Remember how we left the <span> elements that are to contain the computed values blank? Now it’s time to fill those in.

The values of the quantity text boxes were preset to 1, so all we need to do is to perform the same computation that occurs when the values are changed. But we don’t want to repeat any code so we trigger the change handler on the text boxes and let that change handler do its thing.

$('span[price] input[type=text]').change();

With that, we’ve completed whipping up the appetizer order form—at least to the point where we’ve met our stated goals. This example exposed us to some very important lessons:

  • It showed us how to establish click and change handlers on elements that can be used to effect whatever user interface changes we want when triggered.
  • We saw how to trigger handlers under script control to avoid both repeated code and the need to factor common code out into global named functions.
  • We were exposed to some mighty fancy selectors used to pick and choose which elements we wanted to perform operations on.

The complete code for the page is shown in listing 4.8.

Listing 4.8. Complete code for the appetizer order form
<html>
<head>
<title>Bamboo Asian Grille - Online Order Form</title>
<link rel="stylesheet" type="text/css" href="bamboo.css">
<script type="text/javascript"
src="../../scripts/jquery-1.2.1.js"></script>
<script type="text/javascript">
$(function(){
$('fieldset div div').hide();
$(':checkbox').click(function(){
var checked = this.checked;
$('div',$(this).parents('div:first'))
.css('display',checked ? 'block':'none'),
$('input[type=text]',$(this).parents('div:first'))
.attr('disabled',!checked)
.css('color',checked ? 'black' : '#f0f0f0')
.val(1)
.change()
.each(function(){ if (checked) this.focus();});
});
$('span[price] input[type=text]').change(function(){
$('~ span:first',this).text(
$(this).val() *
$(this).parents("span[price]:first").attr('price')
);
});
$('span[price] input[type=text]').change();
});
</script>
</head>

<body>
<h1>Bamboo Asian Grille</h1>
<h2>Online Order Menu</h2>
<fieldset>
<legend>Appetizers</legend>

<div>
<label>
<input type="checkbox" name="appetizers"
value="imperial"/>
Fried Imperials rolls (2)
</label>
<span price="3">
<input type="text" name="imperial.quantity"
disabled="disabled" value="1"/>
$<span></span>
</span>
<div>
<label>
<input type="radio" name="imperial.option"
value="pork" checked="checked"/>
Pork
</label>
<label>
<input type="radio" name="imperial.option"
value="vegetarian"/>
Vegetarian
</label>
</div>
</div>

<div>
<label>
<input type="checkbox" name="appetizers" value="spring"/>
Spring rolls (2)
</label>
<span price="4">
<input type="text" name="spring.quantity"
disabled="disabled" value="1"/>
$<span></span>
</span>
<div>
<label>
<input type="radio" name="spring.option" value="pork"
checked="checked"/>
Pork
</label>
<label>
<input type="radio" name="spring.option"
value="shrimp"/>
Pork and Shrimp
</label>
<label>
<input type="radio" name="spring.option"
value="vegetarian"/>
Vegetarian
</label>
</div>
</div>

<div>
<label>
<input type="checkbox" name="appetizers" value="vnrolls"/>
Vietnamese rolls (4)
</label>
<span price="5">
<input type="text" name="vnrolls.quantity"
disabled="disabled" value="1"/>
$<span></span>
</span>
<div>
<label>
<input type="radio" name="vnrolls.option" value="pork"
checked="checked"/>
Pork
</label>
<label>
<input type="radio" name="vnrolls.option"
value="shrimp"/>
Pork and Shrimp
</label>
<input type="radio" name="vnrolls.option"
value="vegetarian"/>
<label>Vegetarian</label>
</div>
</div>

<div>
<label>
<input type="checkbox" name="appetizers" value="rangoon"/>
Crab rangoon (8)
</label>
<span price="6">
<input type="text" name="rangoon.quantity"
disabled="disabled" value="1"/>
$<span></span>
</span>
<div>
<label>
<input type="radio" name="rangoon.option"
value="sweet checked="checked"/>
Sweet-and-sour sauce
</label>
<label>
<input type="radio" name="rangoon.option" value="hot"/>
Hot mustard
</label>
<label>
<input type="radio" name="rangoon.option" value="both"/>
Both
</label>
</div>
</div>

<div>
<label>
<input type="checkbox" name="appetizers"
value="stickers"/>
Pot stickers (6)
</label>
<span price="5">
<input type="text" name="stickers.quantity"
disabled="disabled" value="1"/>
$<span></span>
</span>
<div>
<label>
<input type="radio" name="stickers.option"
value="pork" checked="checked"/>
Pork
</label>
<label>
<input type="radio" name="stickers.option"
value="vegetarian"/>
Vegetarian
</label>
</div>
</div>

<div></div>

</fieldset>
</body>
</html>

This code is robust in that it’s independent of the number of appetizer entries. You’ll note that nowhere in the JavaScript is it necessary to tell the code what elements correspond to appetizer entries. The power of jQuery selectors allows us to automatically locate them. New appetizer entries can be added at will—as long as they follow the prescribed format—and the code will automatically instrument them along with the previously existing entries.

In many ways, the code could stand some improvements. In the interest of brevity and focusing on the lessons at hand, we took a number of shortcuts that should be fixed before putting any such code into production. The following list details some areas for improvement (or even blatant shortcomings of the code) that you’re encouraged to explore as exercises:

  • As written, the code assumes that users will enter only valid numeric values into the quantity fields. We know better! Add validation that ensures that only valid numeric entries are made. What should you do when an invalid entry is made?
  • When the options for unchecked appetizers are hidden, they are still enabled and will be submitted along with the rest of the visible elements. This is wasted bandwidth and more data for the server-side code to sift through. How would you enable and disable the radio options at appropriate times?
  • The form is incomplete. In fact, without a <form> element, it isn’t a form at all! Complete the HTML to make a valid form that can be submitted to the server.
  • Man does not live by appetizers alone! How would you go about adding new sections for entrees, beverages, and desserts? Banana flambé sounds delightful! How would these new sections affect the setup of the JavaScript code?
  • As diners are selecting (and deselecting) their choices, you could provide a running total of the order amount. How would you go about keeping track of the order total?
  • If the use of custom attributes is not to your liking, refactor the page to eliminate them. But be sure that the price information remains defined in one place only!
  • Perhaps the biggest flaw in the code is that it depends greatly on the positional relationships of the elements in an appetizer entry. This allowed the markup to remain simple but at the expense of both creating a strong binding between the structure of an entry and the supporting code and introducing complex jQuery selectors. How would you go about making the code more robust so that changes to the structure of an entry would have less impact on the code? Adding CSS class names to tag the elements (rather than relying on positional relationships) would be one fine way of accomplishing this; how would you go about it? What other ideas do you have?

If you come up with ideas that you’re proud of, be sure to visit the Manning web page for this book at http://www.manning.com/bibeault, which contains a link to the discussion forum. You’re encouraged to post your solutions for all to see and discuss!

4.4. Summary

Building upon the jQuery knowledge that we’ve gained so far, this chapter introduced us to the world of event handling.

We learned that there are vexing challenges to implementing event handling in web pages, but such handling is essential for creating pages in Rich Internet Applications. Not insignificant among those challenges is the fact the there are three event models that each operate in different ways across the set of modern popularly used browsers.

The legacy Basic Event Model, also informally termed the DOM Level 0 Event Model, enjoys somewhat browser-independent operation to declare event listeners, but the implementation of the listener functions requires divergent browser-dependent code. This event model is probably the most familiar to page authors, and assigns event listeners to DOM elements by assigning references to the listener functions to properties of the elements; the onclick property, for example.

Although simple, this model suffers from a you-only-get-one-shot problem; only one listener can be defined for any event type on a particular DOM element.

We can avoid this deficiency by using the DOM Level 2 Event Model, a more advanced and standardized model in which an API binds handlers to their event types and DOM elements. Versatile though this model is, it enjoys support only by standards-compliant browsers such as Firefox, Safari, Camino, and Opera.

For Internet Explorer 6 and 7, an API-based proprietary event model that provides a subset of the functionality of the DOM Level 2 Model is available.

Coding all event handling in a series of if statements—one clause for the standard browsers and one for Internet Explorer—is a good way to drive ourselves to early dementia. Luckily jQuery comes to the rescue and saves us from that fate.

jQuery provides a general bind() command to establish event listeners of any type on any element, as well as event-specific convenience commands such as change() and click(). These methods operate in a browser-independent fashion and normalize the Event instance passed to the handlers with the standard properties and methods most commonly used in event listeners.

jQuery also provides the means to remove event handlers, causes them to be invoked under script control, and even defines some higher-level commands that make implementing common event-handling tasks as easy as possible.

We explored a few examples of using events in our pages. In the next chapter, we’ll look at how jQuery builds upon these capabilities to put animation and animated effects to work for us.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.33.157