Offline Apps with HTML5: A case study in Solitaire

One of my contributions to the newly-launched Sencha Touch mobile framework is the Touch Solitaire game. This is not the first time I have ventured into the dizzying excitement of Solitaire game development; you may remember the wonderful Ext JS Solitaire from 18 months ago. I'm sure you'll agree that the new version is a small improvement.

Solitaire

Solitaire is a nice example of a fun application that can be written with Sencha Touch. It makes use of the provided Draggables and Droppables, CSS-based animations, the layout manager and the brand new data package. The great thing about a game like this though is that it can be run entirely offline. Obviously this is simple with a native application, but what about a web app? Our goal is not just having the game able to run offline, but to save your game state locally too.

The answer comes in two parts:

Web Storage and the Sencha data package

HTML5 provides a brand new API called Web Storage for storing data locally. You can read all about it on my Web Storage post on Sencha's blog but the summary is that you can store string data locally in the browser and retrieve it later, even if the browser or the user's computer had been restarted in the meantime.

The crucial part of the sentence above is that we can only store string data. In the case of a game of Solitaire we need to store data on the elapsed time and number of moves as well as the location and status of each card. This doesn't sound like the kind of data we want to manually encode into a string, so thankfully the data package comes to the rescue.

The Sencha Touch data package is a complete rewrite of the package that has been so successful in powering Ext JS 3.x. It shares many of the same philosophies and adds the learning we have gained from developing Ext JS 3.x over the past year. One of the new capabilities it offers us is a Local Storage proxy, which automatically marshalls your model data into local storage and transparently restores it when you need it.

Using the new proxy is simple - all we need to do is set up a new Store, specifying the Proxy and the Model that will be saved to it. Models are the spiritual successor to Ext JS 3.x's Records. Now whenever we add, remove or update model instances in the store they are automatically saved to localStorage for us. Loading the store again is equally easy:

//set the store up
var gameStore = new Ext.data.Store({
proxy: new Ext.data.LocalStorageProxy({
id: 'solitaire-games'
}),
model: 'Game'
});

//saves all outstanding modifications, deletions or creations to localStorage
gameStore.sync();

//load our saved games
gameStore.read({
scope: this,
callback: function(records) {
//code to load the first record
}
});
//set the store up
var gameStore = new Ext.data.Store({
proxy: new Ext.data.LocalStorageProxy({
id: 'solitaire-games'
}),
model: 'Game'
});

//saves all outstanding modifications, deletions or creations to localStorage
gameStore.sync();

//load our saved games
gameStore.read({
scope: this,
callback: function(records) {
//code to load the first record
}
});

And just like that we can save and restore games with Web Storage. We can visit our app's webpage and start a game then come back later and find it automatically restored. But we still can't play offline, for that we need the application cache.

The HTML5 Application Cache Manifest

The application cache is one of the best features of HTML5. It provides a simple (though sometimes frustrating) way of telling the browser about all of the files your application relies on so that it can download them all ready for offline use. All you have to do is create what's known as a manifest file which lists all of the files the application needs - the Solitaire manifest looks like this:

CACHE MANIFEST
#rev49

resources/icon.png
resources/loading.png

resources/themes/wood/board.jpg
resources/themes/wood/cards.png

resources/css/ext-touch.css
resources/solitaire-notheme.css
resources/themes/wood/wood.css
resources/themes/metal/metal.css

ext-touch-debug.js
solitaire-all-debug.js
CACHE MANIFEST
#rev49

resources/icon.png
resources/loading.png

resources/themes/wood/board.jpg
resources/themes/wood/cards.png

resources/css/ext-touch.css
resources/solitaire-notheme.css
resources/themes/wood/wood.css
resources/themes/metal/metal.css

ext-touch-debug.js
solitaire-all-debug.js

