Ajax Crash Course

In this tutorial, I’ll try to introduce the theory and basic syntax of AJAX (Asynchronous Javascript Allows eXcellence) programming. I’ll also show you a wrapper I use which, IMHO, adds a bunch of value. This is all very raw right now, so feel free to let me know what’s wrong or what I should add :)

Background

Webpages are atomic objects. Any update requires the entire page (styles, scripts, headers, footers, etc.) to be resent from the server.

In contrast, web apps are made up of components that can each send and receive data to and from the server irrespective of each other. This means data should never be sent redundantly over the wire.

Web apps use Ajax to reduce redundant Internet use.

Ajax is also about making websites feel like desktop apps. This means treating the server like a user’s hard drive – a place to send and receive data. This means that web apps should be able to “multitask” just like desktop apps. Ajax “multitasks” in that it allows the user to simultaneously ask for more data from the server while interacting with the app.

Having said that, Ajax is meant to be used asynchronously – it’s in the name after all. Using it synchronously is evil. If you’re tempted, you’ve probably designed the interface poorly.

Basic Syntax

<script type='text/javascript'>

var ajax_request_var = new XMLHttpRequest();
ajax_request_var.onreadystatechange = function(){
    if (ajax_request_var.readyState==4 && ajax_request_var.status==200){
        alert(ajax_request_var.responseText);
    }
}
ajax_request_var.open("GET","dataFileName.php?var1=value1",true);
ajax_request_var.send();

</script>

Basic Syntax (with comments)

<script type='text/javascript'>

//Initializes an ajax variable
var ajax_request_var = new XMLHttpRequest(); 

//The function that's called on each response from the server
ajax_request_var.onreadystatechange = function(){ 

/*
  This checks if the response is good.
  There are very few reasons for doing anything outside
of this if statement.
*/
    if (ajax_request_var.readyState==4 && ajax_request_var.status==200){
/*
  This is a good line of code to have at the ready if you think
there's problems with your php code or data, as it will print
them to you before trying to parse the data.
*/
        alert(ajax_request_var.responseText);
        //You can call whatever external methods you want from here.
    }
}
/*
@params
1) GET vs. POST (get is easier since vars are appended to query)
2) Query filename
3) isAsynchronousRequest boolean value - remember: false is evil
*/
ajax_request_var.open("GET","dataFileName.php?var1=value1",true);
ajax_request_var.send(); //Sends request to server

</script>

Ajax Wrapper

Because Ajax is (ahem) asynchronous, you have to be ready for user interaction in between calls to the server, this means the client side needs to be vigilant about knowing its own state and, more specifically, its state when calls are sent to and received from the server. Since the server can return data out of order, the interface needs to protect against displaying information to the user out of order.

Wrapper functions help provide this extra functionality and help ensure best practice use (fewer redundancies, asynchronous interaction that ignores order of server responses, etc.).

<script type='text/javascript'>

this.doAjaxCall = function(query,forwardingFunction,pageWhenCalled){
    if (singleton.dataHash[query] != undefined && singleton.dataHash[query].length > 0){
        forwardingFunction(singleton.dataHash[query]);
        return;
    }else if (singleton.dataHash[query] == undefined){
        singleton.dataHash[query] = "";
    }else{
        return;
    }
    var ajax_request_var = new XMLHttpRequest();
    ajax_request_var.onreadystatechange = function(){
        if (ajax_request_var.readyState==4 && ajax_request_var.status==200){
            if (ajax_request_var.responseText=="SOMETHING WICKED THIS WAY COMES"){
		alert("No soup for you!");
            }
            else if (URLHelp().pageEquals(pageWhenCalled,site_history.currentPage)){
                singleton.dataHash[query] = ajax_request_var.responseText;
                forwardingFunction(ajax_request_var.responseText);
            }else{
                singleton.dataHash[query] = ajax_request_var.responseText;
            }
        }
    }
    ajax_request_var.open("GET",query,true);
    ajax_request_var.send();
}
</script>

Ajax Wrapper (with comments)

<script type='text/javascript'>
/**
@params
  String query: Query text. Example: "data.php?var1=value1&var2=value2"
  Function forwardingFunction: Reference to function that should
run once the server's responded with proper data.
  Object pageWhenCalled: JSON object referring to the app state when the
ajax call was made. If it's not the same state when the server
responds with data, the data will be saved, but no action taken
**/
this.doAjaxCall = function(query,forwardingFunction,pageWhenCalled){
/*
  singleton.dataHash keeps the data for the program,
so you don't have to make redundant queries.
  It should have been initialized in its own Singleton "class"
*/
    if (singleton.dataHash[query] != undefined && singleton.dataHash[query].length > 0){
//Send along the data to wherever it's supposed to go in the interface
        forwardingFunction(singleton.dataHash[query]);
        return;
    }else if (singleton.dataHash[query] == undefined){
//Initializes the space for this query in the hash
        singleton.dataHash[query] = "";
    }else{
/*
  If the query has been run before (singleton.dataHash[query] != undefined)
but there is no data in the hash (singleton.dataHash[query].length == 0),
that means it was a bad query, so ignore it
*/
        return;
    }
    var ajax_request_var = new XMLHttpRequest();
    ajax_request_var.onreadystatechange = function(){
        if (ajax_request_var.readyState==4 && ajax_request_var.status==200){
/*
  Your server script should have some standard error message
that you'll catch here, in case it was given bad variables
*/
            if (ajax_request_var.responseText=="SOMETHING WICKED THIS WAY COMES"){ 

//Let the user know something's wrong, perhaps update the interface
		alert("No soup for you!");
            }
/*
  Now that you have new data,
you want to make sure the interface
is on the same page it was when first asked to update
*/
            else if (URLHelp().pageEquals(pageWhenCalled,
site_history.currentPage)){ 

//Save the data on the client to prevent redundant queries
                singleton.dataHash[query] = ajax_request_var.responseText; 

//Send along the data to wherever it's supposed to go in the interface
                forwardingFunction(ajax_request_var.responseText);
            }else{

/*
  Keep the data even if the user doesn't need it at the moment,
so you don't have to make the same query later.
*/
                singleton.dataHash[query] = ajax_request_var.responseText;
            }
        }
    }
    ajax_request_var.open("GET",query,true);
    ajax_request_var.send();
}
</script>

Hope that helps! Let me know what I should add in the comments, or move on to the Ajax History Crash Course (coming soon).

  • Paul

    Can you explain something to me please? I noticed in your example you hash the query and use it as a key to get the data which is held client side if that call has already been made.

    How does this allow for changes to the data server side? I could be wrong in what I think the code is doing however so if I am then sorry!

  • Micah

    Hey Paul, good question! When I wrote this, I was basing it off an app that only read data from the server, and whose data didn’t change more than once a day. If either of those are not the case in your app, you’ll want a different approach (I’ll write an update when I get some time to deal with these cases). For example, if you’re dealing with real-time data (like a Facebook-type app), then you’d want to restrict your local cache to those items that don’t change (interface-related data, etc.).