Javascript Guide - AJAX
You can now also navigate the guide through the table of contents.
So, What is AJAX?
By now, you probably heard the term AJAX quite a lot, and you wonder what it means. The term AJAX stands for Asynchronous Javascript and XML (note that although it's a part of the acronym, I doubt you will ever find yourself working with XML). From Wikipedia:
Wikipedia - Ajax (programming)a group of interrelated web development techniques used on the client-side to create interactive web applications. With Ajax, web applications can retrieve data from the server asynchronously in the background without interfering with the display and behavior of the existing page.
And to try and simplify it a bit more, AJAX has 2 main principles:
- It allows us to communicate with the server without reloading the page.
- It has the ability to do this asynchronously - which means that while the request is ongoing, the rest of our code will go on running, instead of waiting for the process to finish (this ability is optional, and there will be times when we will actually prefer synchronous calls).
AJAX is, today, a fundamental part of the web, and learning JS, we can't escape having a go with it. But, it's API is quite basic, and also it has many cross-browser quirks. It is because of these reasons that we usually use a framework to do our AJAX for us. So, instead of simply showing you how AJAX can be used on it's basic level, I've decided to tackle it in a different way - I've written a small Object that will deal with our AJAX calls in a bit more advanced way.
For the rest of this post, we will go over that code. aside from learning how AJAX work, it will also be a good exercise on writing JS objects, and some other semi-advanced techniques. So, lets start at the result. This code will use our object to create a request to the server, and display it's result:
var ajax = new AJAX('ajax.php','post',true);
ajax.onComplete(function(text,xhr){
document.getElementById('res1').innerHTML=text;
});
ajax.onComplete(function(text,xhr){
console.log(text);
});
ajax.setHeader('encoding','utf-8');
ajax.go({
'a' : 'b'
});
console.log("I'm not waiting on anyone");
There's quite a bit going on, so lets break it up:
- First, we initialize an AJAX object, giving it a URL, a method (post), and telling it to run in async mode (the
truepart). - AJAX works with events. But it's event system is quite basic, and so I've implemented a better one on top of it. The
onCompletemethod will add a function to the event stack, which will fire when the request it completed. Note that it excepts 2 parameters: the response text and the xhr object. - The
addHeadermethod allows us to set headers that will be sent with the requests. - The
gomethod sends the request to the server. It accepts a literal object of key-value pairs to be sent to the sever as parameters. - On the server side, I've put a simple PHP script that tells us what parameters we sent. To make the asynchronous operation visible, I added a 3 second delay to server side.
- Notice that I added a
console.logat the end of the script. This will fire immediately after thegomethod, not waiting up on the request to finish. - Another useful note - notice that in Firebug, whenever you call an AJAX request, it is visible in the console. you can click the + sign to get some more data on the request. Can be immensely useful for debugging requests.
So, now that we have a basic concept of how AJAX is used, let's start breaking up the code. Before we go on any further, here is the full code. Don't be alarmed - it might look a bit long but it isn't so hard to grasp, and I suggest you take 5-10 minutes to read it.
Breaking up the code
Some guidelines - you will notice I use a self-executing lambda to create a private scope for the Object. The lambda receives two variables - a window object and undef - which isn't passed. This is a nice tool, allowing us to check undefined values, as it is, by itself, undefined.
I will now go through the code, function by function, explaining how they work.
getXHR
This method fetches an XHR object for us, in a cross-browser manner. First - the code snippet:
//get a new XMLHttpRequest object
function getXHR(){
//for IE:
if (window.ActiveXObject){
try{ //newer version
return new ActiveXObject('MSXML2.XMLHTTP');
}catch (e){//older version
return new ActiveXObject('Microsoft.XMLHTTP');
}
}
return new XMLHttpRequest();
}
You will notice a new control structure here - the try-catch statement. This is a special structure. It will run the code inside the try block. If that code raises Exceptions it will stop running, and will pass them to the catch block. Exceptions are special types of errors that can be raised by using the throw command. To make this a bit clearer - a code example:
try{
console.log('a');
throw "I'm an error!";
console.log('b');
}catch (e){
console.log(e);
}
//will log a and then I'm an error!
Back to the getXHR code. IE on it's various versions, has different ways of creating XHR objects. We first try the newest one. If that fails, it will throw an error. The catch statement will fire, using the old version.
Lastly, if we are using any standard browser, we can simply create an XMLHttpRequest object. Note, that IE7+ support the standard method, but in a non-standard way, and thus we actually prefer to use their ActiveXObject instead.
AJAX main body
window['AJAX'] = function(url,method,async){
//setting default values
method = method || "get";
async = (async === undef) ? true : async;
var complete_funcs = [] //a stack of functions to call when request is done
, headers = {} //a list of headers to send with the request
, xhr = this.xhr = getXHR(); //the request object
This part is quite simple - we set the default values (note how I use the undef to check if async was defined, as the normal control structure won't behave properly if async is set to false.)
I then initialize some private properties that the object will use internally (this is to enhance the basic capabilities of the native xhr object) - a function stack for the complete event, and a header stack.
setHeader
this.setHeader(key,value){
headers[key] = value;
}
Quite straight forward. I don't think I need to explain this.
onComplete
Just as a reminder, this method adds functions to our custom event stack. I wanted it to be able to receive an infinite number of functions at once, and so I use the arguments variable:
//add function to the complete event
//accepts a N length list of functions
this.onComplete = function onComplete(func){
for (var i = 0 ; i<arguments.length; i++){
//go through all arguments passed to the function and add them to
//the function stack
complete_funcs.push(arguments[i]);
}
}
go
This is the longest part, as it needs to do a few things - so we're going to take it slow. First, we parse the data object passed to us:
var query = ""
, sep = "";
//go through all keys in data
for (var key in data){
if (data.hasOwnProperty(key)){
//if the key isn't derived for the prototype, add it to the request query:
query += sep + encodeURIComponent(key) +"="+ encodeURIComponent(data[key]);
sep = "&";
}
}
This is very simple - we need to make a query string from our object. In order to make sure the strings are passed correctly, we use the uncodeURIComponent to escape all unicode characters.
Next, we handle the headers:
xhr.setRequestHeader("X-Requested-With",'XMLHttpRequest');
for (var key in headers){//add user headers
if (headers.hasOwnProperty(key)) xhr.setRequestHeader(key,headers[key]);
}
Again - not very complicated - notice that I'm sending a special header-
- this is a standard way for us to let the server know we are using AJAX.X-Request-With
Next, we iterate through the headers stack using the for-in loop. This loop will go through all the keys of an object. This loop is quite useful, but can be problematic, as it also goes through all of the object's prototype methods. To make sure we only use keys assigned to it manually, we use the hasOwnProperty method, that does what it name implies.
Lastly, we send the data. For GET, we do this:
if (method =='get'){
this.xhr.open("GET",url+"?"+query,async);
this.xhr.send();
}
The open method opens a channel to the server. It accepts 3 arguments - a method, a URL, and a boolean that tell it whether or not to run asynchronously. For get, we pass the query string with the URL.
The send method initiate the request.
For POST, we use this code:
else{
this.xhr.open("POST",url,async);
this.xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
this.xhr.send(query);
}
The only differences are that we send an extra header, and that we pass the query string using the send method.
Attaching the event
The event attachment is quite basic - we simply set a function to the onReadyStateChange property. You see, a request goes through several stages, and each has it's own ready state. Whenever the request goes into a new ready state, the event will fire.
The problem is, using this method, we can only attach one function to the event, and that simply can't cut it. So instead, we added function stack that will fire within it instead. The code:
this.xhr.onreadystatechange = function(){
//add the event handler
if (xhr.readyState == 4){//if the request is done
if (xhr.status == 200) {//and the request is valid
for (var i = complete_funcs.length-1; i>=0; i--){
//call all functions in complete stack
complete_funcs[i](xhr.responseText,xhr);
}
}
}
}
What this does is simple - the last readyState is 4. Every HTTP request has a status. Ironically, the most famous one is 404, which indicates a "Page Not Found", but there are many, many more. The one we are looking for is 200, which indicated everything is OK.
When we reach this status, we simply fire all the passed functions, passing them the response text from the request, and just in case, we also pass the XHR object. Notice that we fire the functions in reverse order. This is to emulate a pop action, and it is also how native events work.
And this is it! We learned how to create AJAX request. I would say that although the above object will work just fine for most websites, I highly encourage you to use a solid AJAX library for this.
In fact, with this post, we finally conclude our JavaScript guide. On the next, final post, I will point you in some directions you might go from here.