Writing a data-centric oriented web application using HTML5 local storage
By Dariusz Parys, Developer Evangelist, Microsoft
The basic application that was developed alongside this article catalogues products and divides them into categories. Those categories as well as the products can be created, updated and deleted. In addition to this typical CRUD approach, there are other standard tasks that will be handled: internationalization, validation of input and controlling the application via the keyboard. One of the most important aspects of the app is that it will use HTML5 local storage in order to allow offline editing.
Karhu - products overview list
How to start
For this example application I used Sammy.js, primarily because I already know it but also because it is small, well written, tested, and does all the things I need to get started. It does not provide you with an implicit MVC structure but it allows you to easily build upon its base. The only dependency it currently has is jQueryand that is a library I personally use for DOM manipulation anyway. The directory structure I started with looks like this:
The application file
The actual Sammy.js application is created in app.js - here the controllers are loaded and their routes are initialized. I tend to namespace all the variables (controllers, models, etc.) I create. In this case I chose to call this namespace karhu.
First we start loading plugins such as Mustachewhich is a template rendering engine. Then we initialize helpers (karhu.ApplicationHelper) and controllers (karhu.Products). Once the app is defined and all the DOM elements are loaded, you can run the sammy app and direct it to the initial route which is the index of all products.
Once headless browsers like Phantomjs are available as Capybara drivers, it will probably make sense to use those instead of Selenium as they are a lot faster.
For unit testing there are a lot of different possibilities. I used to work with jspec, since it is similar to Ruby's rspec which I have used before. Recently that has been deprecated in favor of jasmine, so I've used that here. It works quite well and brings a raketask that allows it to easily run alongside the acceptance tests. One of the unit tests for the example application looks like this:
Once I finish the scenario I start with the controller, which is very simple when just starting out:
At the moment there is only one route defined, which is a GET on the #/productsroute. The callback will be run once the location hash in the URL changes to /products. So if you append the route to your URL (like http://localhost:4567/index.html#/products) the attached callback will be executed. The same will happen when the application is just started, because we defined in our app.jsthat the initial path points to the same route.
Debugging controllers/products.js with Internet Explorer F12 Developer Tools
We extend the object with all the attributes of the product and then we attach the category of the product to the object. attachCategoryis kept inside the closure to make it a private function. Important to note in this code is the use of the underscore functions which are provided by Underscore.js. Underscore defines helpers for enumerables and helps one to write easy to read, concise code.
Interacting with the product model in the web app
In the above case, we do not need an additional view layer object because the rendering logic is very basic - it is just iterating over the objects of the products we created and displaying the attributes of each, including the category name we attached beforehand. The logic-free mustache template that will be rendered looks like this:
Rendered HTML output from the above mustache template
Moving model specific controller code into the model
It is 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 above code in a more model-centric fashion, I could do something like this:
Standard tasks and difficulties
Most applications, including ours, add basic security by making the user log in before using the application. Since HTTP is stateless, we need to resend the authentication with every request. We can accomplish this by saving a token when first logging in and then using that for every request thereafter. The way I chose to do that was to save a token in local storage once the user had logged in successfully and send that token as a header attached to the XMLHttpRequest. The code to do this looks similar to the following. It is stashed in a backend model which is used by the helpers I mentioned earlier:
X-Karhu-Authentication included in HTTP request
One case is that we already have a token, the other is that the user just logged in and there is a user(name) and a password available. Either way we attach the token or user/password combination as a header and if the request is successful, we know that the user is authenticated successfully. Otherwise the backend will just return an error. This approach was relatively straight forward to implement and the only issue I encountered was that the code became a little complex and unreadable. To fix this I refactored the helpers in to separate model. Abstracting the requests into a backend model is quite common, as, for example, seen in the Backbone.js library where it is a core part of the library. Authentication code is often unique per-application and always depends on the backend and what it expects the frontend to send.
Language switched to German
Validation error on creating a new category
Validation goes further. Navigation away from unsubmitted forms is prohibited. The user has to submit a valid data entry or cancel proactively data entry.
Red flash notification warns user on unsubmitted form data
Caching objects for offline editing
This is the most central and complex part of the application. All objects need to be cached ahead of time so that once we are offline they can be correctly sorted, paginated and filtered. There needs to be a queue for all actions being done before the objects are cached, so that those actions can be applied to the objects as soon as they are cached. There also needs to be a second queue that is filled once we actually are offline, so that when we are back online, everything that was done offline can be patched through to the backend.
App response when taken offline
There are several issues that need to be addressed outside of the already complicated caching and queuing process. For example, when an object is created when offline, it cannot be updated or deleted without further code because it does not have an id. I worked around that for now by simply disallowing those actions for objects created while offline. Another issue was that categories created while offline cannot be used for creating products; here again the reason being that they do not have an id yet. I simply do not display those categories in the list of available categories for creating a product. Some of those 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 either be done 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:
Integration into Windows
Internet Explorer 9 is great in running HTML5 applications. Further 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 easier for your users to access. Direct jump list tasks to go to the views add product, add categories, products overviewand categories overview. The following code snippet demonstrates how to declare a simple jump list task:
At this point, all of the requirements for the example application were implemented. With enough time one could also handle the issues mentioned above. 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.
And to get a detailed overview on how to pin your HTML app / site to the Windows taskbar have a look at the website Build My Pinned Site.
About the authors