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.
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.
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.
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.
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.
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.