Chapter 4. Interactive Effects

All Ajax effects are interactive; the whole point of Ajax is to make a page more responsive. Some effects, though, are more interactive than others—especially those associated with providing immediate information based on some event.

If you've used either Netflix or Blockbuster Online, you've seen pop-up windows that open with information about a movie when your mouse hovers over the movie link. This is typical of an Ajax interactive effect, where the application interprets your intent. In this case, you can find out more about the movie and possibly add it to your queue.

If you've provided commentary at a site and can preview your effort as you write or before final submission, you are seeing another interactive effect. It gives you a chance to review what you've written, correct misspellings or transposed letters, and clarify sentence structure. This type of functionality isn't a requirement for the application to run; rather, it's an interpretation of what your web page readers may want—in this case, a way to preview the text.

Online applications that provide feedback when you perform an action, such as a red flashing effect when data is deleted, or a yellow flash when an update has occurred, are also interactive effects. None is essential, but they provide the page reader reassurance that an action has happened.

Each of these effects provides a signal that the application is aware of your intentions as well as your actions. It's not the same as clicking a button and having a form submitted, or dragging an item to a shopping cart, both of which are expected and essential behaviors, notable only when they don't happen.

No, the Ajax effects covered in this chapter are the web application's way of saying, "I hear you, I see what you're doing, I think I know what you want." None of the effects are essential, but in the great tool chest that is Ajax, these can be the simplest effects to implement and have the strongest positive impact for readers using your applications.

The effects are all quite different: tooltips, Just-In-Time (JIT) form help, color fades, live previews. However, they all have one thing in common—they are all responses to events. Because of this dependence on events, I'll begin the chapter with a review of event handling, particularly event handling in an Ajax environment where multiple libraries may be combined into one application.

Ajax-Friendly Event Handling

Event handlers have been part of JavaScript from the beginning. Most follow the onclick or onload format, where the handler begins with the keyword on, followed by the event name. Typically, the event handlers are placed within the object to which they're connected:

<body onload="somefunction(  )">

Inline event handlers are still viable approaches for managing event capturing, except for two limitations: maintenance and mashability.

Maintainable Event Handling

If event handlers are added directly to page elements, and later there's a change to the function name or parameters, the web developer has to hunt down every occurrence of the load handler text and modify it. This approach of adding event handling isn't efficient, and with today's more complex and dynamically generated web pages, it's infeasible.

Tip

An exception to this premise is dynamically generated content, where the code to create the content is located in one file. Though the generated content can stretch across many pages, you still only have to make changes in one place.

Rather than adding event handlers to objects, a better approach is to assign event handler functions in a scripting block, either in a separate JavaScript file or as a block of code in the head section of a document. This is known as the DOM level 0 event handling approach:

<script type="text/javascript">
...
document.onclick=somefunction
</script>

This is also a viable solution, but another problem arises when you assign a function to an event handler. In the example just given, assigning someFunction to the onclick event handler for the document object overwrites whatever other assignment has been made before this script is run. If you're incorporating external Ajax libraries or merging multiple Ajax libraries into an application, you can't assume that you "own" the event handler. Your code has to play well with others, unless you want to restrict all code to your code and your code only.

Mashable Event Handling

Another approach, and a recommended one, is to use DOM Level 2 event handling. This technique "chains" event handlers so that when an event fires, all associated functions are run. An example of adding an event handler to the click event for the document is as follows:

document.addEventListener("click",eventHandlerFunction,false);

In the addEventListener method, the first parameter is the event (click) and the second is the event handler function. The third optional parameter determines whether the processing of the event begins in the outermost object, flowing to the inner in a set of nested objects (true), or from the innermost to the outer (false). The first is known as the capturing phase, and the second, the default, emulates the older DOM level 0 event processing by handling the event in the bubble up phase.

To demonstrate both, Example 4-1 shows a page with two div elements, one within the other. A generic event handler routine, manageEvent, is created to attach event handlers to objects, taking as parameters three objects: the target object, the event, and the event handler function.

The first event captured is for the window, in order to assign the click event handler to the two div elements after they're loaded. The window's load event handler then assigns the click event handlers of both div elements to the same function: clickHandler.

Example 4-1. An event handler method that plays well with others

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Event Listening</title>
<style type="text/css">
#inner { width: 400px; height: 100px; background-color: #00f;}
#outer { width: 400px; background-color: #ff0; padding: 10px; }
</style>

<script type="text/javascript">
//<![CDATA[

function manageEvent(evntObject,event,eventHandler) {
   evntObject.addEventListener(event,eventHandler,false);
}


