Ajax Activity Indicators – Make Them Global and Unobtrusive

EDIT (7/10/2006): After much feedback, both on this site and ajaxian, I feel that I failed to properly explain where this implementation would be useful. If you have a page with a single action, say a live search for example, separating the indicator from the action is poor form from a usability standpoint. However, a dashboard page with a couple widgets and some live updating data, or some other instance where a lot of ajax may be happening on a page at once would be a perfect use for what I write about. Thanks to everyone for their comments, feedback, and advice.

One of the coolest things about developing ajax-enabled applications and sites is the level of interactivity that you can bring to your users. And perhaps one of the most crucial aspects of this process is adding activity indicators to your site. While a lot of ajax requests can be very fast, it’s still important to let your users know that something is happening. All too often I see people forgetting to add these indicators, purposefully omitting them because there wasn’t a good place to include them in the design, or implementing them poorly. One of the biggest omissions that I see is with live searches and auto-completion widgets, but not without good reason. Activity indicators can break up a good layout, as they can require a fair amount of page real estate. They’re also a major pain to incorporate into your apps, as you often end up writing a lot of redundant code to display and hide different indicators on different pages or parts of a page.

So, what can we do about these problems? Well, if you’re using prototype as a part of your framework, you can register global indicator functions. These get executed when there are active requests, and when the requests complete. However, there’s another dilemma with this method too: Where do you place a indicator that can potentially appear often and keep it from being obtrusive, or, even worse, not being seen as it’s placed outside of the content that’s currently in view? I had to tackle that issue this week while starting development on a new project at work. I wanted to create an indicator that would be in the same place on every page, and that I never had to write extra code to use. View a demo of the solution here.

The solution, in hindsight, seems very obvious and I can’t believe that I hadn’t come up with it earlier: Place a div that spans the entire width of the window at the bottom of the window and make it stick there when the user scrolled. Then use scriptaculous, or moo.fx (whichever you prefer) to show/hide the indicator when ajax requests were made/completed respectively. So how did I do it? Read on…

Step 1: Gather the Tools

Before we write any code, we need to grab some javascript libraries. For this implementation, I use the latest versions of scriptaculous (1.6.1 at the time of writing) and Prototype (1.5.0_rc0 at the time of writing). If you don’t have them, head over to the scriptaculous website and grab the latest version. Prototype comes bundled with it, so that ought to save you some time.

Once you have the file, grab everything in the src folder (the scriptaculous files) and prototype from the lib directory and place them in whatever directory you would like in your site. This example will assume they are in the root.

Step 2: Laying the Groundwork: The CSS

Now for the tricky part. The key to this whole solution is to keep the div at the bottom of the window, not the page. We want to make sure the user always sees the indicator. The solution is actually a slightly modified version of SitePoint’s Catfish Script. If you’ve been to their site lately, you have probably noticed their cool little ads that appear at the bottom of the screen after a few seconds. Well, they call that a catfish, which makes complete sense to me now, but was of no use to me when I was searching for their solution. As usual, I’m going to summarize and borrow from their article, but I suggest you stop reading now and read their full post before continuing. Also, just to be perfectly clear, I’m not trying to steal their work, or claim it as my own, I’m just using it to achieve our goal (don’t need people thinking I’m plaigarizing!).

Anyway, here’s the css for the notification area to work, at least in everything but IE:

#notification {
	position: fixed;
	bottom: 0;
	padding: 0;
	height: 20px;
	margin: 0;
	width: 100%;
	background-color: #FFFF99;
	display: block;
	text-align: center;
	font-weight: bold;
	font-size: 1.3em;
	font-family: Verdana, Arial, Helvetica, sans-serif;
	padding-top: 5px;
}
html,body {
	height: 100%;
}

html {
	padding: 0 0 25px 0;  /* create area for notification area */
}

I’ll take an excerpt from the sitepoint article to explain why this won’t work in IE, and what we need to do to fix it:

At this point the major issue became the small matter that it didn?t work at all in Internet Explorer. If you’re viewing the demo in IE you’ll see that the DIV is behaving exactly as if it were “position: static” (the default).
[...]
We also made another decision at this point. Since FireFox, Opera and Safari do a dandy job with the W3C standard “position:fixed”, why throw extra markup at them?only IE would be getting the extra markup.

The article goes into greater detail, and explains the problem further, but for the sake of continuity, I’ll just post the code we need to get IE to play nice on the next page

First, we need to create a separate css file for IE. Let’s call it IEhack.css:

#header {
	width: auto; /*needed to keep IE from throwing a horizontal scrollbar in the page */
}
#notification {
     position: absolute;
     z-index: 100;
     overflow: hidden;
}
html, body {
     height:100%;
     overflow: hidden;
     width:auto;
}

div#zip {
     width: 100%;
     padding:0;
     margin:0;
     height: 100%;
     overflow: auto;
     position: relative;
}

Now, we need to write a bit of javascript:

function wrapFish() {
	var catfish = document.getElementById('notification');
	var subelements = [];
	for (var i = 0; i < document.body.childNodes.length; i++) {
 		subelements[i] = document.body.childNodes[i];
	}

	var zip = document.createElement('div');    // Create the outer-most div (zip)
	zip.id = 'zip';                      // call it zip

	for (var i = 0; i < subelements.length; i++) {
	zip.appendChild(subelements[i]);
	}
	document.body.appendChild(zip); // add the major div
	document.body.appendChild(catfish); // add the catfish after the zip
}

