1 August 2011
Familiarity with online and offline data storage, JavaScript
Advanced
One of the most interesting features in HTML5 is the ability to store data locally and to allow the application to run offline. There are three different APIs that deal with these features and choosing one depends on what exactly you want to do with the data you're planning to store locally:
The most basic implementation of storing data locally on the user's machine is with the web storage API. This API uses key/value pairs to allow developers to store basic information and variables that can be accessed by the web application. An ideal use case for this is for storing simple data that needs to remain persistent after the user has browsed away from the application or closed the web browser. Saving game state, saving navigation location, or storing some specific information that you want to use across the entire web application (like a username or name) but that you don't want to use cookies for. A similar API can also be used to store data for an individual session. That data is automatically cleared after the user browses away from the application or shuts down the browser.
You need to keep the following in mind when using the web storage API:
The first thing to do to use the web storage API is to make sure that the user's browser supports it:
function checkLocalStorageSupport() {
try {
return 'localStorage' in window && window['localStorage'] !== null;
} catch (e) {
return false;
}
}
As might be obvious from that code snippet, web storage uses an object called localStorage which is an object of the windowclass. The snippet above checks to see whether localStorage is actually an object on window and will then return true so the application can leverage the local storage APIs.
Adding and returning data from the localStorage object is as easy as calling getter and setter methods that are implemented by the local storage specification. Any type of data can be stored using localStorage , but everything is stored as a string within the storage area. That means that it may be necessary to parse the data before sending it to localStorage or after using it. For instance, to store a JavaScript object, using JSON and calling stringify() before and parse() after retrieving the data would be required. The same thing goes for parsing numeric variables once they have been retrieved from the localStorage object.
In this example, a single form input is set up so that when the user clicks Submit the data is stored to the local cache area. When the page is loaded, if the data has already been stored, the page will display the stored information with a greeting. This is the function that is called when the user clicks Submit:
function onClick()
{
if(checkLocalStorageSupport)
{
window.localStorage.setItem("name",document.getElementById("name").value);
}
}
This function uses the setItem method on the localStorage object and then uses the value of the form to populate the storage cache. When the page is loaded, the application has an onLoad function that checks to see if the data is already in the local cache and will add a greeting.
function onLoad()
{
if(checkLocalStorageSupport)
{
var name = window.localStorage.getItem("name");
if(name != null)
{
window.document.getElementById("divName").innerHTML = "Welcome back " + name;
}
}
}
While users can delete the localStorage data using their browsers at any time, it may also make sense to give them an option to clear it from the application itself. localStorage comes with a clear() method that does just that. The code below is triggered when a user clicks a reset button in your application. If you want to remove a specific item from storage, you can use the removeItem() method to just delete a single key from local storage.
function onReset()
{
if(checkLocalStorageSupport())
{
window.localStorage.clear();
}
}
The web storage API also includes a way to listen for and respond to any changes to the local storage. By adding an event listener and listening for a storage event, the application can respond to localStorage changes. The data in the event includes the name of the key that was changed, the new value, the old value (if applicable), and the URL for the page that called the API. The spec for the localStorage API is implemented in such a way that the session who initiated the event won't see the event fire. That's because the specification says that the event should only fire for other tabs or sessions and not the one that changed the storage.
To listen for storage events, the first step is to add the event listener:
window.addEventListener("storage",onStorageChange);
Then set up the onStorageChange event to handle the storage event.
function onStorageChange(e) {
if(e.key == "name")
{
alert(e.newValue + ' just added their name to local storage');
}
}
There is a similar API for creating data that will only persist for the individual session. By using the sessionStorage object instead of localStorage, any saved data will automatically be cleared whenever the user leaves the page. In fact, the API has the exact same methods so you can go through and just replace localStorage with sessionStorage and local data will be stored until the user closes their browser or the tab with the application in it.
Sometimes simply storing a bit of data on the user's machine won't be enough. In many cases, the entire application may have to work offline, not just store some data. For that use case, HTML5 includes the ability to cache files and assets on the user's machine that the browser can then access without an Internet connection. That means images, JavaScript files, HTML files, CSS files, pretty much anything that make up the web application can be stored locally and accessed even without an Internet connection. The key to this is setting up a cache manifest file.
The manifest file is part of a new manifest attribute on the root HTML tag for the page. It is a file on the web server that lists all of the files that the browser should download and save so they can be used later. It has a .manifest extension and the only major gotcha is that the web server has to support the .manifest mime type, so make sure that the web server that will host the application can serve up .manifest files correctly.
The manifest file has a basic structure. Every manifest file starts with CACHE MANIFEST and from there just lists all the files that the browser needs to cache for offline access. Here's a simple example that will store some JavaScript, a CSS file, a couple of images, and the HTML page:
CACHE MANIFEST
style.css
offlinescript.js
images/dreamweaver_logo.png
images/edge_logo.png
The paths are all relative to the HTML page that the user is accessing. There are a couple of other options to be aware of when creating the cache manifest. One is the case of files that should never be cached. Maybe a dynamic script or something that only makes sense to have available online. The cache manifest file can be divided into sections that will tell the browser how to react to certain content. By creating a NETWORK and listing files that should never be cached, the browser will always ignore those files and never make them available offline.
Another case might be when the user tries to access a page that wasn't cached or something that was supposed to be cached wasn't saved correctly. The cache manifest API offers a FALLBACK section that points to a page which will be loaded in the above use case. So when the user tries to access something that wasn't saved, they will see a message about being offline instead. Here's what a theoretical cache manifest could look like with these sections:
CACHE MANIFEST
NETWORK:
my_dynamic_script.cgi
FALLBACK:
my_offline_message.html
CACHE:
style.css
offlinescript.js
images/dreamweaver_logo.png
images/edge_logo.png
In this example, I have an HTML page with an external JavaScript page and external CSS page. The HTML page displays some text describing an Adobe logo and when you click the image, the JavaScript will swap out the image and text for another logo. Here's the HTML code followed by the JavaScript function:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" manifest="cache.manifest">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Adobe Logos</title>
<script src="offlinescript.js"></script>
<link href="style.css" rel="stylesheet" />
</head>
<body>
<div id="textContent">This is the Edge logo:</div><br />
<img id="logo" name="logo" src="images/edge_logo.png" onclick="onLogoClick();" />
<p class="small">Click on the logo to swap it out.</p>
</body>
</html>
// JavaScript Document
function onLogoClick(e)
{
var currentContent = window.document.getElementById("textContent").innerHTML;
if(currentContent == "This is the Edge logo:")
{
window.document.getElementById("textContent").innerHTML = "This is the Dreamweaver logo:";
window.document.logo.src = "images/dreamweaver_logo.png";
} else {
window.document.getElementById("textContent").innerHTML = "This is the Edge logo:";
window.document.logo.src = "images/edge_logo.png";
}
}
The key piece to all of this is the HTML tag with the manifest attribute. That's pointing to my cache.manifest file that I reference above. That manifest file instructs the browser to download all of the files on that list. It doesn't matter if the user loads those files as they're browsing, the browser will automatically download everything in the manifest file. That means both images will be saved for offline access even though the second one doesn't load on the page until I interact with the content. So just by loading this page once I am able to interact fully with it offline and both images will rotate.
The last part of offline access that is important to touch on are the events that occur during the cache process. When the browser encounters the manifest attribute it fires off a series of events on the window.applicationCache object. The first thing that happens is that a checking event fires. This event figures out what needs to be done with this particular cache file. The Google Chrome developer tools provide a great look at the events happening as the cache saves its data (see Figure 1).
If this is the first time a user has visited the site, then a downloading event will fire and the web browser will go through and download all of the assets in the manifest file. It reads the manifest file, figures out how many files it needs to download, and then sends back status updates in the form of progress events for each file. The progress event includes a loaded variable and a total variable so that the developer can determine how much of the cache has been stored.
function onProgress(e)
{
var content = window.document.getElementById("loadedInfo").innerHTML;
window.document.getElementById("loadedInfo").innerHTML = content + '<br /> Loaded file ' + e.loaded + ' of ' + e.total;
}
Once everything has finished saving, the browser fires a cached event to notify the developer that all of the files have been successfully saved for offline use.
Any subsequent time the user visits the page, the browser will check to see if anything has changed in the manifest file. If not, it fires a noupdate event and moves on. If something has changed, it goes through the exact same process as above; it will fire off a downloading with a series of progress events until the files have all been updated. When that happens, instead of firing a cached event, the browser will fire a updateready event to signify that all of the files have been updated and are ready to use offline.
The last event to worry about is the error event which will fire anytime something goes wrong. It could be that files didn't load correctly, that the browser couldn't access the cache manifest, or one or more of the files listed in the manifest wasn't available. To catch that, simply add an event listener for the error event.
window.applicationCache.addEventListener("error",onError);
function onError(e)
{
window.document.getElementById("loadedInfo").innerHTML = "Something went wrong while saving the files for offline use.";
}
The last type of storage that HTML5 introduced is the one that is most in flux currently. Originally there was a Web SQL specification that is no longer maintained. Now most of the energy has moved into working on an Indexed Database API and it looks like this will be the way to store information in a relational database going forward. Firefox and Chrome both support IndexedDB but because the spec and support are still in flux, it's beyond the scope of this article. I plan to update it as that changes down the road.
In this article I explored two of the main ways to store information locally. The first provides for some very basic data storage in the form of the web storage API. With that you can save name value pairs either beyond sessions or for a single session. The second, the application cache manifest, allows developers to store entire files on the local machine so that they can be accessed offline.
For more information on the HTML5 storage APIs, refer to the following resources:
This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License. Permissions beyond the scope of this license, pertaining to the examples of code included within this work are available at Adobe.