manageEvent(window,"load",
            function (  ) {
   manageEvent(document.getElementById('inner'),"click",clickHandler);
   manageEvent(document.getElementById('outer'),"click",clickHandler);
          });

function clickHandler(  ) {
   alert(this.id);
}
//]]>
</script>

</head>
<body>
<div id="outer">
<div id="inner">
</div>
</div>
</body>
</html>

In the example page, clicking on the innermost div element results in an alert message box that displays the word "inner," followed by another alert message box that displays the word "outer." Setting the third parameter in the manageEvent function to true (setting event handling to capturing phase), clicking on the two div elements results in an alert box with outer, followed by another with the word, "inner."

Capturing the event in the wrong phase can have a significantly negative impact on your application. Since most event handlers are coordinated to work in a bubble up phase, unless you want to capture an event in the capturing phase for a specific reason, you'll usually set the third parameter to false.

Using addEventListener is great, except for one thing: Microsoft's Internet Explorer (6.x and 7) doesn't support it. This browser supports another function—attachEvent:

document.attachEvent("onclick",eventHandlerFunction);

There are two differences between attachEvent and addEventListener. The first is that in attachEvent, you pass the event handler name, not the event, as the first parameter (onclick as compared to click). The second is that attachEvent doesn't have a third parameter because it always handles events in the bubble up phase.

To ensure that event handling works with IE 6.x/7 and other browsers, you'll need to incorporate both types of event handler methods, the W3C DOM Level 2 method and Microsoft's. This means you'll have to test to see whether a specific event listener is supported, and code accordingly. Modifying the manageEvent function to work with IE as well as other browsers results in the following:

function manageEvent(eventObj, event, eventHandler) {
   if (eventObj.addEventListener) {

      eventObj.addEventListener(event, eventHandler,false);
   } else if (eventObj.attachEvent) {
      event = "on" + event;
      eventObj.attachEvent(event, eventHandler);
   }
}

The event object is tested to see whether addEventListener is a property associated with the object. If it is, it's used as the event handler dispatch method; otherwise, attachEvent is checked, and it's used if found.

If the user agent doesn't support addEventListener or attachEvent, the application can then fall back to the older DOM level 0 event handler, onclick. This isn't as critical a need anymore, as browsers that can't support some form of advanced event handling have fallen more or less out of use.

To stop listening to an event, use the comple mentary methods, removeEventListener and detachEvent:

function stopManagingEvent(eventObj,event,eventHandler) {
   if (eventObj.removeEventListener) {
      eventObj.removeEventListener(event,eventHandler,false);
   } else if (eventObj.detachEvent) {
      event = "on" + event;
      eventObj.detachEvent(event,eventHandler);
  }
}

In the cases where you want to cancel an event, you'll need to stop the event propagation for the Mozilla browsers, but cancel the bubble up for IE. In addition, you'll need to stop the default event handling, which you do by using preventDefault with Mozilla/W3C browsers, and by setting the returnValue to false for IE. The following cancelEvent method should take care of this:

function cancelEvent(event) {
   if (event.preventDefault) {
      event.preventDefault(  );
      event.stopPropagation(  );
   } else {
      event.returnValue = false;
      event.cancelBubble = true;
   }
}

Event management functions are used so frequently, you'll want to add them to whatever libraries you use. I added them to this book's Ajax library as aaManageEvent, aaStopEvent, and aaCancelEvent, prepending the functions with "aa" so they won't conflict with other libraries you might be using.

If you use external libraries in your Ajax applications, most of them provide event handling. The next section briefly introduces the event handling system from one of them, Dojo's Event System.

The Dojo Event System the Target Object

Most Ajax libraries provide some form of event handling functionality. For instance, the Dojo library includes the Dojo Event System, which simplifies event handling considerably—simplifies and adds its own little tricks. and

In Dojo, to support the same functionality as in our last section—that is, to attach an event listener to catch the window load event and the clicks for the two div elements—all you need to do is use the connect method for the dojo.event object:

dojo.event.connect(obj,"onclick",eventHandler);

It's similar to the function shown in Example 4-1, except that Dojo's event system goes beyond the simple manageEvent. For one thing, Dojo manages browser differences when it comes to passing the event object to the function. Though not demonstrated in Example 4-1, typically if you want to access the event object in an event handler, and do so in a cross-browser manner, you must use code similiar to the following in the first line of the event:

function eventHandler(evnt) {
   var event = evnt || window.event;
...
}

Or:

function eventHandler(evnt) {
   var event = evnt ? evnt : window.event;
...
}

If the event object passed to the evnt method exists, that object is assigned to the event variable; otherwise, you can get the event object from the window object. This is, again, a technique to make the functionality compatible with IE 6.x and 7, which doesn't pass the event object to the event handler function automatically.

