Over the weekend I was fiddling was some code to see how to make a generic activity indicator for Ajax requests. I'm sure most of you have seen these but if not, here's one similar to what Facebook uses:

<a href="http://blog.reybango.com/wp-content/uploads/2010/07/facebook.gif"><img src="http://blog.reybango.com/wp-content/uploads/2010/07/facebook.gif" alt="" title="facebook" width="16" height="11" class="alignright size-full wp-image-948" /></a>

While it's may not look like much, that animated .gif is pretty important because it tells a user that something is happening and that results will appear shortly. The last thing you want is for a user to think the page has locked up or broken and using this simple method advises them that their request is being processed.

<h2>Targeted Indicators</h2>

In my case, I was looking for a way to set an indicator within the specific DOM element that would be affected by the XHR call. So if my XHR call was to set the contents of a specific DIV, I wanted the indicator to render within that specific DIV only (similar to what Facebook does). 

First, I created three DIVs and 3 anchor tags:

[code lang="html"]
&lt;a href=&quot;#&quot;&gt;Box 1&lt;/a&gt;|&lt;a href=&quot;#&quot;&gt;Box 2&lt;/a&gt;|&lt;a href=&quot;#&quot;&gt;Box 3&lt;/a&gt; 

&lt;br /&gt;&lt;br /&gt;

&lt;div id=&quot;box1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div id=&quot;box2&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div id=&quot;box3&quot;&gt;&amp;nbsp;&lt;/div&gt;
[/code]

and I set the DIVs to a specific width and floated them left:

[code lang="css"]
#box1, #box2, #box3 { background-color:#E5EECC; border:1px solid #D4D4D4; width: 200px; float:left; };
[/code]

Next, I created a new method called <em>fillBox()</em> that would take the target element's ID and use that to dynamically create a new IMG tag and insert the activity indicator. What I decided to do was leverage jQuery's global Ajax events to create the generic handlers for this. Using <em>ajaxStart()</em>, which fires off when an Ajax request is initiated, I dynamically create the tag for my activity indicator and inset it using the <em>prepend()</em> method:

[code lang="js"]
		$(ele).ajaxStart( function() {
		
			$(this).prepend( &quot;&lt;img id='throbber' src='facebook.gif' /&gt;&quot; );
		
		})
[/code]

Then using the Ajax global event handler ajaxStop(), I would easily delete the indicator once the Ajax request was done by using the <em>remove()</em> method to delete the IMG tag from the DOM:

[code lang="js"]
ajaxStop( function() {
		
	$(&quot;#throbber&quot;).remove();
		
});
[/code]

Lastly, using some event delegation, I created an event handler for the clicks on the anchor tags:

[code lang="js"]
$( &quot;a&quot; ).click( function(e) {
	
	var el = '';
	switch(e.target.text) {
		case &quot;Box 1&quot;: 
			el = '#box1';
			break;					
		case &quot;Box 2&quot;: 
			el = '#box2';
			break;								
		case &quot;Box 3&quot;: 
			el = '#box3';
			break;										
	} 

	fillBox( el );
	
});
[/code]

The handler simply looked for the anchor tag's text and based on that filled a local var with the ID that would be targeted for the indicator. It then passed that ID to the <em>fillBox()</em> method.

<h2>A Little Roadblock</h2>

I thought I was done and the code look pretty straightforward. When I ran this, though, I experienced some odd behavior:

<a href="http://blog.reybango.com/wp-content/uploads/2010/07/throbber.png"><img src="http://blog.reybango.com/wp-content/uploads/2010/07/throbber.png" alt="" title="throbber" width="619" height="414" class="alignright size-full wp-image-959" /></a>

When I would click on the "Box 1" link, the indicator would correctly appear in the first DIV followed by the data populating the box. When I would next click on the "Box 3" link, though, both the first box <strong>AND</strong> the third box would have the indicators displayed! Not good. 

I looked into it and got feedback from Adam Sontag who provided the answer I needed. I had incorrectly assumed that <em>ajaxStop()</em> would unbind the jQuery global Ajax events from the attached element. Unfortunately it doesn't so by using the <em>unbind()</em> method, I was able to resolve the issue.

[code lang="js"]			
		$.ajax({
		  url: 'test.php',
		  cache: false,
		  success: function(data) {
			
			$(ele).html( data ).unbind( 'ajaxStart' ).unbind( 'ajaxStop' );

		  }
		});

	};
[/code]

<h2>The Solution Gets Easier</h2>

Then I got more feedback from Karl Swedberg which really streamlined the whole thing. He recommended forgoing the Ajax global events altogether and simply inserting the indicator via my fillBox() method directly and since I was replacing the contents of the DIVs with the <em>html()</em> method, there was no need to use <em>remove()</em> since the IMG tag was already being removed.

[code lang="js"]
var fillBox = function( ele ) {

	$(ele).prepend( &quot;&lt;img id='throbber' src='facebook.gif' /&gt;&quot; );

	$.ajax({
		url: 'test.php',
		cache: false,
		success: function(data) {

			$(ele).html( data );

		}
	});

};
[/code]

So while I initially was looking to use jQuery's built-in global Ajax event handlers for making this generic, I'm finding Karl's solution much cleaner. It requires less event handlers and method calls to do the same task. Love it!

Here's the end result and <a href="http://reybango.com/ajax/">you can see the demo here</a>:

[code lang="js"]
&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt; 
  &lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=utf-8&quot; /&gt; 
  &lt;meta name=&quot;robots&quot; content=&quot;noindex&quot; /&gt; 
  &lt;title&gt;Throbber Test&lt;/title&gt; 
  &lt;style&gt;
	#box1, #box2, #box3 { background-color:#E5EECC; border:1px solid #D4D4D4; width: 200px; float:left; };
  &lt;/style&gt;
  &lt;script src=&quot;http://code.jquery.com/jquery.min.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
  &lt;script type=&quot;text/javascript&quot;&gt;

	fillBox = function( ele ) {

		$(ele).prepend( &quot;&lt;img id='throbber' src='facebook.gif' /&gt;&quot; );

		$.ajax({
			url: 'test.php',
			cache: false,
			success: function(data) {

				$(ele).html( data );

			}
		});

	};
	
	$(document).ready( function() {
	
		$( &quot;a&quot; ).click( function(e) {
			
			var el = '';
			switch(e.target.text) {
				case &quot;Box 1&quot;: 
					el = '#box1';
					break;					
				case &quot;Box 2&quot;: 
					el = '#box2';
					break;								
				case &quot;Box 3&quot;: 
					el = '#box3';
					break;										
			} 

			fillBox( el );
			
		});
		
	});
  &lt;/script&gt;
  
&lt;/head&gt; 
 
&lt;body&gt; 

&lt;a href=&quot;#&quot;&gt;Box 1&lt;/a&gt;|&lt;a href=&quot;#&quot;&gt;Box 2&lt;/a&gt;|&lt;a href=&quot;#&quot;&gt;Box 3&lt;/a&gt; 

&lt;br /&gt;&lt;br /&gt;

&lt;div id=&quot;box1&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div id=&quot;box2&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div id=&quot;box3&quot;&gt;&amp;nbsp;&lt;/div&gt;

&lt;/body&gt; 
&lt;/html&gt; 
[/code]
