The Ultimate Ajax Object

Filed: Thu, Mar 08 2007 under Programming|| Tags: ajax javascript object post get

In my recent article Ten Javascript Tools Everyone Should Have I offered an AJAX object as the 10th tool. After publication, I received quite a number of requests to document the object, and so document it I shall.

Introduction

This object represents the pinnacle of my attempts to create a flexible and robust Ajax object. It's small, compact, object oriented, easy to follow and can handle multiple, concurrent, simultaneous requests, something the hello world tutorials always seem to miss. It's also public domain so use it and abuse it however you like and don't sweat giving up any precious screen space to credit me. So without further ado, here's the Ajax object. The documentation follows.

An Object Lesson

function ajaxObject(url, callbackFunction) {
  var that=this;      
  this.updating = false;
  this.abort = function() {
    if (that.updating) {
      that.updating=false;
      that.AJAX.abort();
      that.AJAX=null;
    }
  }
  this.update = function(passData,postMethod) { 
    if (that.updating) { return false; }
    that.AJAX = null;                          
    if (window.XMLHttpRequest) {              
      that.AJAX=new XMLHttpRequest();              
    } else {                                  
      that.AJAX=new ActiveXObject("Microsoft.XMLHTTP");
    }                                             
    if (that.AJAX==null) {                             
      return false;                               
    } else {
      that.AJAX.onreadystatechange = function() {  
        if (that.AJAX.readyState==4) {             
          that.updating=false;                
          that.callback(that.AJAX.responseText,that.AJAX.status,that.AJAX.responseXML);        
          that.AJAX=null;                                         
        }                                                      
      }                                                        
      that.updating = new Date();                              
      if (/post/i.test(postMethod)) {
        var uri=urlCall+'?'+that.updating.getTime();
        that.AJAX.open("POST", uri, true);
        that.AJAX.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        that.AJAX.setRequestHeader("Content-Length", passData.length);
        that.AJAX.send(passData);
      } else {
        var uri=urlCall+'?'+passData+'&timestamp='+(that.updating.getTime()); 
        that.AJAX.open("GET", uri, true);                             
        that.AJAX.send(null);                                         
      }              
      return true;                                             
    }                                                                           
  }
  var urlCall = url;        
  this.callback = callbackFunction || function () { };
}

Creating a new ajaxObject

This AJAX snippet is a javascript object. You assign it to a variable the same way you assign a new Array(), or new Object(). This means you can have many variables, each one it's own little, discrete AJAX handler. You can even make an array of Ajax objects if you so desire. (be aware however that most browsers will only process two requests at a time. You can declare and start as many ajax requests as you want but only two requests will be processed at a time and the other requests will patiently wait their turn until they can be processed).

When you create the variable assignment, you pass the URL you want that that Ajax object to call. (And remember, Ajax requests are bound to the same domain as the web page. If you're on mydomain.com you'll get an error if you try to make an Ajax request to theirdomain.com).

var myRequest = new ajaxObject('http://www.somedomain.com/process.php');

Defining the Ajax Callback Function

If you would like to have a procedure to process the data returned by the server, add the function name you want to handle the request after the URL as such.

var myRequest = new ajaxObject('http://www.somedomain.com/process.php', processData);

This will tell myRequest to call processData when data has come back from the server. The first three arguments passed to processData will be responseText, responseStatus, and responseXML respectively. All three arguments are optional, you don't have to catch them if you don't want, although it is highly recommended you catch responseText and responseStatus as such...

