Offline Apps: Application Cache - JUMP START HTML5 (2014)

JUMP START HTML5 (2014)

Chapter 23 Offline Apps: Application Cache

Application Cache―“AppCache” for short―provides a way to save the assets of an application locally so that they can be used without an internet connection. It’s especially well-suited to “single-page” applications that are JavaScript-driven and use local data stores such as localStorageand IndexedDB.

AppCache consists of two parts: the plain text file known as a manifest, and a DOM interface for scripting and help managing updates. By the end of this chapter, you’ll know how to write a cache manifest file, how to manage updates, and some of the aspects to watch out for when using this API.

Note: AppCache in IE

Application Cache is not supported in Internet Explorer 9 or below. You’ll need to use version 10 or later. For a complete list of browsers supporting AppCache, try CanIUse.com.

Cache Manifest Syntax

Cache manifest files are plain text files, but they must adhere to the manifest syntax. Let’s look at a simple example:

CACHE MANIFEST

# These files will be saved locally. This line is a comment.

CACHE:

js/todolist.js

css/style.css

Every cache manifest must begin with the line CACHE MANIFEST. Comments must begin with a #. Notice that each file is also listed as a separate line.

The CACHE: line is a section header. There are four defined section headers in total:

· CACHE: explicitly states which files should be stored locally

· NETWORK: explicitly states which URLs should always be retrieved

· FALLBACK: specifies which locally available file should be displayed when a URL isn’t available

· SETTINGS: defines caching settings

Saving Files Locally with the CACHE: Section Header

CACHE: is unique because it is an optional section header―and it’s the only one that is. Our cache manifest example could also be written without it:

CACHE MANIFEST

# These files will be saved locally. This line is a comment.

js/todolist.js

css/style.css

In this case, js/todolist.js and css/style.css will be cached locally, since they follow our CACHE: section header. So will the page that linked to our manifest, even though we haven’t explicitly said that it should be saved.

In these examples, we are using relative URLs. We can also cache assets across origins but the details are a little bit more complicated. In recent versions of Firefox, you can cache assets across origins when they share the same scheme or protocol (HTTP or HTTPS). You can, for example, cache assets from http://sitepoint.com on http://example.com. And you can cache assets from https://secure.ourcontentdeliverynetwork.com on https://thisisasecureurl.com. In these cases, the protocol is the same. What you can’t do is cache HTTPS URLs over HTTP or vice-versa. Current versions of IE, Safari, Chrome, and Opera allow cross-origin caching regardless of scheme. Caching assets from http://sitepoint.com on https://thisisasecureurl.com will work. This behavior could change, however, since it is out of line with the specification. Don’t depend on it. As of this writing, however, Chrome Canary ― version 33 ― doesn’t allow cross-origin requests from HTTP to HTTPS and vice versa.

Forcing Network Retrieval with NETWORK:

NETWORK: allows us to explicitly tell the browser that it should always retrieve particular URLs from the server. You may, for example, want a URL that syncs data to be a NETWORK resource. Let’s update our manifest from earlier to include a NETWORK section:

CACHE MANIFEST

# These files will be saved locally. This line is a comment.

js/todolist.js

css/style.css

NETWORK:

sync.cgi

Any requests for sync.cgi will be made to the network. If the user is offline, the request will fail.

You may also use a wildcard—or * character—in the network section. This tells the browser to use the network to fetch any resource that hasn’t been explicitly outlined in the CACHE: section. Partial paths also work with the wildcard.

Specifying Alternative Content for Unavailable URLs

Using the FALLBACK: section header lets you specify what content should be shown should the URL not be available due to connectivity. Let’s update our manifest to include a fallback section:

CACHE MANIFEST

# These files will be saved locally. This line is a comment.

CACHE:

js/todolist.js

css/style.css

FALLBACK:

status.html offline.html

Every line in the fallback section must adhere to the <requested file name> <alternate file name> pattern. Here, should the user request the status.html page while offline, he or she will instead see the content in offline.html.

Specifying Settings

To date, there’s only one setting that you can set using the SETTINGS: section header, and that’s the cache mode flag. Cache mode has two options: fast and prefer-online. The fast setting always uses local copies, while prefer-online fetches resources using the network if it’s available.

Fast mode is the default. You don’t actually need to set it in the manifest. Doing so won’t cause any parsing errors, but there’s no reason to add it.

To override this behavior, you must use prefer-online, and add this to the end of your manifest file:

SETTINGS:

prefer-online

NETWORK:

*

There’s a caveat here, however. The master file―the file that owns the manifest―will still be retrieved from the local store. Other assets will be fetched from the network.

Adding the Cache Manifest to Your HTML

