Structuring jQuery Ajax Calls in Web Applications
Make code more reusable by consolidating Ajax calls into JavaScript objects
June 18, 2012
It's amazing how much web technologies have evolved over the years. Technologies such as Ajax were once considered "niche" and rarely used in websites to communicate with the server. Today, Ajax technologies are mainstream and used in nearly every client-centric web application to communicate asynchronously with the server. Developers like Ajax technologies because they provide a more responsive user interface (UI) that minimizes page refreshes and simplifies state management. Users like Ajax technologies because they get the data they need more quickly along with a more desktop-like application experience in the browser.
As the use of Ajax continues to grow in popularity, you'll find it worthwhile to think through how Ajax calls are structured and used in an application, especially if reuse and maintenance are important to you. If you analyze a lot of the Ajax code out there, you'll likely find that calls to the server are scattered throughout scripts and pages. For example, if an Ajax call is made to get a list of jobs, a call is made directly in the script that needs the data. If another page/script needs the same type of data, another Ajax call is made. This results in code duplication and complicates maintenance because Ajax calls aren't consolidated.
Although there's certainly nothing wrong with this approach (it gets the job done, after all), you can get better reuse out of your Ajax calls by consolidating them into JavaScript objects. In this article, I'll talk about how Ajax calls can be consolidated using JavaScript patterns and describe the benefits of using this approach.
The Case for Consolidation
JavaScript libraries such as jQuery make it so easy to make Ajax calls that we often don't think much about the code being written and where it's being used. For example, consider the code in Figure 1, which is used to retrieve customers from a Windows Communication Foundation (WCF) service and update a page with the retrieved data.
$('#GetCustomersButton').click(function () { $.getJSON('../CustomerService.svc/GetCustomers', function (data) { var cust = data.d[0]; $('#ID').text(cust.ID); $('#FirstName').val(cust.FirstName); $('#LastName').val(cust.LastName); });});
Although this code works fine, it can't be reused across other pages that might also need customer data. If another page or script needs the same data, then the getJSON() call will have to be rewritten since the manner in which the returned data is processed will more than likely be different. In addition to this potential problem, the code is added into a script that might have completely different responsibilities that are unrelated to calling a service.
In the object-oriented world, we strive to follow the single-responsibility principle when designing classes to avoid duplication and provide better reuse. Following this same principle in JavaScript code makes a lot of sense and can lead to better reuse, simplified maintenance, and better overall organization of code in an application. By placing Ajax calls into a reusable JavaScript object, we can use them throughout an application more easily, minimize duplicate code, and provide a better maintenance story. But before jumping into how Ajax calls can be consolidated, let's review a key pattern called the Revealing Module Pattern that can be used to encapsulate JavaScript code.
Encapsulation with the Revealing Module Pattern
There are several different patterns that can be used to structure JavaScript code and provide a way for functions to be encapsulated inside of class-like structures (given that JavaScript doesn't officially support the concept of a class). One such pattern is the Revealing Module Pattern (I covered this pattern in several previous Dev Pro articles; see the list at the end of this article for the article links). By using the pattern, you can encapsulate variables and functions into an object, achieve reuse, and simplify maintenance as well as help avoid naming collisions. There are several other patterns that can be used, such as the Prototype Pattern and Revealing Prototype Pattern (to name just two), but the Revealing Module Pattern provides a simple way to get started creating JavaScript objects that are similar in purpose to C# or Java classes.
Figure 2 shows an example of some simple code that has no structure applied to it (I call it "function spaghetti code"). With this approach, variables and functions are scattered throughout a file with no rhyme or reason as to how they relate to each other. All the variables and functions defined this way are automatically placed in the global scope, which can lead to naming collisions.
var engine = 'V8';function start() { alert('Car started ' + engine + ' engine');}function stop() { alert('Car stopped ' + engine + ' engine');}function turn() { alert('Car turned')}
Figure 3 shows the code from Figure 2 refactored to follow the Revealing Module Pattern. In this simple example, the code is encapsulated in an object named car. Following this pattern allows the code to be organized, variables and functions to be taken out of the global scope, and a reusable object to be defined that can be used in one or more pages or applications.
var car = function(engine) {var start = function() { alert('Car started ' + engine + ' engine');},stop = function() { alert('Car stopped ' + engine + ' engine');},turn = function() { alert('Car turned');}return { start: start, stop: stop, turn: turn};}('V8');
Looking through the code in Figure 3, you'll see that three functions are defined: start(), stop(), and turn(). All three functions are publicly exposed to consumers by using the return object that's defined (an object literal). Any functions not listed in the return object are inaccessible to consumers, making them similar to private members in object-oriented languages. Since the car object is self-invoked (note the parenthesis at the end of Figure 3), you can call it directly by using code such as the following:
car.start();car.stop();car.turn();
If you want to create a new instance of car, you can use the code shown in Figure 4 instead of the code in Figure 3. Notice that the car object starts with an uppercase character, so that the consumer knows to create a new instance of the object to use it. This is a common convention being used more and more among JavaScript developers.
var Car = function(engine) {var start = function() { alert('Car started ' + engine + ' engine');},stop = function() { alert('Car stopped ' + engine + ' engine');},turn = function() { alert('Car turned');};return { start: start, stop: stop, turn: turn};};
To use the car object, you can write the following code:
var car = new Car('V8');car.start();car.stop();car.turn();
If you need only one object in memory as an application is running, the code shown in Figure 3 works well. If you need multiple instances of an object, the self-invoking parenthesis can be removed, as shown in Figure 4.
Now that you've seen how the Revealing Module Pattern can be used to structure JavaScript code, let's see how the pattern can be used to encapsulate Ajax functions into an object.
Consolidating Ajax Calls into a Reusable Object
A sample application named Account at a Glance (download the Account at a Glance app here) built to demonstrate several HTML5, jQuery, and general JavaScript concepts relies on the Revealing Module Pattern to consolidate Ajax calls into a single object that can be reused throughout the application. Figure 5 shows the application's main screen.
By following this pattern, we can define an Ajax call used to retrieve market quotes in one place, then use the Ajax call from other scripts that might need the data. This approach provides several benefits, including more modular, reusable, and maintainable code. If something changes with an Ajax call, you can go to one place to make the core modifications rather than search through multiple scripts and pages to find where changes need to be made.
To access market-quote data in the application, you could use the following call as data is needed in a given script:
$.getJSON('/DataService/GetQuote', { symbol: sym }, function(data) { //process data here});
Although this code works, from a reuse and maintenance standpoint it's much better to consolidate calls into an object. Figure 6 shows an example of an Ajax-specific object named dataService that the Account at a Glance application uses to retrieve different types of JavaScript Object Notation (JSON) data from the server.
var dataService = new function () { var serviceBase = '/DataService/', getAccount = function(acctNumber, callback) { $.getJSON(serviceBase + 'GetAccount', {acctNumber:acctNumber}, function(data) { callback(data); }); }, getMarketIndexes = function(callback) { $.getJSON(serviceBase + 'GetMarketIndexes', function(data) { callback(data); }); }, getQuote = function(sym, callback) { $.getJSON(serviceBase + 'GetQuote', { symbol: sym }, function(data) { callback(data); }); }, getTickerQuotes = function(callback) { $.getJSON(serviceBase + 'GetTickerQuotes', function(data) { callback(data); }); }; return { getAccount: getAccount, getMarketIndexes: getMarketIndexes, getQuote: getQuote, getTickerQuotes: getTickerQuotes };} ();
The dataService object follows the Revealing Module Pattern discussed earlier to encapsulate the various functions. A single instance is created initially at runtime (the dataService function is self-invoked as the script initially loads) that exposes four functions responsible for getting account, market, quote, and ticker data from the server.
Looking through each function in the dataService object, you'll notice that they all accept a callback parameter. Because each function is reusable, it won't know how to handle the data that's retrieved from a given service -- processing of data is unique to the caller of the function. As a result, each function in dataService allows the caller to pass a callback function that is invoked once the data returns from the service to the client. Here's an example of calling the dataService object's getAccount() function:
dataService.getAccount(acctNumber, renderAccountTiles);
When the data is returned, the getAccount() function will invoke the renderAccountTiles callback function shown in Figure 7.
renderAccountTiles = function (data) { $('div.tile[id^="Account"]').each(function () { sceneStateManager.renderTile(data, $(this), 500); });}
Note that a nested/inline callback function could be passed to getAccount() as well, as shown next:
dataService.getAccount(acctNumber, function(data) { //Process data here});
Anytime a script in the application needs to retrieve data from the server, a call can be made to one of the dataService object's functions and required parameters can be passed along with the callback. This technique provides a flexible way for Ajax functionality to be consolidated into an object yet remains flexible as far as callbacks go.
This technique can be applied to other parts of an application as well to create additional objects that encapsulate related variables and functions. By focusing on objects rather than functions, you can more efficiently organize code in an application and achieve many of the benefits mentioned earlier, such as better reuse and simplified maintenance. Eliminating function spaghetti code is definitely a good thing.
Encapsulate to Consolidate
In this article you've seen how Ajax calls can be consolidated into an object that can be used by multiple scripts or pages. With a little work, random variables and functions scattered throughout a script can be encapsulated into objects that can be used throughout an application.
If you'd like additional details about jQuery, structuring JavaScript code, or building an end-to-end application, check out my jQuery Fundamentals, Structuring JavaScript Code, or Building an ASP.NET MVC, EF Code First, HTML5, and jQuery courses from Pluralsight. You can also find additional information about these topics on the Dev Pro website.
About the Author
You May Also Like