Recently I have started working with Drupal 7. Drupal is a CMS built on top of PHP, MySQL, and jQuery. In Drupal, a website layout is managed by a Theme. Boostrap Themes can be plugged in – or, you can roll your own. At Qualcomm, my group created a custom theme that required a newer version of JQuery (1.11) than Drupal 7 provided (1.4).
Initially, our custom theme loaded the newer version of jQuery. Drupal 7 allows this as it uses jQuery’s .noConflict() function. .noConflict() is frees up jQuery’s $ alias for other frameworks to use. In this case the “other” framework … was just a newer version of jQuery.
There were two problems with this …
We were loading jQuery TWICE. When visiting the site Drupal would load jQuery 1.4 – and then our custom theme would load jQuery 1.11.
We discovered that any jQuery plugins loaded by Drupal for jQuery 1.4 needed to be reloaded by our custom theme after it loaded jQuery 1.11.
I found this out the hard way. I kept getting TypeError: $(…).once is not a function” errors. This was because our custom theme required the jquery.once.js plugin. This plugin was initially loaded by Drupal when it loaded JQuery 1.4 – but subsequentally wiped out by our custom theme’s newer JQuery.
To fix this we did the following …
We added the desired version of JQuery to our custom theme :
Notice that jQuery is injected rather than its $ alias. As I mentioned earlier this is because Drupal invokes jQuery’s .noConflict() function. To tidy things up a bit I inject jQuery as $.
We stopped our custom theme from loading its own version of jQuery. It is no longer necessary as (1) and (2) above will force Drupal to use whatever version of jQuery you desire.
The JQuery UI Autocomplete Widget allows you to return a list of suggestions based upon a query string. The widget works great unless the user does not know what to search for. For example, if the user wants to search through a large database of people using only a single letter (presumably the first letter of the last name) the Autocomplete Widget quickly becomes unwieldy. Either the suggestions need to be truncated or the list of suggestions becomes so large as to be unusable.
To resolve this problem I have extended the JQuery UI Autocomplete Widget and added pagination. Arrows allow the user to cycle through pages of suggestions. Each page of is retrieved using AJAX. Click here for a jsFiddle sample. The sample uses MockJax to simulate the AJAX calls. A complete end-to-end example using ASP.NET MVC is available here.
The Paginated Autocomplete Widget inherits the same API as the Autocomplete Widget from which it is derived. However, there are some differences to be aware of …
First, the web server endpoint that handles the AJAX GET must support three parameters – search, pageSize, and pageIndex. search is the query string provided by the user. pageSize is the number of items to return per page. pageIndex is the page to return.
[HttpGet]
public JsonResult SearchCars(string search, int pageSize=20, int pageIndex=0)
{
// Your logic here.
}
ASP.NET MVC Controller ActionMethod to support the Paginated Autocomplete Widget.
Second, the Paginated Autocomplete expects a JSON response from the web server in a specific format. The Widget requires this format to facilitate pagination. data is an array of text/value objects that will get bound to to the list of suggestions. total is the total number of unpaginated suggestions that the search query returned.
{
data : [
{ text : "Item 1", value : 0 },
{ text : "Item 2", value : 1 },
{ text : "Item 3", value : 2 }
],
total : 1000
}
JSON result expected by the Paginated Autocomplete Widget.
Third, when using the Paginated Autocomplete Widget in Javascript you need to specify sourceUrl. The Widget will automatically generate an AJAX GET to the sourceUrl and retrieve paginated suggestions as necessary. This is different than the Autocomplete Widget where you need to define the source method as well as the underlying AJAX GET. The obfuscation is necessary to facilitate pagination. In addition pageSize is an optional parameter that determines the number of suggestions per page.
Using the Paginated Autocomplete Widget in Javascript.
The Paginated Autocomplete Widget requires JQuery 1.9.1 or newer and JQuery UI 1.10.3 or newer. I have tested it against Internet Explorer 7/8/9/10, Firefox 21, and Chrome 27. It will probably run in other permutations as well. Please let me know if you have any comments or questions.
Javascript is garbage collected but it can still leak memory. The majority of these leaks are caused by Javascript “hooks” into the DOM. There are numerous articles on the internet that provide Javascript memory leak patterns to avoid. Here is a real-world example that I encountered while working with Telerik’s Kendo Grid.
My implementation of the Kendo Grid has a 30 seconds refresh interval. When an interval elapses the Kendo Grid’s Datasource is refreshed from the web server and a dataBound() event handler is triggered. The dataBound() event handler creates elements for the newly fetched data and binds them to the DOM. In the dataBound() event handler we attach several event handlers to these newly created DOM elements. I use these event handlers to show dialogs and such when the user clicks on a cell in the Kendo Grid.
The memory leak occurs when the Kendo Grid is refreshed. On a refresh the Kendo Grid removes the elements from the DOM that were previously added during the last dataBound() invocation. Kendo does not dispose of these elements as expected. Specifically, the event handlers that were previously attached to these elements are not “told” that the elements were removed. This causes a memory leak because the event handlers are retained in memory indefinitely. Every refresh of the Kendo Grid causes another generation of these event handlers to be retained.
Fortunately, fixing the leak is easy. You just need to detach every event handler prior to removing the DOM element to which it is bound. Wen using the Kendo Grid you want to attach your event handlers in the dataBound() event. You want to detach your event handlers in the dataBinding() event. If you use JQuery’s .on() to attach an event handler ( this includes .click(), change(), etc…) you need to use .off() to detach it. If you use JQuery’s .bind() to attach an event handler you need to use .unbind() to detach it.
I’ve created a couple of examples to demonstrate the before and after effects of properly handling this memory leak. The initial source code is taken directly from Kendo’s Demo Site. I have attached a .click() event handler in the Kendo Grid’s dataBound() event handler. The .click() event simply displays an alert pop-up whenever a user clicks on a cell in the table. This is the jsFiddle portraying the memory leak. If I detach the .click() event handler in the Kendo Grid’s dataBinding() event handler the memory leak disappears. This is a jsFiddle with the “fix” applied.
This is the memory footprint of each jsFiddle over a 20 minute period. The green line portrays the memory leak. The red line portrays the “fix”. The memory leakage is around 100-200 kb every 10 minutes.
For a long-running single-page application this is not good. Moving forward I now make memory profiling part of my testing regime. Please let me know if you have any questions.
Notes
The JQuery .empty() function is supposed to recursively remove all child elements as well as detach all event handlers. I have not looked at the Kendo Grid source code. However, if .empty() were being called to dispose of obsolete elements upon a Datasource refresh I would expect this memory leak to not occur.
Javascript Interpretters are not commonly found in the wild – they are built into a Web Browser. Javascript interpretters hosted within a Web Browser expose an API to interact with the DOM.
Command-Line Interpretters
There are, of course, exceptions. Rhino, V8, and Windows Script Host run outside of a Web Browser on a command-line. Command-Line Javascript Interpretters are not bound to a Web Browser and as such they do not have a DOM API – as there is no DOM.
Unit Test Frameworks
There are many different flavors of Unit Test Frameworks. When running a Unit Test each of these Frameworks follows one of two methodologies …
In-Browser
An “In-Browser” Unit Test is run directly within a Web Browser. The Unit Test leverages the Web Browser’s Interpretter and DOM API. The Test Result is displayed as a web page and must be visually inspected – or programatically scraped. This can make automation difficult.
JsUnit and QUnit are two commonly used In-Browser Test Frameworks.
Headless
A “Headless” Unit Test is run using a Command-Line Javascript Interpretter. “Headless” means “No monitor”. The Test Result can theoretically be piped anywhere. This makes it easier to automate.
PhantomJs is a Headless Test Framework that utilizes JavaScriptCore and WebKit. JavaScriptCore is the Javascript Interpretter used by Safari. WebKit is the DOM API / Rendering Engine used by Safari and Chrome.
Keep in mind that although Headless Unit Tests are run outside of a Web Browser they are not really Web Browser agnostic. A Headless Unit Test Framework is only as good as the libraries that it depends on. For example, a passing Unit Test in PhantomJs could theoretically fail in Internet Explorer or Firefox simply because the later use entirely different Javascript and DOM API’s.
How to Write an In-Browser Unit Test
Moving forward I am going to show you how to use QUnit to write In-Browser Unit Tests. Other In-Browser Testing Frameworks are very similar.
Components of a QUnit Unit Test
Writting QUnit Unit Tests requires three things …
QUnit Test Runner (.html)
Unit Tests (.js)
Production Scripts (.js)
QUnit Test Runner
A Test Runner is just a Web Page that links together your Unit Tests and your Production Scripts. When opened in a Web Browser the Test Runner will run the Unit Tests and display the results. Mocked-up HTML Markup can be applied to the Test Runner if the Production Scripts being tested require it. The Mocked-up HTML is reset after each Unit Test is run.
This is what the Test Runner’s markup looks like …
This is what the Test Runner looks like in a Web Browser …
Unit Tests
A Unit Test verifies the functionality of Production Scripts. As your Production Scripts change during development it is the Unit Test’s job to verify that proper functionality is maintained. Each Unit Test invokes a function exposed by your Production Scripts and then Asserts that the outcome is correct. The Assertion could be as simple as verifying a return value or as complicated as verifying that the DOM has been modified apporpriately. Unit Tests are grouped into Modules. Multiple Modules can exist within a single Javascript file. A Unit Test can have any number of Assertions. The number of expected Assertions must be declared.
This is a simple example of a Unit Test. No Production Script is invoked – the Unit Test just asserts that “Hello” is not equal to “World”. “MyUnitTest – Group 1” is an arbitrary name to associate with the Unit Test. The name is displayed in the Test Runner when the Unit Test is run. “1” corresponds to the number of Assertions that are performed by the Unit Test. QUnit supports a handful of different Assertions…
These are the scripts that you want to test. They should be identical to whatever gets deployed in your Production environment.
A Simple Unit Test
If you’re lucky your Production Scripts do not have any external dependencies and do not interact with the DOM.
For example, on a recent project I needed the ability to move an element in an Array from one index to another. To do this I extended the Javascript Array object with two functions – move() and indexOf().
The Production Scripts look like this. First, I check to see if the functions already exist for the Array object. If they don’t I simply add them …
if (!Array.prototype.move) {
// Description : Moves an element in an array.
// Params: old_index - The old index of element.
// new_index - The new index of element.
// http://stackoverflow.com/questions/5306680/move-an-array-element-from-one-array-position-to-another
Array.prototype.move = function (old_index, new_index) {
if (new_index >= this.length) {
var k = new_index - this.length;
while ((k--) + 1) {
this.push(undefined);
}
}
this.splice(new_index, 0, this.splice(old_index, 1)[0]);
return this; // for testing purposes
};
}
if (!Array.prototype.indexOf) {
// Description : Finds the index of an object in the specified array
// Params: obj - The object.
// start - The index to start looking for the object in the aray.
// http: //stackoverflow.com/questions/1744310/how-to-fix-array-indexof-in-javascript-for-ie-browsers
Array.prototype.indexOf = function (obj, start) {
for (var i = (start || 0), j = this.length; i < j; i++) {
if (this[i] === obj) { return i; }
}
return -1;
}
}
Extensions.js
As for Unit Testing there are two things that I want to verify. First, I want to make sure that the Production Scripts extended the Array object. Here is what the Unit Test for move() looks like …
Next, I want to make sure that the new Array functions work correctly. To do this I simply create an array, invoke the new Array functions against it, and then use QUnit Assertions to verify the result …
The QUnit Test Runner links everything together. Notice that that the Unit Tests are referenced after the Production Scripts. The QUnit Test Runner loads and processes scripts in order of their appearance. It’s important to not reference your Unit Tests first as the Production Scripts that they are testing may not be loaded yet …
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>QUnit Test Suite</title>
<!-- QUnit Dependencies -->
<link rel="stylesheet" href="http://code.jquery.com/qunit/qunit-1.5.0.css">
<script src="http://code.jquery.com/qunit/qunit-1.5.0.js" type="text/javascript"></script>
<!-- JQuery -->
<script src="http://code.jquery.com/jquery-1.5.min.js" type="text/javascript"></script>
<script src="http://code.jquery.com/ui/1.8.13/jquery-ui.min.js" type="text/javascript"></script>
<!-- Your Production Script(s) -->
<script src="Extensions.js"></script>
<!-- Your Unit Test Script(s) --->
<script src="Extensions.UnitTest.js"></script>
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture">
<!-- The HTML Markup that your Production Script(s) Target -->
</div>
</body>
</html>
TestRunner.html
This is what the Test Runner looks like in a Web Browser when running …
We have been tasked with creating Production Scripts and corresponding Unit Tests to support the required functionality. To do this we’re going to take a Test-Driven approach. Specifically, we’re going to …
Stub out the Production Scripts.
Create the Test Runner.
Create the (Failing) Unit Tests.
Finish the Production Scripts (to Satisfy the Failing Unit Tests).
Stub Out Production Scripts
As a rule of thumb it is very important to keep your Production Script functions as specific as possible. The more complicated the function the more difficult the Unit Test will be to write. You do not want a function that singlehandedly scrapes the DOM, performs a JQuery Ajax POST, and then updates the DOM. Instead, you will want to break down the function into discrete operations.
The application’s functionality can be broken into the following stubbed out functions …
function selectInventoryOnChange (event) {
// TODO : Get Category from selectInventoryCategory <select> and invoke getInventoryFromServer().
}
function getInventoryFromServer ( inventoryCategory ) {
// TODO : Make AJAX GET Request to Web Server for Category and invoke refreshInventoryTable() with retreived Data.
}
function refreshInventoryTable ( jsonInventoryData ) {
// TODO : Refresh <table> with Inventory Data.
}
function showAddInventoryForm () {
// TODO : Display the formAddInventory <form> in a JQuery UI Dialog.
}
function addNewInventoryClick () {
// TODO : Retreive data from formAddInventory <form>, invoke addNewInventory(), and hide JQuery UI Dialog.
}
function addNewInventory ( formData ) {
// TODO : Make AJAX POST to Web Server with <form> data.
}
InventoryManagement.js (Stubbed Out)
As these functions are going to be leveraged by our Unit Tests and the web application we should apply some best practices to make them more portable …
Production Script Best Practices
Namespacing
First, lets encapsulate the functions into a Namespace. Global functions are a no-no as the can overwrite (or be overwritten) by any other Javascript that your application might reference …
var ScottsJewels = ScottsJewels || {};
ScottsJewels.InventoryManagement = ScottsJewels.InventoryManagement || {};
ScottsJewels.InventoryManagement.selectInventoryOnChange = function (event) {
// TODO : Get Category from selectInventoryCategory <select> and invoke getInventoryFromServer().
}
ScottsJewels.InventoryManagement.getInventoryFromServer = function ( inventoryCategory ) {
// TODO : Make AJAX GET Request to Web Server for Category and invoke refreshInventoryTable() with retreived Data.
}
ScottsJewels.InventoryManagement.refreshInventoryTable = function( jsonInventoryData ) {
// TODO : Refresh <table> with Inventory Data.
}
ScottsJewels.InventoryManagement.showAddInventoryForm = function () {
// TODO : Display the formAddInventory <form> in a JQuery UI Dialog.
}
ScottsJewels.InventoryManagement.addNewInventoryClick = function () {
// TODO : Retreive data from formAddInventory <form>, invoke addNewInventory(), and hide JQuery UI Dialog.
}
ScottsJewels.InventoryManagement.addNewInventory = function ( formData ) {
// TODO : Make AJAX POST to Web Server with <form> data.
}
InventoryManagement.js (Stubbed Out using Namespace)
Now instead of invoking our Production Scripts as a global function like this …
addNewInventory ();
We can invoke them through their namespace like this …
Next, let’s restrict access to the functions using the Module Pattern. The Module Pattern permits encapsulation of our functions …
var ScottsJewels = ScottsJewels || {};
ScottsJewels.InventoryManagement = ScottsJewels.InventoryManagement || {};
ScottsJewels.InventoryManagement = (function () {
return {
selectInventoryOnChange : function (event) {
// TODO : Get Category from selectInventoryCategory <select> and invoke getInventoryFromServer().
},
getInventoryFromServer : function ( inventoryCategory ) {
// TODO : Make AJAX GET Request to Web Server for Category and invoke refreshInventoryTable() with retreived Data.
},
refreshInventoryTable : function ( jsonInventoryData ) {
// TODO : Refresh <table> with Inventory Data.
},
showAddInventoryForm : function () {
// TODO : Display the formAddInventory <form> in a JQuery UI Dialog.
},
addNewInventoryClick : function () {
// TODO : Retreive data from formAddInventory <form>, invoke addNewInventory(), and hide JQuery UI Dialog.
},
addNewInventory : function ( formData ) {
// TODO : Make AJAX POST to Web Server with <form> data.
}
};
});
InventoryManagement.js (Stubbed Out using Module Pattern)
Notice that all of the above functions are publically accessible in this Module …
var module = ScottsJewels.InventoryManagement();
module.addNewInventory();
The Module pattern also allows for private functions and data members …
Finally, let’s make the Module a Singleton. The Singleton Pattern provides a common context for all Production Script invocations as well as a smaller memory footprint. It’s as easy as adding () onto the end of the Module declaration. Essentially you are invoking the Module once when it is parsed and loaded …
var ScottsJewels = ScottsJewels || {};
ScottsJewels.InventoryManagement = ScottsJewels.InventoryManagement || {};
ScottsJewels.InventoryManagement = (function () {
return {
selectInventoryOnChange : function (event) {
// TODO : Get Category from selectInventoryCategory <select> and invoke getInventoryFromServer().
},
getInventoryFromServer : function ( inventoryCategory ) {
// TODO : Make AJAX GET Request to Web Server for Category and invoke refreshInventoryTable() with retreived Data.
},
refreshInventoryTable : function ( jsonInventoryData ) {
// TODO : Refresh <table> with Inventory Data.
},
showAddInventoryForm : function () {
// TODO : Display the formAddInventory <form> in a JQuery UI Dialog.
},
addNewInventoryClick : function () {
// TODO : Retreive data from formAddInventory <form>, invoke addNewInventory(), and hide JQuery UI Dialog.
},
addNewInventory : function ( formData ) {
// TODO : Make AJAX POST to Web Server with <form> data.
}
};
})();
InventoryManagement.js (Stubbed Out using Singleton Pattern)
Now you can access the singleton Module now without having to instantiate it …
To allow our Production Scripts to interact with the HTML Markup the HTML Markup needs to be added to the Test Runner’s <div id=”qunit-fixture”> element. Here is the Test Runner with the Production Scripts and HTML Markup added. A reference to the Unit Tests exists as well (although we still need to implement them) …
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>QUnit Test Suite</title>
<!-- QUnit Dependencies -->
<link rel="stylesheet" href="http://code.jquery.com/qunit/qunit-1.5.0.css">
<script src="http://code.jquery.com/qunit/qunit-1.5.0.js" type="text/javascript"></script>
<!-- JQuery -->
<script src="http://code.jquery.com/jquery-1.5.min.js" type="text/javascript"></script>
<script src="http://code.jquery.com/ui/1.8.13/jquery-ui.min.js" type="text/javascript"></script>
<!-- Your Production Script(s) -->
<script src="InventoryManagement.js"></script>
<!-- Your Unit Test Script(s) --->
<script src="InventoryManagement.Test.js"></script>
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture">
<!-- The HTML Markup that your Production Script(s) Target -->
<div class="manage-inventory">
<select id="selectInventoryCategory">
<option value="" selected="selected">(Select a Category)</option>
<option value="Drinks">Drinks</option>
<option value="Food">Food</option>
<option value="Dessert">Desssrt</option>
</select>
<table id="tableInventory">
<thead>
<tr>
<th>SKU</th>
<th>Name</th>
<th>Quantity</th>
</tr>
</thead>
<tbody/>
<tr>
<td colspan="3" >No Inventory</td>
</tr>
</tbody>
</table>
<button type="button">Add Inventory</button>
<form id="formAddInventory" class="hidden" action="http://localhost/Merchandise/UpdateMerchandise" method="post">
<fieldset>
<input type="text" id="inputSKU" name="sku">
<label for="inputSKU">SKU</label>
<input type="text" id="inputName" name="name">
<label for="inputName">Name</label>
<input type="text" id="inputQuantity" name="quantity">
<label for="inputQuantity">Quantity</label>
</fieldset>
</form>
</div>
</div>
</body>
TestRunner.html
As always there are a couple of “gotchas” that you need to look out for. Specifically …
HTML Markup Synchronization
In Production HTML Markup is served by a Web Server. A Unit Test must operate in isolation of the Web Server and all HTML Markup must be hard-coded in the Test Runner. The downside of this is that you must be vigilant to keep the HTML Markup in your Test Runner synchronized with whatever is generated by your Web Server.
Singleton Module and QUnit Problems
A Singleton Javascript Module and the QUnit Test Runner’s <div id=”qunit-fixture”> are not entirely compatible. If your Module selects elements from the DOM during initialization the elements will point to stale references after the first Unit Test is run.
The 2nd Unit Test will fail because the Test Runner refreshes the HTML Markup in <div id=”qunit-fixture”> after every Unit Test. This will cause $myDiv to point at a stale DOM element.
Create Unit Tests
Next we’re going to create the Unit Tests. To keep it simple we are just going to write a single Unit Test for each Production Script function. Each Unit Test will invoke it’s corresponding Production Script function and assert the results.
Here are the stubbed out Unit Tests along with what they need to assert …
QUnit.module( "ScottsJewels.InventoryManagement",
{
setup : function() {},
teardown : function() {}
});
QUnit.test( "selectInventoryOnChange()", 0, function() {
// Test : Verify that getInventoryaFromServer() is called with the selected inventory as a parameter.
});
QUnit.test( "getInventoryFromServer()", 0, function() {
// Test : Verify the AJAX HTTP GET Request is correct.
// Test : Verify that refreshInventoryTable() is called.
});
QUnit.test( "refreshInventoryTable()", 0, function() {
// Test : Verify the <table> is correctly refreshed in DOM.
});
QUnit.test( "showAddInventoryForm()", 0, function() {
// Test : Verify that the appropriate <form> is displayed.
// Test : Verify that the <form> fields are empty.
});
QUnit.test( "addNewInventoryClick()", 0, function() {
// Test : Verify that the <form> is hidden.
// Test : Verify that addNewInventory() is called with the appropriate data from the <form>.
});
QUnit.test( "addNewInventory()", 0, function() {
// Test : Verify the AJAX HTTP POST Request is correct.
});
InventoryManagement.UnitTest.js (Stubbed Out)
Let’s take a look at the first couple of Unit Tests …
The Unit Test for selectInventoryOnChange() must verify that getInventoryFromServer() is invoked properly.
The Unit Test for getInventoryFromServer() needs to verify that JQuery ajax() was invoked and that it, in turn, invokes refreshInventoryTable().
Fleshing out these two Unit Tests poses a couple of problems …
How does my Unit Test I verify that a Production Script function invokes another function?
How do I verify that JQuery ajax() was invoked correctly?
The same solution can be applied to all three : Mocking.
Mocking Dependenices
Mocking is when you create a new object that mimics the behavior of another object. A “mock” object is oftentimes substituted when the object that it is simulating is unavailable. In Unit Testing dependencies are mocked up when it is not practical to use the actual dependency.
In Javascript functions are objects and easily mockable. This comes in handly when verifying a JQuery ajax() call or asserting the invocation of a nested function. As an example, this is how you might mock up JQuery’s dialog() function for a Unit Test. It’s important to restore the original function implementation after you have conducted you tests …
var jQueryDialog_Orginal;
try {
// Backup.
jQueryDialog_Orginal = jQuery.fn.dialog;
// Implement your own.
jQuery.fn.dialog = function () {
// QUnit assertions...
};
// Run test(s) that implement jQuery Dialog.
}
finally {
// Restore.
jQuery.fn.dialog = jQueryDialog_Orginal;
}
Keep in mind that there are (much) more sophisticated 3rd Party mocking/stubbing Javascript libraries that you can use such as SinonJs and JSMock. This is how you would use mocking to flesh out the Unit Tests for selectInventoryOnChange() …
QUnit.test( "selectInventoryOnChange()", 1, function() {
// Test : Verify that getInventoryaFromServer() is called with the selected inventory as a parameter.
var $event,
$selectInventoryCategory,
original_getInventoryFromServer;
// Set the Inventory Category <select> with "Food" <option>.
$selectInventoryCategory = $("#selectInventoryCategory");
$selectInventoryCategory.val("Food");
// Create a "change" event.
$event = $.Event("change");
$event.target = $selectInventoryCategory;
try {
// Save a reference to getInventoryFromServer() so that it can be restored after the test has completed.
original_getInventoryFromServer = ScottsJewels.InventoryManagement.getInventoryFromServer;
// Now, assign a new function to getInventoryFromServer().
ScottsJewels.InventoryManagement.getInventoryFromServer = function ( inventoryCategory ) {
// Verify that getInventoryFromServer is invoked with the "Food" <option>.
equal( inventoryCategory, "Food" );
}
// Invoke selectInventoryOnChange() and send in the "change" event.
// selectInventoryOnChange() should invoke getInventoryFromServer which was (temporarily) redefined above.
ScottsJewels.InventoryManagement.selectInventoryOnChange ($event);
}
finally {
// Restore getInventoryFromServer().
ScottsJewels.InventoryManagement.getInventoryFromServer = original_getInventoryFromServer;
}
});
InventoryManagement.UnitTest.js
… and getInventoryFromServer() …
QUnit.test( "getInventoryFromServer()", 3, function() {
// Test : Verify the AJAX HTTP GET Request is correct.
// Test : Verify that refreshInventoryTable() is called.
var original_ajax;
try {
// Save a reference to JQuery's ajax() so that it can be restored after the test is complete.
original_ajax = $.ajax;
// Now, assign a new funtion to JQuery's ajax().
$.ajax = function (request) {
// Verify that "Food" was supplied as the ajax "data" parameter.
equal(request.data.inventoryCategory, "Food");
// Verify that the ajax is a GET.
equal(request.type, "GET");
// Verify that refreshInventoryTable() will be invoked if ajax is successful.
equal(request.success, ScottsJewels.InventoryManagement.refreshInventoryTable);
}
// Invoke JQuery's ajax(). This should invoke the (temporarily) re-defined ajax() above.
ScottsJewels.InventoryManagement.getInventoryFromServer ( "Food" );
}
finally {
// Restore JQuery's ajax().
$.ajax = original_ajax;
}
});
InventoryManagement.UnitTest.js
Executing the Test Runner at this time will inevitably lead to lots of failures. This is expected as our Production Scripts are merely stubbed out …
Finish Production Scripts
The last thing to do is to flesh out the Production Script functions. Ironically, the Production Scripts are a lot smaller (and less complicated) than their corresponding Unit Tests. This goes to show that Unit Testing Javascript is not a trivial task …
var ScottsJewels = ScottsJewels || {};
ScottsJewels.InventoryManagement = ScottsJewels.InventoryManagement || {};
ScottsJewels.InventoryManagement = (function () {
$(document).ready(function () {
var $selectInventoryCategory,
$buttonShowAddInventory;
$selectInventoryCategory = $("#selectInventoryCategory");
$buttonShowAddInventory = $("#buttonShowAddInventory");
// Hook up event handlers.
$selectInventoryCategory.bind ( "change", ScottsJewels.InventoryManagement.selectInventoryOnChange);
$buttonShowAddInventory.bind ( "click", ScottsJewels.InventoryManagement.showAddInventoryForm);
});
return {
selectInventoryOnChange : function (event) {
// Get Category from selectInventoryCategory <select> and invoke getInventoryFromServer().
var inventoryCategory;
inventoryCategory = $(event.target).val();
if (inventoryCategory !== "") {
ScottsJewels.InventoryManagement.getInventoryFromServer(inventoryCategory);
}
},
getInventoryFromServer : function ( inventoryCategory ) {
// Make AJAX GET Request to Web Server for Category and invoke refreshInventoryTable() with retreived Data.
$.ajax({
type: "GET",
url : "http://ScottsJewels/InventoryManagement/GetInventory",
data: { inventoryCategory : inventoryCategory },
success: ScottsJewels.InventoryManagement.refreshInventoryTable
});
},
refreshInventoryTable : function ( jsonInventoryData ) {
// Refresh <table> with Inventory Data.
var $tableInventoryBody,
html;
$tableInventoryBody = $("#tableInventory tbody");
html = "";
$(jsonInventoryData).each( function ( index, value ) {
html += "<tr>";
html += "<td class='sku'>" + value.SKU + "</td>";
html += "<td class='name'>" + value.Name + "</td>";
html += "<td class='quanity'>" + value.Quantity + "</td>";
html += "</tr>";
});
$tableInventoryBody
.empty()
.append(html);
},
showAddInventoryForm : function () {
// Display the formAddInventory <form> in a JQuery UI Dialog.
var $formAddInventory;
$formAddInventory = $("#formAddInventory");
$formAddInventory[0].reset();
$formAddInventory.dialog({
buttons: [{ text: "Ok",
click: ScottsJewels.InventoryManagement.addNewInventoryClick
}]});
},
addNewInventoryClick : function () {
// Retreive data from formAddInventory <form>, invoke addNewInventory(), and hide JQuery UI Dialog.
var $formAddInventory,
formData;
$formAddInventory = $("#formAddInventory");
formData = $formAddInventory.serialize();
$formAddInventory.dialog("close");
ScottsJewels.InventoryManagement.addNewInventory( formData );
},
addNewInventory : function ( formData ) {
// Make AJAX POST to Web Server with <form> data.
$.ajax({
type : "POST",
contentType : "application/x-www-form-urlencoded",
url : "http://ScottsJewels/InventoryManagement/AddInventory",
data : formData
});
}
};
})();
InventoryManagement.js
Executing the Test Runner at this point should result in a bunch of Passing Tests. Your Test Runner should look something like this …
Your responsibility as a conscientous Developer doesn’t end here. You should re-run your Unit Tests and verify complicance when …
Your Production Scripts interact with the DOM and the HTML structure is modified.
Your Production Scripts change.
A dependency that your Production Scripts use changes.
There is also a fair share of maintenance that comes along with the Unit Testing. If the above causes any of your Unit Tests to fail you might have to revise your Unit Tests to accommodate the failure. This could be as simple as updating the HTML Markup in your Test Runner or as complicated as refactoring an entire Unit Test.
I know what you’re thinking. Oh great, another article on AJAX! Okay, sure. You’re right. But seriously, when I was originally attempting to learn this stuff I found myself jumping through hoops just trying to get a simple example to work…
How do I configure a web service to return JSON?
What in the heck is JSON?
Why not use XML?
What is JQuery?
How do I call a Web Service from JavaScript?
Yeah, there were articles. Lots of articles. But I found that no single article presented a complete example. Some would focus on the client. Some the server. Some would gloss over the “why” and emphasize the “how”. Others would pull in obscure technology that, while cool, would be completely at odds in a business environment.
So, here’s my own entry into wide assortment of AJAX articles – a simple Echo Service.
Why an Echo Service?
It’s a very simple way to demonstrate how to send/receive JSON objects between a client browser and a Web Service. This is common pattern that is used in modern websites. A simple example makes it easier to showcase a bunch of cool (yet essential) technologies such as…
Like XML but with a (much) smaller footprint. JSON is Javascript. It can be serialized into a human-readable string or deserialized into a Javascript object that can be used programatically in Javascript code.
Microsoft’s implementation of a SOA framework. We’ll use it to create a web service.
The Echo Service
Overview
First, we’re going to create an EchoService Web Service using Microsoft’s Windows Communication Foundation (WCF). A Message class will be used to facilitate communication with the EchoService. The Message will be automatically converted to/from JSON by WCF.
Next, we’re going to create an ASP.NET EchoPage Web Form. A blank JSON representation of the Message class will be exposed to the EchoPage’s client-side. This JSON Message will be populated with some text and submitted to the EchoService using JQuery’s AJAX implementation.
Finally, we’re going to apply some enhancements to the EchoService and EchoPage to make them a little more robust – while eliminating unecessary code.
EchoService WCF Web Service
The EchoService exposes a single Echo() method that receives and returns a JSON object. Why a JSON object and not just a bunch of params? Because parameters are sloppy and difficult to manage. Also, it’s easier to deal with the same data object on both the server and the client.
The EchoService’s handling of JSON is declared in the attributes for the Echo() method. Notice the ResponseFormat and RequestFormat indicators. This tells WCF to implicitly convert between the Message class and JSON.
The client-side portion of the EchoPage uses JQuery’s AJAX implementation to submit (and receive) Messages to the EchoService. The “data” parameter specifies the data that you want to submit to the EchoService. The “dataType : json” instructs JQuery to expect a JSON result from the EchoService. JQuery will implicitly convert the EchoService’s response. The “success” function is called upon a successful transaction.
Be very careful when specifying your “data” parameter. It should be in the form of a JSON (name/value pair) object where the name corresponds to the parameter name on the EchoService’s Echo() method. The value is your actual data – serialized using the JSON.Stringify() function. Also, take special notice of the quotation marks. I’ve had problems before with single versus double-quotes.
What’s the deal with the .d in the AJAX success function?
Since ASP.NET 3.5 Microsoft’s WCF Service encapsulates all JSON responses in a “d” name-value pair for security reasons. You can read more about it here and here. A side-effect of this is that we need to handle the encapsulation in our client-side Javascript …
data = data.hasOwnProperty('d') ? data.d : data;
EchoPage.aspx
What’s the deal with the JSON.stringify() ?
When submitting a request to the EchoService using JQuery’s AJAX the data needs to be in the form of a string. JSON.stringify() serializes our Message (which is a JSON object) so that it can be submitted properly.
$.ajax({
type : "POST",
url : "EchoService.svc/Echo",
data : '{ "message" : ' + JSON.stringify(jsonMessage) + '}',
...
EchoPage.aspx
Server-Side (.aspx.cs)
The server-side portion of the EchoPage is relatively sparse. When we start optimizing our code later it will disappear entirely. For now, the server-side portion of the EchoPage creates a JSON representation of an empty Message.
protected string jsonMessage;
protected void Page_Load(object sender, EventArgs e)
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(Message));
using (MemoryStream ms = new MemoryStream())
{
serializer.WriteObject(ms, new Message());
jsonMessage = Encoding.Default.GetString(ms.ToArray()); // Viola!
}
}
EchoPage.aspx.cs
This JSON Message is exposed to the client-side Javascript as a var and is used in the JQuery AJAX submission to the EchoService.
var jsonMessage = <%= jsonMessage %>;
EchoPage.aspx
Okay, great. Is that it?
No, although the above code works there are a few issues that we should probably look into …
ASP.NET’s JSON security is a nice touch. However, our handling of the “d” encapsulation on the client-side of the EchoPage is clunky.
Our serialization of the Message object into JSON on the server-side of the EchoPage is very specific to a particular class.
If an error occurs while the EchoService is processing a Message there is no way to properly notify the EchoPage so that it might handle the error gracefully.
Polishing the Echo Service
JQuery’s AJAX dataFilter
JQuery exposes a customizable “dataFilter” for it’s AJAX calls. Declaring a dataType instructed JQuery to implicitly handle the response from a Web Service. Declaring a dataFilter puts the responsibility solely on the developer.
We can leverage JQuery’s AJAX dataFilter when handling ASP.NET’s “d” security feature on the EchoPage. Notice that we need to programatically deserialize the EchoService’s response into a Javascript object using JSON.parse().
$.ajax({
type : "POST",
url : "EchoService.svc/Echo",
data : '{ "message" : ' + JSON.stringify(jsonMessage) + '}',
/* dataType: "json", */
contentType : "application/json; charset=utf-8",
dataFilter : function(data) {
data = JSON.parse(data);
return data.hasOwnProperty("d") ? data.d : data;
},
success: function(data, textStatus, httpRequest) {
/* data = data.hasOwnProperty('d') ? data.d : data; */
$("#responseMessage").html(data.Text);
},
error: function(httpRequest, status, errorThrown) {
$("#responseMessage").addClass("error");
$("#responseMessage").html("There was a problem calling the Echo Service. Error : " + errorThrown + "!");
}
});
EchoPage.aspx
JSON Serialization Extension Method
NET 3.5 introduced Extension Methods. Extension Methods allow you to add new methods to an existing type. Adding an extension method to the .NET base object class exposes the method to all classes.
Currently the EchoPage can only serialize a Message into JSON. However, by moving the code into an Extension Method we can clean up the EchoPage and eliminate a lot of (potentially) redundant code. This is what the new .GetJsonString() Extension Method looks like …
/// <summary>
/// Extension methods to .NET classes.
/// </summary>
public static class Extensions
{
/// <summary>
/// Extends object to with a JSON serializer.
/// </summary>
/// The object as a serialized JSON string.
public static string GetJsonString(this object obj)
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(obj.GetType());
using (MemoryStream ms = new MemoryStream())
{
serializer.WriteObject(ms, obj);
return Encoding.Default.GetString(ms.ToArray());
}
}
}
Extensions.cs
The EchoPage can now call .GetJsonString() directly from the Message data object.
var jsonMessage = <%= (new SimpleAJAXEchoService.Message()).GetJsonString() %>
EchoPage.aspx
As far as the server-side code for the EchoPage? Gone.
/// <summary>
/// An Web Page that "talks" to the Echo Service using AJAX.
/// </summary>
public partial class EchoPage : System.Web.UI.Page
{
// The addition of the .GetJsonString() method eliminates all of this code. Furthermore, it can be called against any class derived from
// object (which is pretty much anything).
/* Gone!
/// <summary>
/// A serialized JSON representation of the Message class. Will be exposed to the EchoPage's Javascript and used to submit data to
/// the EchoService.
/// </summary>
protected void Page_Load(object sender, EventArgs e)
{
// We'll need to submit an instance of the Message class to the EchoService from the EchoPage using Javascript. JSON is an ideal format to
// work with. Let's serialize an instance of Message into a JSON string and expose it to the EchoPage's Javascript.
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(Message));
using (MemoryStream ms = new MemoryStream())
{
serializer.WriteObject(ms, new Message());
jsonMessage = Encoding.Default.GetString(ms.ToArray()); // Viola!
}
}
*/
}
EchoPage.aspx.cs
Encapsulating the Response
By wrapping the response from the EchoService we can more effectively handle errors that it might throw on the EchoPage. We can wrap up the EchoService’s response Message in a new ClientResponse wrapper. In addition to a Payload (the Message) the ClientResponse exposes an IsSuccessful and ErrorMessage.
/// <summary>
/// A generic client response wrapper. Wraps a web service response so that
/// additional (troubleshooting) information can be returned alongside the payload.
/// </summary>
/// The payload type to encapsulate.
[DataContract]
public class ClientResponse
{
/// <summary>
/// The data to return to the client.
/// </summary>
[DataMember]
public T Payload { get; set; }
/// <summary>
/// True, if the data was retrieved sucessfully.
/// </summary>
[DataMember]
public bool IsSuccessful { get; set; }
/// <summary>
/// The error message if the data was not retrieved sucessfully.
/// </summary>
[DataMember]
public string ErrorMessage { get; set; }
/// <summary>
/// Constructor.
/// </summary>
public ClientResponse()
{
IsSuccessful = true;
ErrorMessage = string.Empty;
}
/// <summary>
/// Constructor.
/// </summary>
/// True, if the data was retrieved sucessfully.
/// The data to return to the client.
/// The error message if the data was not retrieved sucessfully.
public ClientResponse(bool isSuccessful, T payload, string errorMessage) : this()
{
IsSuccessful = isSuccessful;
Payload = payload;
ErrorMessage = errorMessage;
}
}
ClientResponse.cs
We can leverage JQuery’s AJAX dataFilter to implictly handle the ClientResponse wrapper. Any errors incurred will now be passed onto JQuery’s AJAX error() function.
$.ajax({
type: "POST",
url: "EchoService.svc/Echo",
data: '{ "message" : ' + JSON.stringify(jsonMessage) + '}',
/* dataType: "json", */
contentType: "application/json; charset=utf-8",
dataFilter: function(data) {
data = JSON.parse(data);
data = data.hasOwnProperty("d") ? data.d : data;
if (data.hasOwnProperty("IsSuccessful")) {
if (data.IsSuccessful == true) {
return data.Payload;
}
else {
var errorMessage = "Error";
if (data.hasOwnProperty("ErrorMessage") && data.ErrorMessage !== null) {
errorMessage = data.ErrorMessage;
}
throw errorMessage;
}
}
return data;
},
error: function(httpRequest, status, errorThrown) {
$("#responseMessage").addClass("error");
$("#responseMessage").html("There was a problem calling the Echo Service. Error : " + errorThrown + "!");
}
});
EchoPage.aspx
Conclusion
Okay, sure. The Echo Service was a simple example. But hey! It showcased a whole bunch of cool technologies in a nice little package. Now, next time someone asks you on the street, “Excuse me, but what is AJAX?” you can look them in the eyes and reply, “Let me tell you about a simple little Echo Service that I know…”.
The source code is available here. Please let me know if you have and questions or comments.