With the Dojo event system, the library handles this for you, so you can assume you'll always have the event object as a parameter in the event handler. Example 4-2 shows a modified version of Example 4-1, using Dojo and the event object.

Example 4-2. Event handling à la Dojo

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Event Listening</title>
<style type="text/css">
#inner
{
    background-color: #00f;
    height: 100px;

    width: 400px;
}
#outer
{
    background-color: #ff0;
    padding: 10px;
    width: 400px;
}
</style>

<script type="text/javascript" src="dojo/dojo.js">
</script>

<script type="text/javascript">
//<![CDATA[

dojo.require("dojo.event.*");

dojo.event.connect(window,"onload",
                    function setUp(  ) {
   dojo.event.connect(document.getElementById('inner'),"onclick",clickHandler);
   dojo.event.connect(document.getElementById('outer'),"onclick",clickHandler);
   });

function clickHandler(evnt) {
   alert(evnt.currentTarget.id);
}
//]]>
</script>

</head>
<body>
<div id="outer">
<div id="inner">
</div>
</div>
</body>
</html>

In this example, the identifier of the element is accessed through the event object rather than through the object context, represented by this. The advantage to this approach is that the use of the event object is cross-browser compatible. If you use this.id with a browser like IE 7, you'll get a value of undefined rather than the identifier for the object that was impacted by the event.

If you want to stop listening to an event, use dojo.event.disconnect, using the same parameters you used with connect.

Of course, including Dojo just to do simple event management is equivalent to using a machete to open a package of bologna: it's serious overkill. However, the example does demonstrate that when using an external Ajax library, you're going to want to look to see whether it has event handling, and if so, use it rather than your own library. The main reason is to ensure compatible event handling between the external library and your application code as much as possible.

There's another reason I picked Dojo to demonstrate using packaged event management. Dojo goes beyond just handling cross-browser differences with events—it also provides a way of extending the event system so that you can attach invocation event handlers to the functions themselves. These invocation event handler functions are called when their associated functions are processed.

The reason for such an extension is that Dojo, unlike many other Ajax libraries, has an infrastructure that is meant to be built upon via plug-ins, all of which coexist with the library. The other libraries are architectures on which applications are built. If a new Dojo plug-in has a function that has to be called whenever another Dojo function is called, it subscribes to that function.

The web page in Example 4-3 consists of the same two div elements in Example 4-1 and Example 4-2. In Example 4-3, though, when the inner block gets a click event, the inner block's color is changed. When this happens, we also want to change the color of the outer block. In the application code, a new object is created with two methods, change1 and change2, which change the inner and outer blocks, respectively. The application uses the Dojo event system to register the first object method, change1, as an "event publisher," assigning it the topic name of "/example". The code then uses the dojo.event.topic.subscribe method to add a subscriber to this new topic publisher, so that when the first method, change1 fires, the second object method, change2, also fires.

Example 4-3. Exploring publisher/subscribe in the Dojo Event System

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Event Listening</title>
<style type="text/css">
#inner
{
    background-color: #00f;
    height: 100px;
    width: 400px;
}
#outer
{
    background-color: #ff0;
    padding: 10px;
    width: 400px;
}
</style>

<script type="text/javascript" src="dojo/dojo.js">
</script>

<script type="text/javascript">
//<![CDATA[

dojo.require("dojo.event.*");

var connObj = {
   change1 : function (  ) {
             document.getElementById('inner').style.backgroundColor="#ff0000";
            },
   change2 : function (  ) {
             document.getElementById('outer').style.backgroundColor="#ff00ff";
            }};

dojo.event.topic.registerPublisher("/example", connObj, "change1");

dojo.event.topic.subscribe("/example", connObj, "change2");

dojo.event.connect(window,"onload", function(  ) {
    dojo.event.connect(document.getElementById('inner'),"onclick",connObj.change1);
   });

//]]>
</script>

</head>
<body>
<div id="outer">
<div id="inner">
</div>
</div>
</body>
</html>

When accessing the example page and clicking on the inner block, its color is changed from blue to red, as you would expect because of the event handler function assigned directly to the click event for the block. However, the outer block's color also changes from yellow to magenta because of the topic publisher/subscriber functionality added through the Dojo event system. It's a simple demonstration of a very powerful functionality—one that is created specifically for extending Dojo with custom widgets.

It's an intriguing infrastructure, and one to be aware of if using Dojo. Additionally, keep it in mind for your own libraries if you need this type of pluggable infrastructure.

Now, on with the Ajax interactive effects.

Get Adding Ajax now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.