addLoadEvent(function() {
	wrapFish();
});

Finally, as mentioned by the SitePoint article, we need to create a way to serve this css and js to only IE users. Here's the code:

<!--[if IE]>
<link rel="stylesheet" href="IEHack.css" type="text/css" />
<script type="text/javascript" src="notification.js"></script>
<![endif]-->

So, we've got our CSS, we've got our IE hacks, but we still need our HTML, and the main JavaScript that sets this all up for us. Let's get into the HTML next...

Step 3: The HTML

There's no real need for explanation here, as the HTML is pretty basic. I'm just going to give you the HTML from my demo page, but you should be able to easily modify it to suit your needs:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title>Global Ajax Activity Indicator Demo</title>
<script type="text/javascript" src="prototype.js"></script>
<script type="text/javascript" src="scriptaculous.js?load=effects"></script>
<script type="text/javascript" src="responder.js"></script>
<style type="text/css">
<!--
html,body {
	height: 100%;
}

html {
	padding: 0 0 25px 0;  /* create area for notification area */
}
body,td,th,p {
	font-family: Verdana, Arial, Helvetica, sans-serif;
	font-size: 12px;
	color: #333333;
}
body {
	margin-left: 0px;
	margin-top: 0px;
	margin-right: 0px;
	margin-bottom: 0px;
}
#notification {
	position: fixed;
	bottom: 0;
	padding: 0;
	height: 20px;
	margin: 0;
	width: 100%;
	background-color: #FFFF99;
	display: block;
	text-align: center;
	font-weight: bold;
	font-size: 1.3em;
	font-family: Verdana, Arial, Helvetica, sans-serif;
	padding-top: 5px;
}
#header {
	padding: 5px;
	background-color: #CCCCCC;
	font-size: 15px;
	font-weight: bold;
}
#content {
	padding: 5px;
}
-->
</style>
<!--[if IE]>
<link rel="stylesheet" href="IEHack.css" type="text/css" />
<script type="text/javascript" src="notification.js"></script>
<![endif]-->
</head>
<body>
<div id="header">This would be your fancy header!</div>
<div id="content">
And all your site content could go here! Notice that if you keep executing requests so that the page gets longer than the window, you the notification stays "stuck" to the bottom.<br />
<br />
<a onClick="testClass.demoRequest();" style="cursor: pointer; color: #0066CC;">Click Here to Execute a Request</a><br />
</div>
<div id="notification" style="display: none;"><img src="ajax_indicator.gif" width="16" height="16" align="absmiddle"> Working...</div>
</body>
</html>

Let's take a look at the HTML. In the header, we've included all our necessary javascript, including the file that we'll create in the next step. We've also included the IE-specific code, and the base css. This could, of course, be a part of your main CSS file, I just put it in the header for clarity. In the body, the only two necessary elements are the header and notification divs. The only other thing we need before we start writing the javascript, is the indicator:

Ajax Indicator The activity indicator

Alright, let's get to the fun stuff: the javascript!

Step 4: The JavaScript

OK, so all that's left for us to do is to create the javascript that will show/hide our notification area when there are active/complete requests. What we're going to do is use prototype's ajax responder functions to display the notification area when there are any open requests, and then hide it when all requests complete. The code is pretty straightforward:

Ajax.Responders.register({
	onCreate: function() {
		if($('notification') && Ajax.activeRequestCount > 0)
			Effect.Appear('notification',{duration: 0.25, queue: 'end'});
	},
	onComplete: function() {
		if($('notification') && Ajax.activeRequestCount == 0)
			Effect.Fade('notification',{duration: 0.25, queue: 'end'});
	}
});

This code can either be placed in a file that you include on every page, or, as I prefer to do, place it at the top of the file that contains all my javascript for a particular page. It's completely up to you how you do it, just make sure that it gets included once on every page you want the notification area to work with. You can also change the scriptaculous effects to whatever you would like, I simply prefer the fading, but the blind effects could work nicely as well. Of course, you could also use another effects package, such as moo.fx, or simply change the display from none to block.

Wrapping Up

That's all there is to it! A simple, unobtrusive, but extremely useful notification area that will stick to the bottom of your window. You could also use this area for other notifications, such as successful saves, or any other event that occurs in your app. Like I said before, this saves you the effort of writing function-specific indicators, and of trying to find a place to put the indicator in your design. If you have any comments, suggestions, or improvements, don't hesitate to let me know. Enjoy!

Resources

Other's Thoughts   (3 so far...)


  • Don
    Jun 24 '08 at 8:42 am

    Where can I download responder.js or is responder.js the JavaScript in this article?

    Thanks,
    Don


  • Jerry
    Mar 28 '09 at 12:01 pm

    When you say “Now, we need to write a bit of javascript:” What do you name that JS file?


  • vvb
    May 6 '10 at 4:44 am

    dfff

  • Share Your Thoughts...

    Some HTML is ok. If this is your first comment on my site, it will be reviewed before being posted publicly. Your comment may be edited or marked as spam if it appears intended for SEO purposes.