O'Reilly logo

Adding Ajax by Shelley Powers

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

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.

Just-In-Time Information

Pages are interactive when they’re responsive to actions from the web page reader. The interaction could be clicking a button or link, dragging a div element, or filling in a form. Most of the time, the page context provides enough information for readers to figure out what to do, but not always. For example, web page form fields with labels such as “last name” may be easy to figure out, but others, such as “Enter fiduciary amount” may not.

Printing out detailed information within the page, however, can be messy, and forcing folks to go to another page is a pain. Opening new windows may help, but they separate the action from the object. That’s where just-in-time (JIT) help comes in.

In JIT help, typically a box is displayed with text describing the item or providing additional instructions. The box could open at the point of focus or at a standard place on the page regardless of the element getting focus. The help is displayed based on actions, such as hovering the mouse over a form field.

Form Help

Form element labels can be self-explanatory, such as “first name” or “age,” but every once in a while you come across one like “Post Slug” (from Wordpress, a blogging application) that leaves people scratching their heads as they check through the help documentation. Rather than sending people to a separate page, a better approach would be to add information into the page itself. Unfortunately, though, there may not be enough room. The Wordpress edit page is a good example of too many form elements and too little space.

The simplest help for forms is to capture the focus events for each form element and then display the appropriate description text block. Rather than guessing which elements might need help, assume they all do, and provide for each one. The information can be preloaded into the page or pulled up from a web service using an Ajax call.

Example 4-4 shows a simple application of JIT form help. Since the application is so small, I’ve combined the script, stylesheet, and web page elements into one file. The code for the page attaches an onfocus event handler for each form element, including the submit button. When the form element receives the focus event. Its name is sent as part of a web service query using XMLHttpRequest. The web service returns the element’s definition, which is then displayed in a separate page element, a div element identified as “help” using the innerHTML property.

Example 4-4. JIT onfocus form help
<!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" xml:lang="en" lang="en">
<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Form Field Help</title>
<style type="text/css">
#help
{
    left: 300px;
    padding: 10px;
    position: absolute;
    top: 20px;
}
form
{
    margin: 20px;
}
input
{
    margin: 10px;
}
</style>
<script type="text/javascript" src="addingajax.js">
</script>
<script type="text/javascript">
//<![CDATA[

var xmlhttp;

aaManageEvent(window, 'load', function(  ) {
   for (var i = 0; i < document.forms[0].elements.length; i++) {
      aaManageEvent(document.forms[0].elements[i],"focus",showHelp);
   }
});

function showHelp(evnt) {
  evnt = (evnt) ? evnt : window.event;
  if (!xmlhttp)  xmlhttp = getXmlHttpRequest(  );
  if (!xmlhttp) return;
   var objId = (evnt.currentTarget) ? evnt.currentTarget.id : evnt.srcElement.id;
  var qry = "item=" + objId;
  var url = 'help.php?' + qry;
  xmlhttp.open('GET', url, true);
  xmlhttp.onreadystatechange = printHelp;
  xmlhttp.send(null);
}

function printHelp(  ) {
   if(xmlhttp.readyState == 4 && xmlhttp.status == 200) {
      document.getElementById('help').innerHTML=xmlhttp.responseText;
   }
}

//]]>
</script>


</head>
<body>
<form action="ch04-04.htm" method="post">
<fieldset>
<legend>Personal info</legend>
<label for="firstname">First Name:</label><br />
<input id="firstname" name="firstname" type="text" /><br />

<label for="lastname">Last Name:</label><br />
<input id="lastname" name="lastname" type="text" /><br />

<input type="submit" value="Save" />
</fieldset>
</form>
<div id="help">
</div>
</body>
</html>

This is a very simple approach to dynamically providing help. With just the two fields, neither of which really need additional explanation, you wouldn’t normally use this approach, but the simplicity does provide a clear demonstration. The server component of the Ajax application is equally as simple:

<?php
//If no search string is passed, then we can't search
$item = $_REQUEST['item'];
if(empty($item)) {
    echo "";
} else {
    //Remove whitespace from beginning & end of passed search.
    $search = trim($item);
    switch($item) {
      case "firstname" :
         $result = "<p>Enter your first name</p>";
         break;
      case "lastname" :
         $result = "<p>Enter your last name</p>";
         break;
      default :
         $result = "";
         break;
      }

     echo $result;
}
?>

However, if you have several forms spread out over several pages, providing the same help system to all of the pages allows you to make modifications in the help text in one place instead of in each of several different pages. This approach then becomes much more viable. Additionally, if the application caches the lookup information when it’s retrieved the first time, the connections to the server are decreased:

function printHelp(  ) {
   if(xmlhttp.readyState == 4 && xmlhttp.status == 200) {
      document.getElementById('help').innerHTML=xmlhttp.responseText;
      helpObj[helpItem] = xmlhttp.responseText;
   }
}

With the caching modification, the application tests for the existence of the help item in the associative array cache.

Another improvement is to make the help more “on-demand,” rather than putting it up only when the page reader enters a field. One way to do this is to capture the click event for the labels rather than the focus event for the fields, and display help when the labels are clicked:

function setUp(  ) {
   var items = document.getElementsByTagName('label');
   for (var i = 0; i < items.length; i++) {
       aaManageEvent(items[i],"click",showHelp);
   }
}

For this approach to work, the labels are given identifiers, rather than the form fields:

<form action="ch04-05.htm" method="post">
<label id="elem1">First Name:</label><br />
<input type="text" /><br />
<label id="elem2">Last Name:</label><br />
<input type="text" /><br />
<input type="submit" Value="Save" />
</form>

A problem with using the labels is there’s no way to know that they’re clickable. One way around this is to add a mouseover effect to the label, setting the mouse pointer to a question mark. Do this by adding a CSS stylesheet setting to change the mouse pointer to the help cursor (typically a question mark) whenever it is placed over a label:

label { cursor: help; }

Of course, you still have to know to move your mouse over the label to get the cursor. Plus, you have to be using a mouse to realize that the label has a different cursor, and you must have script enabled.

A more unobtrusive and intuitive approach would be to surround the labels with a hypertext link, set the target attribute to a new window name, and display the help based on the mouseover event rather than the click. If scripting is enabled, the mouseover displays the help. If scripting is not enabled, the hypertext link opens a separate window with the help information. This change makes the JIT help “keyboard-enabled” and more accessible for screen readers.

There are two problems with the link workaround. One is that XHTML 1.0 Strict does not support the target attribute. The other is that you need to warn folks using a screen reader or other assisting device that they will open a new window if they click the link.

You could avoid both of these problems by opening the help within the same page, but that is not a good solution for providing help in a form, especially a form that has been partially filled in. A popular approach to this problem is to attach a rel="external" key/value attribute pair to the links to open in new windows and use JavaScript to create a new window when the link is clicked. However, one of the primary reasons for having the help open in a new window is because scripting is disabled.

There are also people who will want to click the link anyway because they’re using screen readers that may not pick up the help that’s generated dynamically. Again, we don’t want to overwrite their half-filled form. For them, we could either decide to blow off the XHTML 1.0 Strict validation (tempting), or create the JavaScript to intercept the click and open the new window.

I can’t tell you which is the best option, as only you can determine what’s important for your applications and your clients. For now, I’ve extended the addingajax.js file to add the link interception routine based on the rel="external" attribute, which at least gives us this option. Example 4-5 shows the new library entries, including the window onload event handler to process the links.

Example 4-5. New library entries to provide an XHTML variation of the anchor target attribute
function externalLinks(  ) {
 var anchors = document.getElementsByTagName("a");
 for (var i=0; i<anchors.length; i++) {
   var anchor = anchors[i];
   if (anchor.getAttribute("href") &&
       anchor.getAttribute("rel") == "external")
     anchor.target = "_blank";
 }
}

aaManageEvent(window,'load',externalLinks);

Tip

A third option to the XHTML Strict and target problem is to extend the XHTML by creating a custom DTD. For discussion of this, see Wayne Burkett’s weblog post, “Extending XHTML: Target and Strict,” at http://dionidium.com/2004/05/xhtml-tests.

Though the page’s static contents now validate, this approach does add invalid markup dynamically. A second way to code this function, one that bypasses the validation issue completely, is to use the following with each anchor:

aaManageEvent(anchor,'click',function(  ) {
      window.open(this.href);
      aaCancelEvent(event);
});

This code opens a new window without adding invalid markup statically or dynamically. This approach does require accessing the event object in order to process the cancel event method call.

While we’re improving the application, we can add some frills to our help to make it seem a little more polished. We will change the background color and add a border. The stylesheet is moved into its own file, jit.css:

#help
{
    background-color: #FFFF8F;
    border: 1px solid #82887e;
    left: 300px;
    padding: 10px;
    position: absolute;
    top: 20px;
    visibility: hidden;
}
form
{
    margin: 20px;
        width: 500px;
}
input
{
    margin: 10px;
}
label
{
    cursor: help;
}
a
{
    text-decoration: none;
}

The JavaScript is also pulled into a separate file, jit.js. The new script, shown in Example 4-6, adds caching and event management on the labels. The JavaScript to show the help is split out into a separate function and called from both the XMLHttpRequest processing function, as well as the showHelp function—if the element’s help is cached.

Example 4-6. The Just-In-Time form help using Ajax
var xmlhttp;
var helpItem;
var helpObj = new Object(  );

aaManageEvent(window,"load", function(  ) {
   var items = document.getElementsByTagName('label');
   for (var i = 0; i < items.length; i++) {
       aaManageEvent(items[i],"mouseover",showHelp);
       aaManageEvent(items[i],"mouseout",hideHelp);
   }
});

// retrieve the help info
function showHelp(evnt) {
  evnt = (evnt) ? evnt : window.event;
  // get XMLHttpRequest object if not set
  if (!xmlhttp) xmlhttp = aaGetXmlHttpRequest(  );
  if (!xmlhttp) return;

  helpItem = (evnt.currentTarget) ? evnt.currentTarget.id : evnt.srcElement.id;
  var qry = "item=" + helpItem;

  // if cached item, print existing and return
  if (helpObj[helpItem]) {
      printHelp(  );
      return;
  }

  // invoke help system
  var url = 'help.php?' + qry;
  xmlhttp.open('GET', url, true);
  xmlhttp.onreadystatechange = getHelp;
  xmlhttp.send(null);
}

