UI Code Expert
Caching in on HTML5 Local Storage
HTML5 introduces client-side caching for local and session storage. Learn how to boost your app's performance through the new functionality.
With all the excitement around the Microsoft BUILD conference -- at which "Windows 8" was unveiled -- it's only logical that we would continue the UI Code Expert column's in-depth coverage of HTML5. Arguably one of the most useful HTML5 enhancements for those writing database-centric Web sites is support for a client-side caching mechanism through either local storage or session storage. In Peter Vogel's article, "HTML5 for ASP.NET Developers" (September 2011), he introduced the local storage topic. In this article, I'll take a more detailed look at its capabilities and how to use it.
Client Caching Benefits
HTML5 storage support is designed for two key scenarios. First, it enables a site to work offline. As site users input data, even when offline, the data can be stored locally on the client and then published up to the host server when a connection's available. Similarly, data retrieved from a Web site can be cached locally so that it's available at a later time, even when the site's unavailable.
A second feature of HTML5 storage is the ability to provide a local cache for data generally on the server but not subject to frequent change. By taking advantage of HTML5 storage, it's possible to pull down data and cache it locally so that future interaction with the data doesn't require re-downloading it. Similarly, HTML5 storage enables only downloading the parts of the data that change, rather than repeatedly downloading the entire data set, including the unchanged portions.
HTML5 storage includes two different types: LocalStorage and SessionStorage. The first of these is designed as a long-term storage mechanism for data that spans sessions and time, and provides a mechanism for longer-term storage of data. It can be used for things like offline e-mail support, where the data cached locally should be consistent across sessions and browser invocations.
LocalStorage is great for relatively static files or database tables, to increase performance when the data is needed and to potentially offer offline versions of the data.
In contrast, SessionStorage is only persistent for the life of a particular user session. As a result, two simultaneous sessions from the same machine wouldn't be able to share SessionStorage data, making SessionStorage more suitable for temporary session-specific states, such as caching data related to a game state.
Developing with HTML5 Storage
The basics of using HTML5 storage are exactly that -- basic. The essentials are a localStorage property on the JavaScript Window object. Given an instance of LocalStorage, you can call setItem(name, value) and getItem(name), respectively, to store and retrieve the string local storage. Following is an example using QUnit (the testing framework for jQuery) to test:
test("Basic Reading an Writing with window.localStorage",
function () {
var expected = new Date().toString();
window.localStorage.setItem("dateTimeStamp", expected);
var actual = window.localStorage.getItem("dateTimeStamp");
equal(actual, expected, "The values were unexpectedly different");
});
For those unfamiliar with QUnit, the test function is used to write a test. In this case, the test includes a single validation method call -- equal -- that verifies what was written to (and retrieved from) local storage was identical.
The strongly typed method names are getItem and setItem, but there's an easier shortcut for accessing the values because of the dynamic capabilities of JavaScript. Instead of using the methods to read and write the data, we can use properties in which the property name is the name used when calling the getItem and setItem methods:
test("Basic Reading an Writing with LocalStorage properties",
function () {
var expected = new Date().toString();
window.localStorage.dateTimeStamp = expected;
var actual = window.localStorage.dateTimeStamp;
equal(actual, expected, "The values were unexpectedly different");
});
Properties are easier to read, and although they take advantage of the dynamic nature of JavaScript, they're no less strongly typed than the method names were; this is because the name argument passed to getItem and setItem isn't checked by the compiler anyway.
Worth mentioning are two other functions on window.localStorage: clear and removeItem(name). An example is shown in Listing 1.
Notice that clear removes all storage associated with the Web site while removeItem targets a specific named storage value.
A key behavior to note with HTML5 storage is that it only works with strings. Attempting to store anything other than a string will call toString implicitly and store the result. This is true for data types as simple as Date, or more complex ones like arrays (see Listing 2).
This significantly complicates storing data in HTML5 storage, because it requires serializing all data into a string for storage and de-serializing it back out again when retrieving the data. For this reason, it's helpful to provide a simple wrapper around the HTML5 storage, one that automatically handles the serialization to and from a string.
Fortunately, this is exactly what JSON is designed to do, so we can rely on this mechanism for storing data. The LocalStorage class shown in Listing 3 demonstrates such a wrapper (this is a modified version of Stephen Walter's wrapper, which can be found here).
The JSON serialization functionality can be found here; this is natively supported by the latest versions of the major browser, however, so no explicit reference is required.
By leveraging this class for reading and writing data, we're able to handle more complex data types like Arrays (see Listing 4).
There's a caveat when it comes to Date serialization: Because JavaScript doesn't have a native Date literal, there's no standard for encoding and parsing dates. This in turn leads to inconsistencies between the data stored using JSON and the data retrieved. For a workaround, see "How Do I Serialize Dates with JSON."
What makes support for Arrays particularly beneficial when combined with a database-centric application is how easy it becomes to cache data from the database. By converting the data from a Web service (OData, for example) into an array, you can cache it into the localStorage. Then use the cached copy next time the page loads, rather than loading the data from the database again and incurring the latency from sending the data across to the browser.
HTML5 Storage Limits
Unfortunately, the amount of space available to HTML5 storage varies from browser to browser. This results in the need to check for exceptions related to exceeding the storage capacity. To support this, calls to setItem need to be wrapped in a try-catch block so as to handle overage appropriately. If it's safe to assume that a failed attempt to store data locally is innocuous (forcing a contingent mechanism for cached data, for example), then it's possible to change the set function in LocalStorage to automatically catch and suppress the exception, as shown here:
this.set = function (name, value) {
try {
window.localStorage.setItem(name, JSON.stringify(value));
} catch (exception) {
if ((exception != QUOTA_EXCEEDED_ERR) &&
(exception != NS_ERROR_DOM_QUOTA_REACHED)) {
throw exception;
}
}
};
In a similar fashion, try-catch blocks could be added for any JSON exceptions that might arise. In fact, you may want to consider suppressing all exceptions, regardless of the cause, and assume that callers will downgrade gracefully, disallowing caching or offline access.
Browser Support
One final important concern is browser support. All the major browsers include support for HTML5 storage, including Firefox 3.5, Safari 4, Internet Explorer 8 and Chrome 4. In addition, there are alternative mechanisms for earlier browsers, but they're all browser-specific implementations. To check if the browser has support, evaluate whether typeof(localStorage) is undefined or not, as shown here:
if (typeof (localStorage) == 'undefined') {
...
}
Using this logic, all calls to window.localStorage within LocalStorage could potentially be wrapped by this check and, if not supported, return do nothing or return null; this assumes again there's an acceptable contingency in place such as no offline or caching support available:
function LocalStorage() {
...
this.set = function (name, value) {
if (typeof (localStorage) != 'undefined') {
window.localStorage.setItem(name, JSON.stringify(value));
}
};
...
}
Learning how to cache locally will pay off in better applications and more engaged users.
About the Author
Mark Michaelis (http://IntelliTect.com/Mark) is the founder of IntelliTect and serves as the Chief Technical Architect and Trainer. Since 1996, he has been a Microsoft MVP for C#, Visual Studio Team System, and the Windows SDK and in 2007 he was recognized as a Microsoft Regional Director. He also serves on several Microsoft software design review teams, including C#, the Connected Systems Division, and VSTS. Mark speaks at developer conferences and has written numerous articles and books - Essential C# 5.0 is his most recent. Mark holds a Bachelor of Arts in Philosophy from the University of Illinois and a Masters in Computer Science from the Illinois Institute of Technology. When not bonding with his computer, Mark is busy with his family or training for another triathlon (having completed the Ironman in 2008). Mark lives in Spokane, Washington, with his wife Elisabeth and three children, Benjamin, Hanna and Abigail.