Oct 9
Writing a Reusable AJAX Handler
Due to the number of questions I receive on various forums as well as the number of times I have run into the need for an AJAX response handler in my own work, I decided I would share one of my solutions in the hopes that it will save someone else some time as well. I assume, if you are reading this, that you have a basic knowledge of AJAX and the principles guiding the technology. In addition, basic understanding of how libraries such as jQuery perform AJAX queries and callback functions in JavaScript is a plus. For further reading on these prerequisites, check out this Google search and the jQuery docs.
After manually handling my responses in multiple projects, I finally decided to write a JavaScript object that could handle the XML responses for me and return me a usable object to my callback function. Enter the AJAX Handler object. I decided to let jQuery handle the actual requests for me, but I wanted a way to easily parse out my response, checking for errors and handle them accordingly. The result was something I have been able to use numerous times in different projects. To simplify things, let’s look at the code a piece at a time. At the bottom of this post, you will find the entire code.
To start out, let’s create our name space and object itself; then we can flesh out the individual methods within. For my case, I define the guahan name space in which I generate my specific objects:
var guahan = {};
guahan.ajax = {
};
Now, we have a place holder in which we will create our methods. Let’s discuss what we are expecting from our AJAX calls so that we know how to properly parse the response into a usable object. First, in our case, we are expecting a valid XML response. The root node can be named whatever we want, but there are a few required nodes we will be searching for:
<error_code>- “0″ or error number<error_title>- “” or error title<error_message>- “” or error message (imagine that!)<data>- “” or node structure of our response
So, with these tags in mind, a response resulting in failure that we want to trigger our error handler callback would look something like this:
<response>
<error_code>1</error_code>
<error_title>My Error</error_title>
<error_message>There was an error when trying to perform this action</error_message>
<data></data>
</response>
Likewise, a successful response may look something like this (an actual example taken from my Google App Engine game):
<response>
<error_code>0</error_code>
<error_title />
<error_message />
<data>
<coords>
<item>
<x>1</x>
<y>1</y>
</item>
<item>
<x>2</x>
<y>2</y>
</item>
<item>
<x>2</x>
<y>1</y>
</item>
</coords>
</data>
</response>
Obviously, we are returning a series of coordinates. In the case of my game, it is a list of possible moves for a unit. We need to be able to extract all the data into a usable object to return through the callback function, but we need to write it generically enough to build a coherent object no matter what the structure returned within the <data> tags may be. However, before we look at building our data object, let’s look at performing the request and our handler methods we will be building:
request()- Executes the request and performs the appropriate callback functionshowError()- The default handler for errors (up to you to flesh this one out)buildResponseObject()- Builds the actual data object to returnrecursiveBuildObject()- Helper tobuildResponseObject()(this is where the magic happens)
guahan.ajax.request()
So, let’s dive right into our request() method. This method takes three required and one optional parameter:
url- The URL to which the request is to be sentparams- The parameters which will be posted to the URL (object or query string)callback- A callback function which will be passed our resulting data object as a parametererr_callback- An optional parameter defining a callback function for when the request fails or we have an error returned from our response. If this parameter is not provided, we default to calling the built inguahan.ajax.showError()method
Now that we have an idea of the structure of the function, let’s discuss exactly what will happen when this method is called. While this is the largest method (code wise) within our handler, it actually is very straight forward when considering it is simply a wrapper to the jQuery ajax object. By making the request, we parse out the error code, title and message from our response and checks to see if the error code is set to something other than “0″ (our default). At this point, we call the error callback (whether the one provided or the default) if there is an issue, and if not, we build the data object by calling guahan.ajax.buildResponseObject() and pass the result to our callback function. That’s it!
So, now our class should look something like this:
guahan.ajax = {
request: function(url, params, callback, err_callback)
{
$.ajax({
type: "POST",
url: url,
data: params,
dataType: "xml",
success: function(xmlData)
{
var errno = $("error_code", xmlData).text();
var errtitle = $("error_title", xmlData).text();
var errmsg = $("error_message", xmlData).text();
// No errors returned
if (errno == '0')
{
o = guahan.ajax.buildResponseObject(xmlData);
callback(o);
}
else
{
if (!err_callback)
{
guahan.ajax.showError(this, errtitle, errmsg);
}
else
{
err_callback(errtitle, errmsg, errno);
}
}
},
error: function(XMLHttpRequest, textStatus, errorThrown)
{
if (!err_callback)
{
guahan.ajax.showError(this, textStatus, errorThrown);
}
else
{
err_callback(this, textStatus, errorThrown);
}
}
});
}
};
guahan.ajax.showError()
As we mentioned before, this is really just a place holder function for you to fill out with whatever you want your default behavior to be on error. As such, we will not go into the time to discuss nuances of it, save for a review of the parameters this function takes. Keep in mind that if you provide and err_callback parameter to the request() function, it needs to accept these same parameters, too:
XMLHttpRequest- The request object that received the responsetextStatus- Status of the request (title of the error)errorThrown- Error message encountered
So, the implementation of this method within the handler would look something like this:
guahan.ajax = {
showError: function(XMLHttpRequest, textStatus, errorThrown)
{
}
};
guahan.ajax.buildResponseObject()
Now we arrive at the moment of truth. This function in conjunction with the last is where we actually build a usable object from the XML response we have received. Here is the principle we are following: we want an object with nested elements that reflect the node structure of the XML document. So, if we have the following XML response returned:
<response>
<data>
<california>
<population>28525965</population>
<size>unknown</size>
<capital>Sacramento</capital>
</california>
</data>
</response>
The goal of our response class is to have a data object in which we can reference each element directly. So, suppose the variable data holds our data object: to get the capital of California based on our response, we should be able to reference it directly by calling data.california.capital. I’m sure you can see where this would come in handy in dealing with very specific responses from an AJAX request. When we get to the last function, we will cover how we deal with duplicate elements such as our <item> tags in the original response mentioned above.
For now, though, let’s look at how buildResponseObject() works. First of all, notice that we simply pass in the text of the XML response and use jQuery selectors to see if the data node has any children. If not, we return an empty object, but if so, we begin to recursively check all children down through the response:
guahan.ajax = {
buildResponseObject: function(xmlData)
{
var data = {};
$("data", xmlData).children().each(function(i)
{
data[this.tagName] = guahan.ajax.recursiveBuildObject(this);
});
return data;
}
};
Now, let’s look at the true magic of this response object.
recursiveBuildObject()
As you can see from the previously discussed function, this one receives a node from the XML response as its sole parameter. All we are doing here is processing the children of this node (likewise calling ourself on each of them &8212; hence the recursion) and returning the result. There are a few possible outcomes to each processed node, so let’s see what the function returns in each case:
- The node has no children: we have reached the end of our structure, so return the text of this node as the data
- The node has children, but the data object for this node has not yet been set: set the new data object and apply all children to it after processing
- The node has children, and the data object has already been set: simply apply all children to the new data object after processing
- The node has children and a variable for a child node is also already set: this indicates that we have duplicate response tags in our XML, so we need to set the value of this variable to an array of objects instead of a single instance
Once all nodes have been recursively crawled in this way, we have a nicely structured object which we can return via the callback method in our request() function. So, for instance, you could reference the second coordinate from our original response by tapping the item array within the data object: data.coord.item[1].x would return the X coordinate of the second item. Let’s look at the code:
guahan.ajax = {
recursiveBuildObject: function(node)
{
if ($(node).children().length == 0)
{
return $(node).text();
}
var new_data = null;
$(node).children().each(function(i)
{
if (new_data == null)
{
new_data = {};
new_data[this.tagName] = guahan.ajax.recursiveBuildObject(this);
}
else if (!new_data[this.tagName])
{
new_data[this.tagName] = guahan.ajax.recursiveBuildObject(this);
}
else
{
var temp = new_data[this.tagName];
if (!(temp instanceof Array))
{
new_data[this.tagName] = [];
new_data[this.tagName].push(temp);
}
new_data[this.tagName].push(guahan.ajax.recursiveBuildObject(this));
}
});
return new_data;
}
};
So, now we’ve looked at all the individual functions of our object, and it’s time to put it to work. Let’s see the completed code, and then we’ll put our coordinates sample to good use.
Full Code and Use Case
Here is the AJAX handler as it stands now:
var guahan = {};
guahan.ajax = {
request: function(url, params, callback, err_callback)
{
$.ajax({
type: "POST",
url: url,
data: params,
dataType: "xml",
success: function(xmlData)
{
var errno = $("error_code", xmlData).text();
var errtitle = $("error_title", xmlData).text();
var errmsg = $("error_message", xmlData).text();
// No errors returned
if (errno == '0')
{
o = guahan.ajax.buildResponseObject(xmlData);
callback(o);
}
else
{
if (!err_callback)
{
guahan.ajax.showError(this, errtitle, errmsg);
}
else
{
err_callback(errtitle, errmsg, errno);
}
}
},
error: function(XMLHttpRequest, textStatus, errorThrown)
{
if (!err_callback)
{
guahan.ajax.showError(this, textStatus, errorThrown);
}
else
{
err_callback(this, textStatus, errorThrown);
}
}
});
},
showError: function(XMLHttpRequest, textStatus, errorThrown)
{
},
buildResponseObject: function(xmlData)
{
var data = {};
$("data", xmlData).children().each(function(i)
{
data[this.tagName] = guahan.ajax.recursiveBuildObject(this);
});
return data;
},
recursiveBuildObject: function(node)
{
if ($(node).children().length == 0)
{
return $(node).text();
}
var new_data = null;
$(node).children().each(function(i)
{
if (new_data == null)
{
new_data = {};
new_data[this.tagName] = guahan.ajax.recursiveBuildObject(this);
}
else if (!new_data[this.tagName])
{
new_data[this.tagName] = guahan.ajax.recursiveBuildObject(this);
}
else
{
var temp = new_data[this.tagName];
if (!(temp instanceof Array))
{
new_data[this.tagName] = [];
new_data[this.tagName].push(temp);
}
new_data[this.tagName].push(guahan.ajax.recursiveBuildObject(this));
}
});
return new_data;
}
};
Let’s put this bad boy to work! Suppose, as in the case of my tactics game, I want to run a query to retrieve the valid moves for my currently selected unit. First, I build the parameters that my AJAX file is expecting: in this case, simply the ID of the selected unit. Then, I call my ajax request directly and build a callback function that will receive the data object and work on it according to my needs:
guahan.ajax.request('/url/to/myfile.php', {id: unit_id}, function (data)
{
// Let's loop over our coordinates and show the spaces
for (var i = 0; i < data.coords.item.length; i++)
{
var x = data.coords.item[i].x;
var y = data.coords.item[i].y;
// Display the corresponding space on the board
}
});
This then allows us to call any AJAX file, parse the results and use the data object in any script we write. I’m sure that the benefits of having a wrapper for consolidating all your AJAX requests is very self evident by now, and I hope that this simple handler class will give you a springboard from which to launch yourself into a much more cleanly structured JavaScript framework.
Feel free to use this code in any way you can, but I do ask that you let me know where and how you are using it so I can simply keep track of the people who have found some use for my code. Also, please report any bugs or recommendations as well. As mentioned, this is a very rudimentary handler object, and I would recommend anyone to flesh things out a bit more and do some additional error checking in touchy places, but otherwise, enjoy!
No Comments
Leave a comment