// hide help field
function hideHelp(  ) {
   document.getElementById('help').style.visibility="hidden";
}

// display help field with help
function getHelp(  ) {
   if(xmlhttp.readyState == 4 && xmlhttp.status == 200) {
      helpObj[helpItem] = xmlhttp.responseText;
      printHelp(  );
   }
}
function printHelp(  ) {
  var help = document.getElementById('help');
  help.innerHTML = helpObj[helpItem];
  help.style.visibility="visible";
}

In the web page, the CSS and JavaScript files are linked in, and the form labels are wrapped in hypertext links with the individual help item attached to the URL for each, as shown in Example 4-7. The links also have an explicit title with clear information that clicking the links will open a second window, in addition to the rel="external" attribute/value.

Example 4-7. JIT help page—valid, accessible, and active
<!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>Form Field Help</title>
<link rel="stylesheet" href="jit.css" type="text/css" media="screen" />
<script type="text/javascript" src="addingajax.js">
</script>
<script type="text/javascript" src="jit.js">
</script>
</head>
<body>

<form action="ch04-05.htm" method="post">
<fieldset>
<legend>Personal info
<a href="help.php?item=firstname" " title="opens help window for firstname field"
rel="external">
<label id="firstname" for="first">First Name:</label></a><br />
<input type="text" id="first" name="first" /><br />

<a href="help.php?item=lastname" accesskey="l" title="opens help window for
lastname field" rel="external">
<label id="lastname" for="last">Last Name:</label></a><br />
<input type="text" id="last" name="last" /><br />

<input type="submit" value="Save" />
</fieldset>
</form>

<div id="help">
</div>
</body>
</html>

Figure 4-1 shows the page with one of the help items displayed.

A form that uses JIT help
Figure 4-1. A form that uses JIT help

Lots of code for a simple effect, but the pieces are in place to make this into a library of JavaScript functions to provide JIT help for more than forms. JIT help is almost equivalent to another effect known as the tooltip. All that remains now is expanding the functionality to other objects and positioning the displayed material where the event occurs.

Tooltips

As I stated in the beginning of this chapter, if you’ve been out to the Netflix or Blockbuster Online sites, you’ve seen how moving the mouse over a movie-related link or image pops up information about the movie, as well as a link to add the movie to the queue. This is probably one of the best uses of tooltips I’ve seen. It quickly provides more detailed information about an object rather than forcing users to go to another page. This is especially advantageous if the site user is browsing through many items. Tooltips can be used for anything: a shop, getting more detailed information about a row of data returned from a database, camera information associated with a photo, and so on—anytime you want to provide information about an object within a browsing context.

The only modifications required to convert the JIT application in the prevous section into a tooltip is to provide a bubble-like background for the text and position it so that it’s close to where the mouseover event occurs. Sounds simple, but it begins to add to the amount of code.

The following web page fragment is very similar to that shown in Example 4-7, with the addition of two more page elements: an h1 header and a standalone link. These are added to demonstrate that any page element can get a tooltip, not just form elements:

<body>

<h1 id="header">Testing Tooltips</h1>

<form action="ch04-08.xhtml" method="post">
<fieldset>
<legend>Personal info</legend>
<a href="help.php?item=firstname" accesskey="f" title="opens help window for
firstname field" rel="external">
<label id="firstname" for="first">First Name:</label></a><br />
<input type="text" id="first" name="first" /><br />

<a href="help.php?item=lastname" accesskey="l" title="opens help window for
lastname field" rel="external">
<label id="lastname" for="last">Last Name:</label></a><br />

<input type="text" id="last" name="last" /><br />

<input type="submit" value="Save" />
</fieldset>
</form>
<p><a href="" id="link">Tooltip for a hypertext link</a></p>
<div id="help">
</div>
</body>

In the stylesheet, the tooltip help block is set to a bubble background:

#help
{
    background-image: url(back.png);
    background-repeat: no-repeat;
    height: 200px;
    padding: 10px 0 0 10px;
    position: absolute;
    visibility: hidden;
    width: 150px;
}

The most significant change to the new application is in the JavaScript. For each element that has a tooltip, the mouseover event is captured in order to display the tip. The mouseout event is captured to then hide it. When the mouseover event fires, the mouse cursor position is captured so that the help element can be moved to approximately the same page location. Its position is also modified relevant to the bubble size, to put the pointed part of the “bubble” as close to the cursor position as possible. The only variation is with the top element. Since it’s at the top of the page, the vertical position of the tooltip is adjusted so that the tip’s top won’t go beyond the page top.

Example 4-8 shows the new JavaScript file.

Example 4-8. Providing tooltips for form and other elements
var xmlhttp;                // global XMLHttpRequest obj
var helpItem;               // current help item
var helpObj = new Object(  ); // cached help items
var posX; var posY;