Now for the simplest part of AppCache: adding it to your HTML document. This requires adding a manifest attribute to your <html> tag:

<html manifest="todolist.appcache">

As your page loads, the browser will download and parse this manifest. If the manifest is valid, the browser will also download your assets.

You’ll need to add the manifest to every HTML page that you wish to make available offline. Adding it to http://example.com/index.html will not make http://example.com/article.html work offline.

Note: Verify Your Manifest Syntax

To avoid problems with your manifest file, verify that its syntax is correct by using a validator, such as Cache Manifest Validator. If you’re comfortable with Python and the command line, try Caveman.

Serving the Cache Manifest

In order for the browser to recognize and treat your cache manifest as a manifest, it must be served with a Content-type: text/cache-manifest header. Best practice is to use an .appcache extension (e.g. manifest.appcache or offline.appcache), though it’s not strictly necessary. Without this header, most browsers will ignore the manifest. Consult the documentation for your server (or ask your web hosting support team) to learn how to set it.

Avoiding Application Cache “Gotchas”

As mentioned, AppCache uses local file copies by default. This produces a more responsive user interface, but it also creates two problems that are very confusing at first glance:

· uncached assets do not load on a cached page, even while online

· updating assets will not update the cache

That’s right: browsers will use locally stored files regardless of whether new file versions are available, even when the user is connected to the network.

Solving Gotcha #1: Loading Uncached Assets from a Cached Document

Application Cache is designed to make web applications into self-contained entities. As a result, it unintelligently assumes all referenced assets have also been cached unless we’ve said otherwise in our cache manifest. Browsers will look in the cache first, and if they’re unable to find the referenced assets, those references break.

To work around this, include a NETWORK: section header in your manifest file, and use a wildcard (*). A wildcard tells the browser to download any associated uncached file from the network if it’s available.

Solving Gotcha #2: Updating the Cache

Unfortunately, if you need to push updates to your users, you can’t just overwrite existing files on the server. Remember that AppCache always prefers local copies.

In order to refresh files within the cache, you’ll need to update the cache manifest file. As a document loads, the browser checks whether the manifest has been updated. If it has, the browser will then re-download all the assets outlined in the manifest.

Perhaps the best way to update the manifest is to set and use version numbers. Changing an asset name or adding a comment also works. When the browser encounters the modified manifest, it will download the specified assets.

Though the cache will update, the new files will only be used when the application is reloaded. You can force the application to refresh itself by using location.reload() inside an updateready event handler.

Cache Gotcha #3: Break One File, Break Them All

Something else to be aware of when using AppCache: any resource failure will cause the entire caching process to fail. “404 Not Found,” “403 Forbidden,” and “500 Internal Server Error” responses can cause resource failures, as well as the server connection being interrupted during the caching process.

When the browser encounters any of these errors, the applicationCache object will fire an error event and stop downloading everything else. We can listen for this event, of course; however, to date no browser includes the affected URL as part of the error message. The best we can do is alert the user that an error has occurred.

Safari, Opera, and Chrome do report the failed URL in the developer console.

Testing for Application Cache Support

To test for support, use the following code:

var hasAppCache = window.applicationCache === undefined;

If you’re going to use several HTML5 features, you may also want to try Modernizr to check for them all.

It’s unnecessary to test for AppCache support using JavaScript, unless your application interacts with the cache programmatically. In browsers without support for Application Cache, the manifest attribute will be ignored.

The Application Cache API

Now that we’ve talked about the syntax of Application Cache, and a few of its quirks, let’s talk about its JavaScript API. AppCache works quite well without it, but you can use this API to create your own user interface for your application’s loading process. Creating such a user interface requires understanding the event sequence, as well as a bit of JavaScript.

The AppCache Event Sequence

As the browser loads and parses the cache manifest, it fires a series of DOM events on the applicationCache object. Since they’re DOM events, we’ll use the addEventListener method to listen for and handle them.

When the browser first encounters the manifest attribute, it dispatches a checking event. “Checking” means that the browser is determining whether the manifest has changed, indicating an application update. We can use this event to trigger a message to the user that our application is updating:

var checkinghandler = function(){

updatemessage('Checking for an update');

}

applicationCache.addEventListener('checking',checkinghandler);

If the cache hasn’t been downloaded before or if the manifest has changed (prompting a new download), the browser fires a downloading event. Again, we can use this event to update the user about our progress:

var downloadinghandler = function(){

updatemessage('Downloading files');

}

applicationCache.addEventListener('downloading',downloadinghandler);

If the cache manifest file hasn’t changed since the last check, the browser will fire a noupdate event. Should we have an update, the browser will fire a series of progress events.

