jQuery Tip #7 - Consolidating jQuery Ajax Calls
Previous Tips:
- jQuery Tip #1 - Defining a Context When Using Selectors
- jQuery Tip #2 - Manipulating the DOM in a Loop
- jQuery Tip #3 – Using the data() Function
- jQuery Tip #4 – Use the on() Function for Event Handling
- jQuery Tip #5 – Using jQuery’s end() Function to Work with Sets
- jQuery Tip #6 – Creating a Custom jQuery Selector
As the use of Ajax continues to grow in popularity it's worth taking the time to think through how Ajax calls are structured and used in an application especially if re-use and maintenance are important. 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 since Ajax calls aren't consolidated.
While there's certainly nothing wrong with this approach (it gets the job done after all), you can get better re-use out of your Ajax calls by consolidating them into JavaScript objects. In this post I'll talk about how Ajax calls can be consolidated using JavaScript patterns and describe the benefits this approach provides.
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 following code that's used to retrieve customers from a 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 re-used across other pages that may also need customer data. If another page or script needs the same data then the getJSON() call will have to be re-written 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 may 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 re-use. Following this same principle in JavaScript code makes a lot of sense and can lead to better re-use, simplified maintenance, and better overall organization of code in an application. By placing Ajax calls into a re-useable JavaScript object we can use them throughout an application more easily, minimize duplicate code, and provide a better maintenance story. 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 pattern that can be used is the Revealing Module Pattern. By using the pattern you can encapsulate variables and functions into an object, achieve re-use, simplify maintenance, and can also help avoid naming collisions. There are several other patterns such as the Prototype Pattern and Revealing Prototype Pattern that can be used (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.
The following code 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 of 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') }
Listing 1. Function-based JavaScript Code with no encapsulation.
Listing 2 shows this same code 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 re-useable 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');
Listing 2. Organize functions in an object to provide encapsulation, re-use, easier maintenance, and to help avoid naming collisions.
Looking through the code in Listing 2 you'll see that 3 functions are defined including start(), stop(), and turn(). All three are publicly exposed to consumers 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 Listing 2) you can call it directly using code such as the following:
car.start(); car.stop(); car.turn();
If you want to create a new instance of car the code in Listing 3 can be used instead of the code shown in Listing 2. Notice that the Car object starts with an upper-case 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 }; };
Listing 3. Using the Revealing Module Pattern to create a Car object that can be created using the "new" keyword.
To use the Car object the following code can be written:
var car = new Car('V8'); car.start(); car.stop(); car.turn();
If you only need one object in memory as an application is running the code shown in Listing 2 works well. If you need multiple instances of an object the self-invoking parenthesis can be removed as shown in Listing 3.
Now that you've seen how the Revealing Module Pattern can be used to structure JavaScript code, let's see how it can encapsulate Ajax functions into an object.
Consolidating Ajax Calls
A sample application named Account at a Glance (download it here – an image from the application is shown below) 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 re-used throughout the application. By following this pattern, an Ajax call used to retrieve market quotes can be defined in one place and then called from other scripts that may need the data. This approach provides several benefits including more modular, re-useable, and maintainable code. If something changes with an Ajax call you can go to one place to make the core modifications rather than searching through multiple scripts and pages to find where changes need to be made.
To access market quote data in the application the following call could be made as data is needed in a given script:
$.getJSON('/DataService/GetQuote', { symbol: sym }, function (data) { //process data here });
Although this code works, it's much better from a re-use and maintenance standpoint to consolidate calls into an object. Listing 4 shows an example of an Ajax-specific object named dataService that the Account at a Glance application uses to retrieve different types of 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 }; } ();
Listing 4. Consolidating Ajax calls into a dataService object.
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 re-useable, 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. An example of calling the dataService object's getAccount() function is shown next:
dataService.getAccount(acctNumber, renderAccountTiles);
When the data is returned the getAccount() function will invoke the renderAccountTiles callback function shown in Listing 5.
renderAccountTiles = function (data) { $('div.tile[id^="Account"]').each(function () { sceneStateManager.renderTile(data, $(this), 500); }); }
Listing 5. The renderAccountTiles() function handles data returned from the call to dataService.getAccount().
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 while remaining 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 re-use and simplified maintenance. Eliminating Function Spaghetti Code is definitely a good thing.
If you'd like additional details about structuring JavaScript code or building an end-to-end application check out my Structuring JavaScript Code or Building an ASP.NET MVC, EF Code First, HTML5, and jQuery courses from Pluralsight.