// setup tooltip event
function addTooltip(ttObj) {
   aaManageEvent(ttObj,"mouseover",showHelp);
   aaManageEvent(ttObj,"mouseout",hideHelp);
}

// attach tooltip events to objects
aaManageEvent(window,"load",function(  ) {
   var items = document.getElementsByTagName('label');
   for (var i = 0; i < items.length; i++) {
      addTooltip(items[i]);
   }

   addTooltip(document.getElementById('title'));
   addTooltip(document.getElementById('link'));
});
// get help from the server
function showHelp(evnt) {
  evnt = (evnt) ? evnt : window.event;

  // get position
  posX = evnt.clientX;
  posY = evnt.clientY;

  // get XMLHttpRequest object if not set
  if (!xmlhttp) xmlhttp = aaGetXmlHttpRequest(  );
  if (!xmlhttp) return;

  helpItem = (evnt.currentTarget) ? evnt.currentTarget.id : evnt.srcElement.id;
  var qry = "item=" + helpItem;

  // if cached item, print existing and return
  if (helpObj[helpItem]) {
      printHelp(  );
      return;
  }

  // invoke help system
  var url = 'help.php?' + qry;
  xmlhttp.open('GET', url, true);
  xmlhttp.onreadystatechange = getHelp;
  xmlhttp.send(null);
}


// hide help bubble
function hideHelp(  ) {
//I would suggest changing the class name instead of directly manipulating
style properties//
   document.getElementById('help').style.visibility="hidden";
}
// display help
function getHelp(  ) {
   if(xmlhttp.readyState == 4 && xmlhttp.status == 200) {
      helpObj[helpItem] = xmlhttp.responseText;
      printHelp(  );
   }
}

//position tooltip
function printHelp(  ) {
  var help = document.getElementById('help');
  help.innerHTML = helpObj[helpItem];
  y = posY - 130;
  if (y < 0) y = 10;

  help.style.top = y + "px";
  help.style.left = (posX + 10) + "px";
  help.style.visibility="visible";
}

View the tooltips in action in Figure 4-2.

Tooltips in action, providing users with extra information
Figure 4-2. Tooltips in action, providing users with extra information

Again, it seems a lot of code to create a simple effect, but it’s also code that can be easily repackaged for use in a separate library. All you have to do is provide a way to create the tooltip object, passing in the location and content, and the tooltip object takes care of the rest. You could package more of the functionality, passing in just an element, and the tooltip object can take care of the event handling, though you’ll most likely need to provide the content unless you embed the contents as elements in the page rather than pulling it in from a web service.

Tip

An even simpler approach is to use an existing tooltip library. One such library is Tooltip.js, which is based on Prototype and script.aculo.us. You can download the most recent version of the library at http://tooltip.crtx.org.

For effects such as those found on Netflix and Blockbuster, you’ll need to provide an image that has a transparent background. The only way to do this is to use a transparent GIF or a PNG image, though the latter doesn’t work that well with IE (not even with IE 7, which adds some odd color effects). You’ll have to layer your effect, providing a header and footer image, and a separate body image that can repeat along the vertical axis so that it can be resized based on the length of the contents.

One other important item to remember when providing this type of functionality is that tooltips don’t work if scripting is disabled. However, one workaround is to provide hypertext links around the element to open a separate page and use anchors for individual help items.

In-Page Previews

Sometimes the simplest technology can have the biggest impact. Of all the changes I’ve ever made at any of my sites, the one that people liked the best was also one of the simplest to implement: live preview.

Live preview echoes what a user writes as she types. It allows the user to see how the writing looks in the context in which it will be published, rather than within little windows in small forms. It does so before she submits the new or modified material. It’s also quite easy to implement a nonscript and/or accessible alternative: you provide a preview page where a user can review the writing before it’s permanently submitted. You can even provide both: live preview and a Preview button.

Where is live preview most useful? Anytime you ask for commentary from the web page reader. This includes email messages to be sent to customer service, comments on weblogs, feedback in product reviews—anyplace a user is writing more than a few words.

Live preview consists of listening for any activity in a form field, capturing the keys pressed, and echoing them to another page element. How you echo depends on how active you want the preview to be.

You can echo the letters as they’re typed for a true “live” experience. You can also provide a Preview button, but rather than opening up a new page, it will display the comment directly in the page. This approach doesn’t capture the keypress, and the data from the comment field is accessed only when the Preview button is activated.

Both approaches have good points and bad, which I’ll get into as we look at each individual approach.

Live Echo Preview

A live, echoed preview is relatively simple to set up. You begin by capturing the keyup event in either a form textarea or input element, and take all of the text and reflect it in an associated div element through the innerHTML property.

In the code, you do need to adjust the text, replacing the hard carriage returns with a break element, br. This keeps the text more or less in sync with the input.

Example 4-9 includes a complete application that uses live preview. Since the example is so small, the stylesheet and script are both included within the web page. The previewed text isn’t modified other than transforming the carriage break into a br. If you want to modify the text preview in other ways, such as stripping HTML tags to reflect what the comment will look like published, you’ll need to adjust the script accordingly. Remember, though, to keep the preview simple—the function to live preview the input is called whenever a keyup event happens in the form element.

