October 2011

Volume 26 Number 10

HTML5 - Writing a Business-Oriented JavaScript Web Application

By Frank Prößdorf | October 2011

Microsoft is putting lots of effort into promoting HTML5 and JavaScript as key for Windows developers, and there are quite a number of high-quality libraries and frameworks available for building production-ready applications. In this article, we’ll create a basic business-oriented application that can serve as a starting point for digging deeper into the existing options—and let you experience how much fun coding quality JavaScript can be.

The application will catalogue products and divide them into categories, and both products and categories can be created, updated and deleted (CRUD), as shown in Figure 1. In addition to these typical CRUD operations, the application will handle other standard tasks: internationalization, validation of input and keyboard control of the application. One of the most important aspects of the app is that it will use HTML5 local storage in order to allow offline editing. We won’t get into the details of all of this here, but you’ll find the complete code for this example application on codeplex.

The Products Overview List
Figure 1 The Products Overview List

Getting Started

So how do you go about writing a business-oriented JavaScript application? One well-established way is by using a Model-View-Controller (MVC) structure. This has been successfully employed in frameworks like Ruby on Rails, Django or ASP.NET MVC. MVC allows for a strict structure and the separation of the concerns of the application, like the view and business logic. This is especially important with JavaScript because it’s really easy to write confusing code, doing everything in one place. We often have to remind ourselves not to do that and to try and write clean, readable and reusable code. There are several frameworks built for an MVC structure, most notably Backbone.js, Eyeballs.js and Sammy.js.

For this example application we used Sammy.js, primarily because we already know it, but also because it’s small, well-written, tested and does all the things we need to get started. It doesn’t provide you with an implicit MVC structure but it allows you to easily build upon its base. The only dependency it currently has is jQuery and that’s a library we use for DOM manipulation anyway. The directory structure I started with looks like this:

- public
  - js
      app.js
    + controllers
    + models
    + helpers
    + views
  + templates
  - vendor
    - sammy
        sammy.js
    - jquery
        jquery.js

We put all the template files that might be rendered through the JavaScript code in the templates directory, and all the JavaScript code relevant for rendering those templates in the views directory.

The Application File

We create the actual Sammy.js application in app.js—here the controllers are loaded and their routes are initialized (see Figure 2). We tend to namespace all the variables (controllers, models, and so forth) I create. In this case, we chose to call this namespace karhu, the name of the company whose products we’re cataloging.

Figure 2 Karhu.app

karhu.app = $.sammy(function() {
  this.element_selector = '#main';
  this.use(Sammy.Mustache, 'mustache');
  this.use(Sammy.NestedParams);
  this.use(Sammy.JSON); 
  this.helpers(karhu.ApplicationHelper);
  this.helpers({ store: karhu.config.store });
  karhu.Products(this);
});
$(function() {
  karhu.app.run('#/products');
});

The first step is to load plug-ins such as Mustache, which is a template-rendering engine. Then you initialize helpers (karhu.ApplicationHelper) and controllers (karhu.Products). Once the app is defined and all the DOM elements are loaded, you can run the app and direct it to the initial route, which is the index of all products.

Writing Tests

Before showing you how the product controller works and displays all the products, we want to briefly go into how the quality of JavaScript applications can be greatly increased through testing. As we went about developing the example application, before every major step we first wrote an acceptance test to ensure that the code would actually work. This also prevents regressions, guaranteeing everything written before also still functions correctly. For more complex code, we write unit tests and try to cover most of the cases that might occur when running the code. One of the easiest and most readable ways to write acceptance tests is to use Capybara with Selenium, though once headless browsers like PhantomJS are available as Capybara drivers, it will probably make sense to use those instead of Selenium, as they’re a lot faster.

For our first scenario (Figure 3), let’s test whether we can see a list of products.

Figure 3 A Testing Scenario

