Prior to 'Ajax' and 'Comet', interactions between a browser and a some web server
followed this pattern:
With Ajax, a web page displayed in a browser sends a web server some data and the same page in the browser receives a reply and updates itself, without loading or reloading a page.
With Comet, a web server sends data to a web page without the web page making any request.
Everyone does asynchronous Ajax, which means your web page does not wait for a reply after sending a web server some data. This is important because otherwise any delays in getting a response would freeze your browser.
The mechanism, or commands, to do asynchronous Ajax are not the same for all browsers. A few lines of code get around this for all modern browsers; however some JavaScript toolkits have very elaborate mechanisms for ensuring that you can so Ajax with just about any browser.
Let's assume the toolkit offers a function serverRequest. For simplicity we assume this function is hardwired to make POST requests to the server and replies with data of the format 'text/plain', ie a simple ASCI string. So somewhere in our code we will see something like this:
var processReply=function(replyData){ alert('the server says '+replyData); };
serverRequest('processRequest.php',requestData,processReply);
Most Ajax code boils down to something like this.
Here is a rough outline of how a shopping application might use Ajax. When a user chooses a category, getItems is called, and it makes an Ajax call to get the items that belong to that category. processReply is called when the page receives the item data from the server. To keep this example short, processReply merely echos this data to a div on the page.
function shoppingGUI(div) {
// 'div' is where in the web page the shopping functionality is located
..
function showItems() {
// called by onclick event handler of some form widget
var processReply=function(replyData) {
replyDiv.innerHTML='we found these items for '+form_categoryField.value+': '+replyData;
};
var requestData=[{category:form_categoryField.value}];
serverRequest('getItems.php',requestData,processReply);
}
..
}
..
new shoppingGUI(div);
..
The last two lines of getItems are used to request data.. We wrap these lines in what becomes our data access function, and immediately execute the function.
function showItems() { // called by onclick event handler of some form button
var processReply=function(replyData) {
replyDiv.innerHTML='we found these items for '+formField.value+': '+replyData;
};
function getItemsInCategory_Ajax(category) {
var requestData=[{category:category}];
serverRequest('getItems.php',requestData,processReply);
}(form_categoryField.value);
}
processReply is available inside getItemsInCategory_Ajax because of closure. Eventually, we want to move getItemsInCategory_Ajax, which has the data access logic, out of the application, shoppingGUI. So closure will no longer provide processReply. This necessitates passing processReply as an argument to getItemsInCategory_Ajax.
function showItems() {
// called by onclick event handler of some form button
var processReply=function(replyData) {
replyDiv.innerHTML='we found these items for '+formField.value+': '+replyData;
};
function getItemsInCategory_Ajax(category,processReply) {
var requestData=[{category:category}];
serverRequest('getItems.php',requestData,processReply);
}(form_categoryField.value,processReply);
}
Now move getItemsInCategory_Ajax outside of shoppingGUI, passing it as an argument:
function shoppingGUI(div,getItemsInCategory) {
// getItemsInCategory takes a category name and return a list of items to its 2nd arg
..
function showItems() {
// called by onclick event handler of some form button
var processReply=function(replyData) {
replyDiv.innerHTML='we found these items for '+formField.value+': '+replyData;
};
getItemsInCategory(form_categoryField.value,processReply);
}
..
}
var getItemsInCategory_Ajax=function(category,processReplyFn) {
var requestData=[{category:category}];
serverRequest('getItems.php',requestData,function(reply) { processReplyFn(reply); });
}
..
new shoppingGUI(div,getItemsInCategory_Ajax);
..
Let's start with the disadvantages. You need to wrap some code in a function, move this function somewhere else, and pass it as an argument to the application. This adds a little code, which is not a big deal. But the indirection might cause confusion, especially if you aren't careful naming the wrapper function or the argument you use to pass the wrapper function. And because the serverRequest is no longer located next to the function that processes the reply, you need to document the wrapping function and the format of the data it should provide to the application.
Separating data access and application logic makes testing of both data access and application logic much easier.
To test a particular data access method becomes very straightforward, and no longer requires the application. Here is a test of getItemsInCategory_Ajax:
function echoReply(reply) { return reply; };
var getItemsInCategory_Ajax=function(category,processReplyFn) {
var requestData=[{category:category}];
function(reply) { processReplyFn(reply); }
serverRequest('getItems.php',requestData,function(reply) { processReplyFn(reply); });
}
function test_getItemsInCategory_Ajax() {
var s='';
s+='for shoes we have:'+getItemsInCategory_Ajax('shoes',echoReply)+
';
s+='for pants we have:'+getItemsInCategory_Ajax('pants',echoReply)+
';
s+='for bras we have:'+getItemsInCategory_Ajax('bras' ,echoReply)+
';
return s;
}
$('debug').innerHTML=test_getItemsInCategory_Ajax();
It is easy test application logic with sample data by feeding the program JSON data. This is the last few lines of our sample program, modified to use a JSON data source:.
..
var testData={
shoes:['nike','reebok','new balance'],
pants: ['levi','gap']
}
var getItemsInCategory=function(category,processReplyFn) {
var reply=testData[category];
processReplyFn(reply);
}
..
new shoppingGUI(div,getItemsInCategory);
..
One can develope the application even if the complex 'back-end' operations,
that eventually will provide data access, are not yet available.
The previous example replaced Ajax with JSON as a data source. You may want to be able to change your source easily or even provide the user with an interface that allows them to choose a data source while using a page. Lets say the page has a widget which calls set_getMap with a user's selection of MapQuest, Yahoo Maps, or Google Maps as a source for map data. This code will result in mapGUI using the choosen data source:
var getMap_mapquest_Ajax=function(category,processReplyFn) { .. }
var getMap_yahoo_Ajax =function(category,processReplyFn) { .. }
var getMap_google_Ajax =function(category,processReplyFn) { .. }
var getMap_Ajax=getMap_mapquest_Ajax;
function set_getMap(source) { // source should be 'mapquest', 'yahoo', or 'google'
getMap_Ajax={mapquest:getMap_mapquest_Ajax,
yahoo:getMap_yahoo_Ajax ,
google:getMap_google_Ajax }[source];
}
..
new mapGUI(div,getMap_Ajax);
..
By separating data access and application logic,
anyone can offer the user a choice of data source without having to change mapGUI.
In fact, mapGUI can be a complete black box,
we only have to know what the format is for the data that getMap_Ajax sends to processReplyFn.
Not all sources of data will give the data to you in the format you require. For instance, the items you want may be part of a numbered list: ([[1,'nike'],[2,'reebok'],[3,'new balance']],), so
var getItemsInCategory_Ajax=function(category,processReplyFn) {
var requestData=[{category:category}];
serverRequest('getItems.php',requestData,function(reply) { processReplyFn(reply); });
}
would need to be replaced with the following code, which removes the numbering:
var getItemsInCategory_Ajax=function(category,processReplyFn) {
var requestData=[{category:category}];
var filter_then_call_processReplyFn=function(reply) {
var numberedItems=eval(reply);
var items=[];
for(var i=0; i<numberedItems.length; i++) { items[i]=numberedItems[i][1]; }
processReplyFn(items);
}
serverRequest('getItems.php',requestData,filter_then_call_processReplyFn);
}
This kind of filtering logic and code does not complicate your application
when you separate data access and application logic.
Separating data access and application logic in your programs requires minimal effort. The separation makes it easier to develope, test, and maintain code. This is especially important when programs are mashups. With many mashups the data source is very fluid, you are forever looking for the best data source. And many of these data sources give you no guarentee that their access methods or output format won't change on you or even disappear; so testing and maintenance are primary activities.
John Slater is a programmer living in Silicon Valley. When he is not JavaScripting he is probably out biking somewhere.