Example 4-9. Simple live preview
<!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>Live Preview</title>
<style type="text/css">
#preview
{
    background-color: #ccc;
    margin: 20px;
    padding: 10px;
    width: 500px;
}
input
{
    margin-right: 10px;
}
</style>
<script type="text/javascript" src="addingajax.js">
</script>
<script type="text/javascript">
//<![CDATA[

manageEvent(window,"load",function(  ) {
            aaManageEvent(document.getElementById('comment'),"keyup",echoPreview)});

// echo the keypresses
function echoPreview(evnt) {
   var commentText = document.getElementById("comment").value;
   modText = commentText.split(/\n/).join("<br />");
   var previewElem = document.getElementById("preview");
   previewElem.innerHTML = modText;
}
//]]>
</script>
</head>
<body>
<form action="preview.htm" method="post">
<div>
<textarea id="comment" cols=50 rows=10></textarea><br />
<input type="button" value="Preview" />
<input type="submit" value="Save" />
</div>
</form>

<div id="preview">
</div>
</body>
</html>

Whatever is typed into the textarea is reflected in the preview element.

The form also has Preview and Save buttons, both of which go to the preview.php page. From there, a user can save the comment or click the Back button to return to the form and continue editing. Neither requires script. Later on in this chapter, we’ll see how we can submit the form and display the new comment, all without leaving the page.

There’s one major problem with live preview, and that’s what happens when the page is served with an XHTML MIME type. The way that browsers implement innerHTML differs among applications, though most do support some version of innerHTML. However, what happens during live preview if you begin to add an XHTML tag differs dramatically between browsers. In Firefox 2.x and up, the incomplete tag throws dozens of errors until it’s completed, all of them saying the text is not well-formed XML. In Opera, however, the browser treats the incomplete tag as escaped content until the tag is closed, in which case, it then treats it as XHTML.

Tip

A workaround is to escape HTML characters, <, >, and &, replacing the modText generation with the following:

 modText = commentText.replace(/&/g, '&amp;').replace(/>/g,
> '&gt;').replace(/</g, '&lt;').split(/\n/).join("<br/>");

This disables the links and other HTML. In nontrusted situations, you may want to consider completely stripping out all HTML.

The differences in behavior between browsers is significant enough that you’ll probably want to use live preview only with pages served as HTML, at least until the new HTML 5.0 standard comes out, which should clarify how a document fragment is managed. In the meantime, if you want XHTML and an in-page preview, I’d suggest you use the “Ajax” version of comment preview.

Tip

There are many implementations of live preview online, but I first saw it via a Wordpress plug-in created by Chris Davis at http://www.chrisjdavis.org.

Ajax Preview

The Ajax preview version of live preview doesn’t echo the preview text as it occurs; rather, it takes all of the text when a Preview button is clicked, but instead of bringing up a separate preview page, the text is echoed in the preview area.

The advantage to this method is that the application isn’t as CPU-intensive, since it doesn’t have to catch all of the keyup events. It’s also much friendlier from an XHTML perspective since the input can be formatted for proper XHTML handling before it’s displayed. Live preview is just that: live. No real formatting can be done other than to strip out markup.

The page demonstrating Ajax preview is identical to that shown in Example 4-9 except for a few elements in the script and the addition of an identifier on the Preview button. Example 4-10 shows the page with these modifications highlighted.

Example 4-10. Using Ajax preview on comments
<!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>Ajax Preview</title>
<style type="text/css">
#preview
{
        background-color: #ffc;
        margin: 20px;
        padding: 10px;
        width: 500px;
}
input
{
        margin-right: 5px;
}
form
{
        margin: 10px;
        width: 550px;
}
</style>
<script type="text/javascript" src="addingajax.js">
</script>
<script type="text/javascript">
//<![CDATA[

aaManageEvent(window,"load",function(  ) {
     aaManageEvent(document.getElementById('previewbutton'),'click',showPreview)});

// echo keypress
function showPreview(evnt) {

   // cancel button's default click behavior
   evnt = evnt ? evnt : window.event;
   aaCancelEvent(evnt);

   // add preview

   var commentText = document.getElementById("comment").value;
   modText = commentText.split(/\n/).join("<br />");
   var previewElem = document.getElementById("preview");
   previewElem.innerHTML = modText;
}
//]]>
</script>
</head>
<body>
<form action="preview.php" method="post">
<fieldset>
<legend>Comment</legend>
<label for="comment">Comment:</label><br />
<textarea id="comment" name="comment" cols="50" rows="10"></textarea><br />
<input type="submit" name="button" value="Preview" id="previewbutton" />
<input type="submit" name="button" value="Save" /> // Should these buttons not
have different values for name? //
</fieldset>
</form>
<div id="preview">
</div>
</body>
</html>

The event that triggers the preview now is a click of the Preview button. To prevent the default behavior, submitting the form, from occurring, the event is terminated within the event handler function.

With Ajax preview, embedding XHTML elements in the comment won’t trigger an error while the text is in an incomplete state like it does with Firefox 2.x (at least, at the time of this writing).