Feature: Products
  In order to know which products I have
  As a user
  I want to see a list of products
  Scenario: list products
    Given a category "Trees" with the description "Plants"
      And a product "Oak" with the description "Brown"
                                         and the price "232.00€"
                                         that is valid to "12/20/2027"
                                         and belongs to the category "Trees"
      And a product "Birch" with the description "White"
                                         and the price "115.75€"
                                         that is valid to "03/01/2019"
                                         and belongs to the category "Trees"
    When I go to the start page
    Then I should see "Trees"
      And I should see "Oak"
      And I should see "Brown"
      And I should see "232.00€"
      And I should see "12/20/2027"
      And I should see "Birch"

For unit testing, there are a lot of different possibilities. We used to work with jspec, because it’s similar to Ruby’s rspec, which we’ve used before. Now jspec has been deprecated in favor of Jasmine, so we’ve used that here. It works quite well and includes a rake task that allows it to easily run alongside the acceptance tests. Here’s what one of the unit tests for the example application looks like:

describe("Product", function() {
  describe("attachCategory", function() {
    it("should assign itself its category", function() {
      var categories = [{id: 1, name: 'Papiere'}, {id: 2, name: 'Baeume'}];
      var attributes = {id: 1, name: 'Fichte', category_id: 2};
      var product = new karhu.Product(attributes, categories);
      expect(product.category.name).toEqual('Baeume');
    });   
  });
});

Defining Controllers

Once we finish the scenario we start writing the controller, which is very simple at first:

karhu.Products = function(app) {
  app.get('#/products', function(context) {
    context.get('/categories', {}, function(categories) {
      context.get('/products', {}, function(products) {
        products = products.map(function(product) { return new karhu.Product(
          product, categories); });
        context.partial('templates/products/index.mustache', {products: products});
      });
    });
  });
};

At the moment there’s only one route defined, which is a GET on the #/products route.The callback will be run once the location hash in the URL changes to /products. So if you append the route to your URL (such as https://localhost:4567/index.html#/products), the attached callback will be executed. The same will happen when the application is first started, because in app.js we defined that the initial path points to the same route.

Inside the route we retrieve the categories and products via helpers that do just a basic AJAX GET request to our back end. Once we retrieve this data, we map it to JavaScript objects and then render those objects inside the index.mustache template. This will render them in the <div id="main"> HTML tag, which was defined as the root element_selector in the app.js file.

Defining Models

We need to map the data to JavaScript objects so we can associate the products with the category they belong to and render the name of the category alongside the product, which looks like this:

karhu.Product = function(attributes, categories) {
  _.extend(this, attributes);
  attachCategory(this, categories);
  function attachCategory(product, categories) {
    product.category = _.find(categories, function(category) {
      return parseInt(category.id, 10) === parseInt(product.category_id, 10);
    });
  }
};

We extend the object with all the attributes of the product and then we attach the category of the product to the object. We keep attachCategory inside the closure to make it a private function. Note in this code the use of the underscore functions, which are provided by Underscore.js. This library defines helpers for enumerables, and helps you write easy-to-read, concise code.

Figure 4 shows how the model will appear to the user.

Interacting with the Product Model in the Web App
Figure 4 Interacting with the Product Model in the Web App

Rendering Templates

With the model just shown, we don’t need an additional view layer object because the rendering logic is very basic—it just iterates over the objects of the products we created and displays the attributes of each, including the category name we attached beforehand. The logic-free mustache template that will be rendered looks like what’s shown in Figure 5.

Figure 5 The Mustache Template