We tell the browser about the manifest file by pointing to it in the tag's manifest atttibute. When the browser finds this file it downloads each of the listed assets so that they are ready for offline consumption. Note that it does not automatically include them on the page, you still need to do that yourself via the usual link and script tags. Here's a snippet of the Solitaire index.html file:

<!doctype html>
<html manifest="solitaire.manifest">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Solitaire</title>

<link rel="stylesheet" href="resources/css/ext-touch.css" type="text/css">
<link rel="stylesheet" href="resources/solitaire-notheme.css" type="text/css">
<link rel="stylesheet" href="resources/themes/wood/wood.css" type="text/css">

<script type="text/javascript" src="ext-touch-debug.js"></script>
<script type="text/javascript" src="solitaire-all-debug.js"></script>
<!doctype html>
<html manifest="solitaire.manifest">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Solitaire</title>

<link rel="stylesheet" href="resources/css/ext-touch.css" type="text/css">
<link rel="stylesheet" href="resources/solitaire-notheme.css" type="text/css">
<link rel="stylesheet" href="resources/themes/wood/wood.css" type="text/css">

<script type="text/javascript" src="ext-touch-debug.js"></script>
<script type="text/javascript" src="solitaire-all-debug.js"></script>

Note the manifest file definition in the html element at the top, and the fact that we still include our page resources the normal way. It sounds easy, but without a little setup first it can be a very frustrating experience. Usually your browser will try to cache as many files as possible, including the manifest file itself - we don't want this. As soon as your browser has a long-term cache of the manifest file it is extremely difficult to update your application - all of the files are already offline and won't be updated, and the browser won't even ask the server for an updated manifest file.

Preventing this behaviour turns out to be fairly easy, and the solution in its simplest form comes in the shape of a .htaccess file with contents like the following:

<Files solitaire.manifest>
ExpiresActive On
ExpiresDefault "access"
</Files>
<Files solitaire.manifest>
ExpiresActive On
ExpiresDefault "access"
</Files>

This directs Apache to tell the browser not to cache the manifest file at all, instead requesting the file from the server on every page load. Note that if the device is currently offline it will use the last manifest file it received.

This is half the battle won, but let's say you change one of your application files and reload - you'll find nothing happened. This is because when your browser asked the server for the manifest file it actually asked if the file had changed or not. As the manifest itself wasn't updated, the server responds with a 304 (Not Modified) and your browser keeps the old file.

To make the browser pick up on the change to the application file you need to update the manifest file itself. This is where the mysterious "#rev49" comes in on the manifest example file above. This is a suggestion from the excellent diveintohtml5 article on the subject - whenever you change any application files just bump up the revision number in the manifest file and your browser will know to download the updated files.

One final detail is that your Apache server probably isn't set up to server manifest files with the correct mime type, so be sure to add the following line to your Apache config and restart the server:

AddType text/cache-manifest .manifest
AddType text/cache-manifest .manifest

Wrapping it up

Offline access is a big deal for mobile apps and Sencha Touch makes them much easier to write. The benefit is not so much that the apps can run without an internet connection (many modern touch devices have a near-permanent connection to the internet already), but that web apps can now be treated as first-class citizens alongside native apps.

The fact that many devices allow your users to save your app to their home screen and load it as though it were native is an important step - you keep all of the advantages of web app deployment while gaining some of the benefits of native apps. As more and more native hardware APIs become available to web apps their importance will only grow.

If you want to check out Solitaire's offline support for yourself visit the application's site and save it to your iPad's home page. Try turning on airplane mode and loading the app and see how it behaves as though it were native. If you don't have an iPad, you can load the app in up-to-date versions of Chrome or Safari and get a similar experience.

Share Post:

What to Read Next

If you're interested in more about game development with Ext JS, check out my post on ExtJS Solitaire where I share my experience creating a Solitaire game using the ExtJS library. Additionally, for a deeper understanding of the Sencha Touch framework, you might find the Sencha Touch tech talk at Pivotal Labs insightful, as it provides a concise overview of its capabilities.