However, if you put in “bad” XHTML and you’re using a browser that checks the content with innerHTML, you will get JavaScript errors when you do assign the text to the innerHTML element. Throwing an error isn’t a bad thing, but throwing a JavaScript error is.

A better approach would be to place the code setting the innerHTML element within a try...catch block and provide an error message meant for the web page reader, not the developer:

   modText = commentText.split(/\n/).join("<br />");
   var previewElem = document.getElementById("preview");
   try {
      previewElem.innerHTML = modText;
   } catch(err) {
      previewElem.innerHTML = "<p>An error occurred, please check your
comment for (X)HTML errors.</p>";
   }

Opera will just correct (or tolerate) the bad markup, as will Safari, IE, and OmniWeb, but Firefox and WebKit both trigger the error handling. Unfortunately, there’s no way to pinpoint the error—not unless we want to attempt to create an XML document and then see what happens—a rather drastic approach, and one that goes way beyond the usefulness of this effect. A better technique is to avoid all errors by stripping out the markup and providing buttons to allow formatting of hypertext links and such.

Tip

Again, if you don’t want the risk of XHTML in the comments, either escape the HTML characters or strip tags out altogether.

Another nice effect you can use with commentary and the like, especially if all comments are listed in the page, is using Ajax to update the data store and then “refreshing” the list without having to refresh the page. Throw in a color fade, and you’ve got a nice bit of polish without too much code.

Color Fades for Success or Failure

One of the more popular Ajax effects is the color flash or fade (to differentiate the effect from the Adobe functionality) that signals some form of successful (or not) update. Usually these are associated with data updates, but they can be used for any activity where you want to signal to the application user to pay attention that something is happening.

A fade changes the background color of an element or group of elements, moving from a darker shade to a lighter, and typically back again. The fade can consist of variations of one color, such as flashing red to signal a deletion, or a yellow fade to create a highlight. Multiple colors can also be used, for instance, a blue to yellow fade to signal a positive outcome.

Regardless of the exact effect used, one thing all color fades require is the use of timers to create the necessary animation. Before getting into the code necessary to implement a fade, we’ll do a quick refresher on timers and animation.

Tip

If you’re comfortable with your understanding of timers and animations, feel free to skip the next section.

Timers and Animations

JavaScript includes a couple of different ways of controlling animations. One is to use the setTimeout method, which is invoked once and has to be reset if multiple timer events are needed. The other is setInterval, which refires in consecutive intervals until canceled. However, setTimeout is used when different parameters are being passed to the timer function with each iteration, and as such, is the one most popularly used with animations such as a color fade.

The setTimetout method takes two parameters: a function or expression as the first, and the number of milliseconds before the timer function/expression is invoked for the second. The last parameter is relatively simple, but the first has undergone a significant metamorphosis through the generations of JavaScript, from simple uses in early DHTML applications to the more esoteric uses in Ajax.

Example 4-11 demonstrates one way that setTimeout was used in earlier iterations of JavaScript applications. The timer function does a countdown starting at 10 and ending when the count is set to zero. An element, item, is accessed with each iteration, and its innerHTML property is used to rewrite the page section. In the setTimeout call, the timer function and the count argument are given in the first parameter, the time interval, before next firing in the second. Straight, simple, and uncomplicated.

Example 4-11. Straight, simple, and uncomplicated timer function
<!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>Old Timers</title>

<style type="text/css">
#item
{
        font-size: 72px;
        margin: 70px auto;
        width: 100px;
}
</style>
<script type="text/javascript" src="addingajax.js">
</script>
<script type="text/javascript">
//<![CDATA[

aaManageEvent(window,"load",function(  ) {
   setTimeout('runTimer(10)',100);
});

function runTimer(count) {
   if (count == 0) return;
   document.getElementById('item').innerHTML=count;
   count--;
   setTimeout('runTimer(' + count + ')',1000);
}


//]]>
</script>
</head>
<body>
<div id="item">
10
</div>
</body>
</html>

The approach is simple, but an issue with it is that using object methods rather than discrete functions means the timer doesn’t have a way of passing the object’s context along with the method, as the parameter to setTimeout.

With the increased interest in Ajax, and especially through the development of libraries such as Prototype, the look of setTimeout has changed significantly—enough to make it difficult to understand exactly what’s happening. The next section looks at Prototype’s use of setTimeout, and then implements the same functionality separate from the library to demonstrate the Ajaxian influence on timers and timer event handlers.

Ajaxian Timers

Prototype implements a method called bind, which is attached to the Function object through the JavaScript prototype property. A quick reminder: the prototype property is a way of attaching a new method or property to the basic implementation of an object in such a way that all instances of that object “inherit” the extension equally. In the case of Prototype’s bind, this method returns a function that in turn calls the Function object’s apply method, passing in a string of the outer function’s arguments. The original code looks like the following:

Function.prototype.bind = function(  ) {
  var _  _method = this, args = $A(arguments), object = args.shift(  );
  return function(  ) {
    return _  _method.apply(object, args.concat($A(arguments)));
  }
}