<h2>Products</h2>
<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Description</th>
      <th>Price</th>
      <th>Valid To</th>
      <th>Category</th>
    </tr>
  </thead>
  <tbody>
    {{#products}}
      <tr>
        <td>{{name}}</td>
        <td>{{description}}</td>
        <td>{{unit_price}}</td>
        <td>{{valid_to}}</td>
        <td>{{#category}}{{name}}{{/category}}</td>
      </tr>
    {{/products}}
  </tbody>
</table>

The rendered output is shown in Figure 6.

Rendered HTML Output from the Mustache Template
Figure 6 Rendered HTML Output from the Mustache Template

Moving Model-Specific Controller Code into the Model

It’s a matter of taste how much responsibility a controller is given and how much can be refactored into model code. If I want to write the code in Figure 5 in a more model-centric fashion, I could do something like Figure 7.

Figure 7 A More Model-Centric Approach

Controller

karhu.Products = function(app) {
  app.get('#/products', function(context) {
    karhu.Product.all(function(products) {
      context.partial('templates/products/index.mustache', {products: products});
    });
  });
};

Model

karhu.Product.all = function(callback) {
  karhu.backend.get('/categories', {}, function(categories) {
    karhu.backend.get('/products', function(products) {
      products = products.map(function(product) { return new karhu.Product(product, categories); });
      callback(products);
    });
  });
};

Standard Tasks

There are a number of tasks that are very common to Web development and will recur when you work on JavaScript applications. We want to explain how we tackled those tasks and solved the problems we encountered. As usual, there are several different ways to approach an issue.

Authentication Most applications, including this one, add basic security by making the user log in. Because HTTP is stateless, you need to resend the authentication with every request. You can take care of this by saving a token when the user first logs in and then using that token for every request thereafter. What I chose to do was to save a token in local storage once the user had logged in successfully, and then send that token as a header attached to the XMLHttpRequest. The code to do this is shown in Figure 8. This code is stashed in a back-end model that’s used by the helpers we mentioned earlier.

Figure 8 Saving a Token When a User Logs In

this.get = function(url, data, success, error) {
  sendRequest('get', url, data, success, error);
};
function authenticate(xhr) {
  var token = '';
  if(karhu.token) {
    token = karhu.token;
  } else if(karhu.user && karhu.password) {
    token = SHA256(karhu.user + karhu.password);
  }
  karhu.token = token;
  xhr.setRequestHeader("X-Karhu-Authentication", 'user="' + karhu.user + '", token="' + karhu.token + '"');
};
function sendRequest(verb, url, data, success, error) {
  $.ajax({
    url: url,
    data: data,
    type: verb,
    beforeSend: function(xhr) {
      authenticate(xhr);
    },
    success: function(result) {
      success(result);
    }
  });
}

Figure 9 shows a saved user token.

X-Karhu-Authentication Included in an HTTP Request
Figure 9 X-Karhu-Authentication Included in an HTTP Request

If the user just logged in, you have a username and a password available. If the user logged in earlier, you have a saved token. Either way, you attach the token or user/password combination as a header and if the request is successful, you know that the user is authenticated successfully. Otherwise the back end will just return an error. This approach was relatively straightforward to implement and the only issue we encountered was that the code became a bit complex and unreadable. To fix this, we refactored the helpers into a separate model. Abstracting the requests into a back-end model is quite common, as, for example, with the Backbone.js library where it’s a core part of the library. Authentication code is often unique for each application and it always depends on the back end and what it expects the front end to send.

Internationalization (I18n) Internationalization is a common task for Web applications, and jquery.global.js is often used to accomplish it in JavaScript applications. This library provides methods for formatting numbers and dates and allows you to translate strings using a dictionary for the current locale. Once you load this dictionary, which is a simple JavaScript object with keys and translated values, the only thing you need to pay attention to is formatting numbers and dates. A sensible place to do that is in the models before rendering the objects to the templates. In the product model it would look something like this:

var valid_to = Date.parse(product.valid_to);
product.valid_to = $.global.format(valid_to, "d");

Figure 10 shows German as the display language.

Switching the Language to German
Figure 10 Switching the Language to German

Validation One of the benefits of developing in JavaScript is that you can give the user real-time feedback. It makes sense to use this potential to validate the data before it’s sent to the back end. Note that it’s still necessary to validate the data in the back end as well because there might be requests that don’t use the front end. The jQuery library jquery.validate.js is often used for validation. It provides a set of rules on a form and shows errors on the appropriate input fields if the content doesn’t match the rules. It makes sense to structure those validation rules into the models we already have, so every model has a validations function that returns the rules. Here’s how the validation for our category model could look:

karhu.Category = function() {
  this.validations = function() {
    return {
      rules: {
        'category[name]': {
          required: true,
          maxlength: 100
        }
      }
    };
  };
};

Figure 11 shows how an error might display.

A Validation Error on Creation of a New Category
Figure 11 A Validation Error on Creation of a New Category

Validation goes further. Navigation away from unsubmitted forms is prohibited. The user must actually submit valid data or proactively cancel data entry (see Figure 12).

A Red Flash Notification Warns User of Unsubmitted Form Data
Figure 12 A Red Flash Notification Warns User of Unsubmitted Form Data

Caching Objects for Offline Editing Sometimes users will need to work offline. Allowing for this is the most central and complex part of the application. All objects need to be cached ahead of time so that once the app is offline they can be correctly sorted, paginated and filtered. There needs to be a queue for all actions taking place before the objects are cached, so that those actions can be applied to the objects as soon as they’re cached. There also needs to be a second queue that’s filled once we actually are offline, so that when we are back online, everything that was done offline can be patched through to the back end. Figure 13 shows the application offline.

The Application Response When Taken Offline
Figure 13 The Application Response When Taken Offline

There are a number of issues that need to be addressed besides the already complicated caching and queuing process. For example, when an object is created when offline, it can’t be updated or deleted without further code because it doesn’t have an id. We worked around that for now by simply disallowing those actions for objects created while offline. For that same reason, categories created while offline can’t be used for creating products. I simply don’t display those categories in the list of available categories for creating a product. These types of problems might be solved by working with temporary ids and by rearranging the offline queue.

In addition, the available partials and template need to be cached. This can be done either through a cache manifest as defined in HTML5 if the targeted browser group supports it, or simply through loading the partials and putting them into local storage. This is quite simple with Sammy.js and looks something like this:

context.load('templates/products/index.mustache', {cache: true});

Integration into Windows

Internet Explorer 9 is great for running HTML5 applications. Moreover, it gives Web applications the ability to natively integrate into the Windows 7 Taskbar, enhance the application with the possibility to show notifications, integrate navigation and offer jump list support. The integration of jump lists is straightforward, in its simplest form just a declaration of meta tag attributes. This is exactly the approach used in Karhu, which makes it easy for users to access what they need. You can direct jump list tasks to go to the views add product, add categories, products overview and categories overview (see Figure 14). The following code demonstrates how to declare a simple jump list task:

<meta name="msapplication-task"
      content="name=Products;
      action-uri=#/products;
      icon-uri=images/karhu.ico" />

Jump List Tasks
Figure 14  Jump List Tasks

You can learn all about pinning and Windows 7 integration at Build My Pinned Site, which has additional ideas for Web apps, such as browser notifications and dynamic jumplists using JavaScript.  It’s easy to add more functionality to your Web app with just a little extra effort. Other starting points for getting a better understanding of the subject are the MSDN JavaScript Language Reference and the documentations of the aforementioned libraries and framework.

Wrapping Up

At this point, we’ve implemented all of the basic requirements for the example application. With enough time, we  could also take care of the issues previously mentioned. Tasks like authentication, internationalization and handling business logic need to be coded independent of the frameworks and libraries, which are really just a starting point.

If you always write tests and pay attention to the structure of the application, writing production-ready JavaScript applications that can continue to evolve is, in our opinion, not only possible but a goal well worth investing in. Getting started is easy, but it’s important to keep checking for a clean code base and refactoring if necessary. If these requirements are met, JavaScript gives you the opportunity to write very elegant and maintainable applications.


Frank Prößdorf is a freelance Web developer and co-founder of NotJustHosting who loves working with Ruby and JavaScript. His passion is discovering and playing with new technologies. He regularly supports open source software as he is excited about the opportunity to contribute and share code and ideas.  Besides his work, he enjoys traveling, sailing and playing tennis.  Get to know Prößdorf via his github profile or his Web site.

Dariusz Parys is a developer evangelist at Microsoft Germany, doing Web development with a strong focus on upcoming technologies. Currently he’s writing a lot of JavaScript and HTML5.

Thanks to the following technical expert for reviewing this article: Aaron Quint