Every event is an object. We can think of this object as a container with information about the operation that triggered it. In the case of progress events, there are two properties to know about: loaded and total. The loaded property tells us where the browser is in the download queue, whiletotal tells us the number of files to be downloaded:

var downloadinghandler = function(event){

var progress = document.querySelector('progress');

progress.max = event.total;

progress.value = event.loaded;

}

applicationCache.addEventListener('downloading',downloadinghandler);

The first time a cache downloads, the browser fires a cached event if everything goes well. Updates to the cache end with an updateready event.

If there are problems, the browser will fire an error event. Typically this happens when a file listed in the manifest returns an error message. But it can also happen if the manifest can’t be found or reached, returns an error (such as “403 Forbidden”), or changes during an update.

Let’s look at an example using a simple to-do list.

Setting Up Our Cache Manifest

First, let’s define our manifest file: todolist.appcache. This file tells the browser which files to cache offline:

CACHE MANIFEST

# revision 1

CACHE:

js/todolist.js

js/appcache.js

css/style.css

imgs/checkOk.svg

Once the browser encounters the manifest attribute, it will begin downloading the files listed, plus the master HTML page.

Setting Up Our HTML

In order to set up our application, we first need to create an HTML document. This is a single-page application without a lot of functionality, so our markup will be minimal:

<!DOCTYPE html>

<html lang="en-us" manifest="todolist.appcache">

<head>

<meta charset="utf-8">

<title>To do list</title>

<link rel="stylesheet" type="text/css" href="css/style.css">

</head>

<body>

<section id="applicationstatus"></section>

<section id="application" class="hidden">

<form id="addnew">

<p>

<label for="newitem">

What do you need to do today?

</label>

<input type="text" id="newitem" required autofocus>

<button type="submit" id="saveitem">Add</button>

</p>

<p>

<button type="button" id="delete">Delete completed</button>

</p>

</form>

<ul id="list"></ul>

</section>

<script src="js/appcache.js"></script>

<script src="js/todolist.js"></script>

</body>

</html>

Setting Up Our CSS and JavaScript

Notice here that we have added a manifest attribute to our html tag. Our stylesheet controls the appearance of our application, including the styles we’ll need for our interactions. The pertinent CSS from our stylesheet is shown:

.hidden {

display:none;

}

Our hidden class controls the display of our elements. We’ll use DOM scripting to add and remove this class name from our elements when the browser has fired a particular event.

First, let’s define our global variables:

var appstatus, app, progress, p;

appstatus = document.getElementById('applicationstatus');

app = document.getElementById('application');

progress = document.createElement('progress');

p = document.createElement('p');

appstatus.appendChild(p);

appstatus.appendChild(progress);

We’ll also create an updatemessage function that we’ll use to update our messages:

/* Create a reusable snippet for updating our messages */

function updatemessage(string){

var message = document.createTextNode(string);

if (p.firstChild) {

p.replaceChild(message, p.firstChild)

} else {

p.appendChild(message);

}

}

Next, let’s set up our checking event handler. This function will be called when the browser fires a checking event:

function checkinghandler(){

updatemessage('Checking for an update');

}

applicationCache.addEventListener('checking', checkinghandler);

Here, we’ve updated our application to tell the user that the browser is checking for an update.

Let’s also add a progress event handler. This handler will update our application with the index of the file currently being downloaded:

function progresshandler(evt){

evt.preventDefault();

progress.max = evt.total;

progress.value = evt.loaded;

updatemessage('Checking for an update');

}

applicationCache.addEventListener('progress', progresshandler);

If this is the first time our application cache is downloading our application, it will fire a cached event. In this function, we’re going to hide div#applicationstatus, and show div#application by adding and removing the hidden class:

function whendonehandler(evt){

evt.preventDefault();

appstatus.classList.add('hidden');

app.classList.remove('hidden');

}

applicationCache.addEventListener('cached', whendonehandler);

This is what makes our application active. We’ll also use this function to handle our noupdate event. If there isn’t an application update, we’ll just hide the status screen and show the application:

applicationCache.addEventListener('noupdate', whendonehandler);

Finally, we need to take an action when the browser has downloaded an update. Once the cache updates, the web application won’t use the latest files until the next page load.

In order to force an update, we reload our page using location.reload(). And we can invoke location.reload() when the browser fires the updateready event:

function updatereadyhandler(){

location.reload();

}

applicationCache.addEventListener('updateready',updatereadyhandler);

That’s it! We have a loading interface for our application. Now let’s take a look at the application itself. In the next chapter, we’ll take a look at web storage: the localStorage and sessionStorage objects.