The JavaScript apply method lets us apply one object’s method within the context of another object’s method. It takes the external object’s context, represented as an object (passed as the first parameter in the argument list), and passes it as the first parameter. The second parameter is an argument list, derived using Prototype’s $A method, which returns an array of iterative objects (necessary when modifying the parameters of built-in objects, as Prototype does with objects like Function and Array).

How bind works with setTimeout is that the object’s state is maintained with each call to setTimeout, including the value of the object’s properties. Since the state is maintained, Ajax developers don’t have to worry about passing function parameters with the timer or using a global variable.

This functionality will be necessary for other applications later in the book, so it is worthwhile to convert it into a function and add it to the addingajax.js library. It’s not the same as Prototype’s approach because this book’s use differs, but it performs the same functionality of binding the object context to the method invoked as an event handler:

function aaBindEventListener(obj, method) {
  return function(event) { method.call(obj, event || window.event)};
}

Example 4-12 is a rewrite of Example 4-11, using objects and the new aaBindEventListener. Instead of passing a function directly into the setTimeout function call, the aaBindEventListener method is invoked, which returns a function. Doing this preserves the state of the object, including the countdown amount, which is now a property of the object.

Example 4-12. Taking a closer look at an Ajaxian timer
<!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>New Timers</title>

<style type="text/css">
#item { font-size: 72px; margin: 70px auto;
        width: 100px;}
</style>

<script type="text/javascript" src="addingajax.js">
</script>
<script type="text/javascript">
//<![CDATA[

aaManageEvent(window,"load", function(  ) {
   var theCounter = new Counter('item',10,0);
   theCounter.countDown(  );
});

function Counter(id,start,finish) {
   this.count = this.start = start;
   this.finish = finish;
   this.id = id;
   this.countDown = function(  ) {
      if (this.count == this.finish) {
         this.countDown=null;

         return;
      }
      document.getElementById(this.id).innerHTML=this.count--;
      setTimeout(aaBindEventListener(this,this.countDown),1000);
   };

}
//]]>
</script>
</head>
<body>
<div id="item">
10
</div>
</script>
</body>
</html>

The reason that the Counter object sets its countDown method to null at the end is based on a memory leak in IE 6.x when using a recursive or function closure technique (function within function). This has been fixed in IE 7, but Ajax developers need to account for IE 6.x until clients are no longer using this browser.

The use of Function.call in managing timers is an interesting technique, if a bit difficult to wrap your mind around at first. It is a better approach than setting global values hither and yon, as it makes it much simpler to maintain values between timer calls.

The next section applies the timer functionality to creating a flashing notice fade.

Creating a Flashing Notice