function processData(responseText, responseStatus) {
  if (responseStatus==200) {
    alert(responseText);
  } else {
    alert(responseStatus + ' -- Error Processing Request);
  }
}

Here processData will accept responseText and responseStatus. If responseStatus is 200 (everything went a-ok) it will display the text from the server. If the response was not 200 it will display the error code and an error message.

Passing a callback function when declaring a new ajaxObject is optional. If you don't pass a function to call, ajaxObject will just discard any results it gets back from the server. This is useful when you don't need to process any return data for example when you're just notifying the server the mouse has passed over an object or that the page is being unloaded.

Having an external function to process the data is optional as well. External (or global) functions are useful if you have many ajax variables all calling the same response handler but if you're doing something unique you can more tightly bind the callback function with the ajax variable as such.

var myRequest = new ajaxObject('http://www.somedomain.com/process.php');
    myRequest.callback = function(responseText, responseStatus, responseXML) {
      // do stuff here.
    }

This inserts the callback function into the object instance itself. Note that you can't prototype this since callback is only set up in the constructor calls or via this method, a prototype would have no effect on new ajax constructors.

Making the call: Initiating the AJAX request.

When you create your new variable assignment ajaxObject is initiated but no calls to the server will happen until you call the update method. The update method accepts two optional arguments, you can pass along some data to be sent to the server and you can specify that data be sent as a POST instead of a GET. (GET basically passes all your data in the URL, POST sends it differently so all the data doesn't show up in the server logs, plus you can send more data with POST than GET).

The format is as follows...

var myRequest = new ajaxObject('http://www.somedomain.com/process.php', processData);
    myRequest.update('id=1234&color=blue');  // Server is contacted here.

This would initiate an ajax call to the server, passing an id of 1234 and color=blue to the server as a GET method. (in php it would set the variables $id and $color).

Remember, there's no actual request to the server until the update() method is called. Update will call the url the ajaxObject was constructed with and then, if there's a callback function (processData in this example) it will call that function when there's a response from the server.

Sending data as a POST in AJAX

To send data to the server as a POST we'd use the following structure...

var myRequest = new ajaxObject('http://www.somedomain.com/process.php', processData);
    myRequest.update('id=1234&color=blue','POST');

This will send id and color to the server as a POST (in php $_POST['id'] and $_POST['color']. The post flag is not case sensitive it just needs to say post in the string.

Testing the Update()

The update() method will return true if the Ajax call was successfully initialized. It will return false if you tried to start a new request while another request was already outstanding. Update() will also return false if the method was unable to initialize the browser's AJAX library (which means the browser is too old to have an xmlhttprequest object).

Escape The Data

The update examples above are extremely simplified. To avoid problems with your data containing illegal characters and quotes you should escape the data before you pass it to update. If you want to pass three variables, you should at the very least prepare them as such...

passName=encodeURIComponent(name);
passAge=encodeURIComponent(age);
passJob=encodeURIComponent(job);
sendString = 'name='+passName+'&age='+passAge+'&job='+passJob;
myRequest.update(sendString,'POST');

There are three escape commands in Javascript: escape, encodeURI, and encodeURIComponent. A very good explination of the three can be found at xkr.us. In general however, you will want to use encodeURIComponent() and avoid escape().

updating

When you call the update() method the updating property is given a timestamp which you can use to see if a call is still in progress and how long the request has been processing. Here's a simple test to see if the object is updating.

if (myRequest.updating) {
  alert('this request is being processed.');
} else {
  alert('this object is idle');
}

You can also see how long a request has been processing....

if (myRequest.updating) {
  now=new Date();
  alert('This request is '+now-myRequest.updating+' miliseconds old.');
}

Aborting an Ajax Request

If you feel an Ajax request has gone on too long you can abort the request with the abort() method. This will issue an abort command and reset the object to an inactive state ready to accept a new update().

myRequest.abort();

If you call abort and there is no pending ajax call this method will have no effect.

Preventing Caching For Internet Explorer

Even if you specify a POST method the URL will always be appended with a timestamp ('?timestamp=123412341243'). This timestamp is the javascript julian date integer, a number representing the number of milliseconds that have elapsed since Midnight, January 1, 1970 GMT. In php you can access the timestamp as $timestamp.

By appending the julian day number to the end of the URL we effectively make each call unique and that means Internet Explorer will not cache the Ajax call as it is wont to do.

Cool Tricks And Tips

You don't actually need a server side script to use this object. It will pull a plain text file off your server without complaint. Of course the content won't be dynamic, it will just be the contents of that static file but if you're poping up a notice, ad, or flyer in a division this is a really nice way to get data dynamically after you've created the page.

myRequest = new ajaxObject('http://somedomain.com/ad.html');
myRequest.callback = function(responseText) {
  document.getElementById('someAdDiv').innerHTML=responseText;
}
myRequest.update(); 

Since the ajaxObject can work with static files we can use it to load javascript libraries after the page has loaded so we can ask for them as we need them instead of sending a huge javascript library at the start which the user may not need.

myRequest = new ajaxObject('http://somedomain.com/javascriptLibrary.js');
myRequest.callback = function(responseText) {
  eval(responseText);
}
myRequest.update();

As you can see, simulating a <script src='library.js'> call is as simple as passing the responseText through an eval statement (eval is basically the javascript compiler).

Multiple Ajax Calls, Ajax Concurrency, and Dynamic Flexibility

The beauty of encapsulating Ajax into an object is that you can define an Ajax request as easily as you can define a variable and keep the different requests as discrete as you can keep the values of different variables discrete.

Here is a simple code which defines three separate Ajax variables and binds them to three separate rss feeds (via a server-side script). The update method is called via links so when a feed is clicked on, the associated ajax request is called and the response is placed in a division named feed.

function processFeed(responseText) {
  if (responseStatus==200) {
    document.getElementById('feed').innerHTML=responseText;
  }
}

var diggFeed=new ajaxObject('feedHandler.php',processFeed);
var redditFeed=new ajaxObject('feedHandler.php',processFeed);
var dzoneFeed=new ajaxObject('feedHandler.php',processFeed);

<A HREF="" onClick='diggFeed.update("feed=http://www.digg.com/rss/index.xml"); return false;'>Get Digg Feed</A>
<A HREF="" onClick='redditFeed.update("feed=http://reddit.com/.rss"); return false;'>Get Reddit Feed</A>
<A HREF="" onClick='dzoneFeed.update("feed=http://feeds.dzone.com/dzone/frontpage"); return false;'>Get DZone Feed</A>

Changing the calling AJAX URL

After you've created an ajaxObject you can update the URL and change the callback function if you need to by simply assigning a new ajaxObject as such…

var myRequest = new ajaxObject('serverScript.php',callbackFunc);
    myRequest.update();

// Now we're going to change the URL

    myRequest = new ajaxObject('newScript.php', newcallback);
    myRequest.update();

An Array of AJAX Objects!

Just for fun…

ajaxArray=[];
for (var i=0; i<10; i++) {
  ajaxArray[i] = new ajaxObject('someScript.php',callbackFunc);
}
// Call them all at once!
for (i=0; i<10; i++) {
  ajaxArray[i].update('id='+i;);
}

The Ultimate Ajax Object

Is it the ultimate Ajax Object? Probably not. It is however probably one of the better ones you're likely to find outside the major frameworks like JQuery, YUI, and Prototype. In addition to being public domain, it's also simple, compact and easy to get under the hood and modify to your own uses -- to me, that makes it an ultimate. Hopefully, for you as well.

Addendum

After the publication of this article, Dmitriy Sonis dropped me an email which suggested adding a content-length header when sending data as a post, as this was a most sensible and level-headed suggestion I complied and have added this line to the Object. As a result, the object should be a little bit more compatible with server-side scripts now.

Mr. Sonis also suggested using a Curry to provide a uniform callback method. It's a very advanced approach but a very useful one as well. Here is his suggestion…

It makes greater sense to have a centralized CallBack function for all AJAX request to do some initial processing, like based on status, and then pass control to a dedicated funciton to handle a specific functionality. This can be achieved using currying.

So here is my AJAX call.

var myRequest = new ajaxObject('http://localhost/AALetters/MergeData.asmx/PutDocument', Curry(OnSaveDoc, CallBack));

myRequest.update(postData, 'Post');

Here is my custom handler.

function OnSaveDoc(){
            alert('Executing OnSaveDoc()...');
        }

This is standard CallBack function.

function CallBack(customHandler, responseText, status, responseXML){
    // Do standard processing then pass execution to dedicated function
    // to handle specific tasks
    if(200 != status){
        alert("Problem");
        return;
    }  
    customHandler();
}

And this is curry function

function Curry (fn, scope) {
    var scope = scope || window;
    var args = Array.prototype.slice.call(arguments,2);
    return function() {
        fn.apply(scope, args);
    };
}