I’m not sure why yellow became the color of choice for the Ajax fade technique. Come to think of it, I’m not sure why it’s called a fade, other than because the color fades once flashed. The yellow fade was first pioneered at the 37signals site (at http://www.37signals.com/svn/archives/000558.php), and perhaps the color and the name were kept out of habit.

Yellow is a good choice, though, because yellow/blue color blindness is rare compared to red/green, which impacts up to eight percent of men (exact percentages are not known). Even with total color blindness, though, a fade is noticeable as a flash changing saturation rather than color.

The fade technique uses a timer and loops through a set of color variations, from bright to light shades of the same hue, changing the background color of an element with each iteration. There are now many variations, including ones that fade from one color to another rather than remaining limited to the yellow.

A fade technique is most likely not the most complex Ajax application you’ll create, but it isn’t trivial to implement unless you use a fixed array of color values. For a more generic fade, for each iteration of color shade, the application accesses the background color, parses out the two-character string for the hexadecimal value for the reds, the blues, and the greens, converts it to a numeric value, adjusts it for the next step, and then converts back to a string. Given beginning and ending values, the application has to calculate the change in each of the color tones between each step, maintain these values separately, and use them to adjust the value.

There is no real method to make a fade unobtrusive or accessible because it is a visual cue. However, a fade is more of a nicety than a necessity—other page effects should denote that some action has happened.

Tip

Unfortunately, these secondary clues also don’t tend to show up in screen readers. Chapter 7 covers some ways of working with screen readers for visual and dynamic effects.

We’re going to combine examples from earlier in this chapter for our last application. The page has a comment form, with a textarea for a comment and two buttons, one for previewing and one for saving. If scripting is enabled, when the comment form is saved, rather than sending the contents through using the traditional post, an Ajax method is called to save the effects.

For this example, the called program doesn’t do anything with the data except echo the comment if scripting is enabled and the Save button is clicked. If scripting is disabled or if the Preview button is clicked, the application prints the comment to the browser:

?php

$comment = $_POST['comment'];
$submit = $_POST['submitbutton'];

if (empty($submit)) {
   echo $comment;
} else {
   echo $submit . ':' . $comment;
}
?>

This example is simplified, but in your systems, you’ll want to escape the content to ensure it’s safe before updating your databases. Otherwise, you risk SQL injection errors.

The stylesheet isn’t too complicated, but it does add some design and color to spice things up a bit:

#list
{
        border: 1px solid #ccc;
        margin: 20px;
        padding: 10px;
        width: 600px;
}
.comment
{
        margin: 10px 0;
        width: 400px;
}
form, #preview
{
        border: 1px solid #cc0;
        margin: 20px;
        padding: 10px;
        width: 600px;
}

It’s amazing how small a web page is when you split out the stylesheet and JavaScript:

<!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>Timers, Ajax, and Fade, oh my</title>
<link rel="stylesheet" href="events.css" type="text/css" media="screen" />
<script type="text/javascript" src="addingajax.js">
</script>
<script type="text/javascript" src="comments.js">
</script>
</head>
<body>
<div id="list">
<h1>Current Comments:</h1>
</div>
<form action="addcomment.php" id="commentform" method="post">
<fieldset>
<legend>Comments</legend>
<label for="comment">Comment:</label>
<textarea id="comment" name="comment" cols="65" rows="10"></textarea>
<br /><br />
<input type="submit" id="previewbutton" value="Preview" name="submitbutton" />
<input type="submit" id="save" value="Save" name="submitbutton" />
</fieldset>
</form>
<div id="preview">
</div>
</body>
</html>

Example 4-13 displays the content of the JavaScript file, comments.js. Since the data is being updated, the Ajax call is a POST rather than a GET. Once the comment is saved, it’s reflected back in the comment list with a yellow fade to highlight that it has been added to the list. This example uses the traditional yellow, beginning with a value of #ffff00 and ending with white, #ffffff. In addition, it uses the Adding Ajax bind technique for managing the timer events.

Example 4-13. Combining comment preview, Ajax send method, and yellow flash
// global
var commentCount = 0;
var xmlhttp;

function yellowColor(val) {
   var r="ff";
   var g="ff";
   var b=val.toString(16);
   var newval = "#"+r+g+b;
   return newval;
}

aaManageEvent(window,"load", function(  ) {
  aaManageEvent(document.getElementById('save'),"click",saveComment);

});

function saveComment(evnt) {

  // cancel event bubbling
  evnt = evnt ? evnt : window.event;
  aaCancelEvent(evnt);

  // create XHR object
  if (!xmlhttp) xmlhttp = aaGetXmlHttpRequest(  );
  if (!xmlhttp) return;

  // get comment
  var commentText = document.getElementById("comment").value;
  modText = commentText.split(/\n/).join("<br />");
  var param = "comment=" + modText;
  var url = 'addcomment.php?' + param;
  xmlhttp.open('POST', url, true);

  // send comment
  xmlhttp.onreadystatechange = addComment;
  xmlhttp.setRequestHeader('Content-Type',
    'application/x-www-form-urlencoded');
  xmlhttp.send(null);
  return false;
}
// add comment to existing list, with color flash
function addComment(  ) {

  if(xmlhttp.readyState == 4 && xmlhttp.status==200) {
      var modText=xmlhttp.responseText;
      console.log(modText);
      var newDiv = document.createElement("div");
      commentCount++;
      newDiv.setAttribute("id","div"+commentCount);
      newDiv.setAttribute("class","comment");
      newDiv.innerHTML = modText;

      // add object to page
      document.getElementById("list").appendChild(newDiv);

      // start flash counter
      var ctrObj = new Counter("div"+commentCount,0,255);
      ctrObj.countDown(  );
   }
}

function Counter(id,start,finish) {
   this.count = this.start = start;
   this.finish = finish;
   this.id = id;
   this.countDown = function(  ) {
      this.count+=25;
      if (this.count >= this.finish) {
         document.getElementById(this.id).style.background="transparent";
         this.countDown=null;
         return;
      }
      document.getElementById(this.id).style.backgroundColor=yellowColor(this.count);
      setTimeout(aaBindEventListener(this,this.countDown),100);
   }
}
aaManageEvent(window,"load",function(  ) {
      aaManageEvent(document.getElementById('comment'),"keyup",echoPreview)});

function echoPreview(evnt) {
   var commentText = document.getElementById("comment").value;
   modText = commentText.split(/\n/).join("<br />");
   var previewElem = document.getElementById("preview");
   previewElem.innerHTML = modText;
}

From the top, when a comment is saved, the form submission is canceled because Ajax is being used to make the update. The comment is accessed and only simple processing is made on it before being POSTed through the XMLHttpRequest object. When the Ajax request is successfully processed, a new div element is created to host the comment, which is then appended to the existing list of comments. As the item is appended, the fade is flashed to highlight the addition.

The result of the final example is shown in Figure 4-3. Unfortunately, I’m not skilled enough with an image capture timer to catch the yellow fade, but the example is among those packaged for this book.

Preview, Flash, and Ajax combined
Figure 4-3. Preview, Flash, and Ajax combined

Of course, this is a simplified look at how live comments and live updates can coexist. However, it could be easily integrated into an existing application by calling whatever server-side functionality is used for comments, which should also ensure that the comment text is safe. Then the comment can either be fetched from the function or from the database to be returned for display in the field. To the user, all of this should take a fraction of a second, and the result looks instantaneous. Best of all, no page reload occurs to add distraction. Scripting disabled? No problem, regular comment management is still part of the page.

This application is, in a way, Ajax in a nutshell: a combination of user interaction, Ajax requests, objects, timers, and visual effects. Pat yourself on the back; you’re an Ajax programmer now. However, I wouldn’t skip the rest of the book.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required