In this chapter, we will explore what you can do with offline HTML5 applications. HTML5 applications do not necessarily require constant access to the network, and loading cached resources can now be more flexibly controlled by developers.
The first, and most obvious, reason to use the application cache is offline support. In the age of universal connectivity, offline applications are still desirable. What do you do when you do not have a network connection? Before you say the days of intermittent connectivity are over, consider the following:
As more applications move to the Web, it is tempting to assume 24/7 uninterrupted connectivity for all users, but the reality of the Internet is that interruptions happen and, in situations like air travel, can happen predictably for several hours at a time.
Intermittent connectivity has been the Achilles' heel of network computing systems. If your applications depend on communication with remote hosts, and those hosts are unreachable, you're out of luck. However, when you do have an Internet connection, web applications can always be up-to-date, because the code loads from a remote location on each use.
If your applications require only occasional communication, they can still be useful as long as the application resources are stored locally. With the advent of browser-only devices, web applications that continue to function without continuous connectivity will only grow more important. Desktop applications that do not require continuous connectivity have historically held that advantage over web applications.
HTML5 exposes control over application caching to get the best of both worlds: applications built with web technology that run in the browser and update when they are online but can also be used offline. However, this new offline application feature must be used explicitly, because current web servers do not provide any default caching behavior for offline applications.
The HTML5 offline application cache makes it possible to augment an application to run without a network connection. You do not need a connection to the Internet just to draft an e-mail. HTML5 introduces the offline application cache that allows a Web application to run without network connectivity.
An application developer can specify specific additional resources comprising an HTML5 application (HTML, CSS, JavaScript, and images) to make an application available for offline use. There are many use cases for this, for example:
Using offline storage can avoid the normal network requests needed to load an application. If the cache manifest is up to date, the browser knows it does not need to check if the other resources are also up to date, and most of the application can load very quickly out of the local application cache. Additionally, loading resources out of a cache (instead of making multiple HTTP requests to see if resources have been updated) saves bandwidth, which can be especially important for mobile web applications. Currently, slower loading is one way that web applications suffer in comparison with desktop applications. Caching can offset that.
The application cache gives developers explicit control over caching. The cache manifest file allows you to group related resources into a logical application. This is a powerful concept that can give web applications some of the characteristics of desktop applications. You can use this additional power in new, creative ways.
Resources identified in the cache manifest file create what is known as an application cache, which is the place where browsers store the resources persistently, typically on disk. Some browsers give users a way to view the data in the application cache. For example, the Offline cache device section in the internal about:cache
page in Firefox shows you details about the application cache and a way to view individual files in the cache, as shown in Figure 12-1.
Similarly, the internal page chrome://appcache-internals/
provides details about the contents of the different application caches stored on your system. It also provides a way to view the contents and remove these caches entirely as shown in Figure 12-2.
For a complete overview of the current browser support, including mobile support, refer to http://caniuse.com
and search for Offline Web Applications or Application Cache. If you have to support older browsers, it's always a good idea to first see whether Application Cache is supported before you use the API. The section “Checking for Browser Support” later in this chapter will show you how you can programmatically check for browser support.
In this section, we will explore the specifics of how you can use the Offline Web Applications API.
Before you try to use the Offline Web Applications API, it is a good idea to check for browser support. Listing 12-1 shows how you can do that.
if(window.applicationCache) {
// this browser supports offline applications
}
Let's say that you want to create a one-page application that consists of an HTML document, a style sheet, and a JavaScript file. To add offline support to your HTML5 application, you include a manifest
attribute on the html
element as shown in the Listing 12-2.
<!DOCTYPE html>
<html manifest="application.appcache">
.
.
.
</html>
Alongside the HTML document, provide a manifest file with the *.appcache
extension) specifying which resources to cache. Listing 12-3 shows the contents of an example cache manifest file.
CACHE MANIFEST
example.html
example.js
example.css
example.gif
To make applications aware of intermittent connectivity, there are additional events exposed by HTML5 browsers. Your applications may have different modes for online and offline behavior. Some additions to the window.navigator
object make that easier. First, navigator.onLine
is a Boolean property that indicates whether the browser believes it is online. Of course, a true
value of onLine
is not a definite assurance that the servers that your web application must communicate with are reachable from the user's machine. On the other hand, a false
value means the browser will not even attempt to connect out over the network. Listing 12-4 shows how you can check to see if your page is online or offline.
// When the page loads, set the status to online or offline
function loadDemo() {
if (navigator.onLine) {
log("Online");
} else {
log("Offline");
}
}
// Now add event listeners to notify a change in online status
window.addEventListener("online", function(e) {
log("Online");
}, true);
window.addEventListener("offline", function(e) {
log("Offline");
}, true);
Offline applications consist of a manifest listing one or more resources that browser will cache for offline use. Manifest files have the MIME type text/cache-manifest
. The SimpleHTTPServer
module in the Python standard library will serve files with the .manifest
extension with the header Content-type: text/cache-manifest
. To configure settings, open the file PYTHON_HOME/Lib/mimetypes.py
, and add the following line:
'.appcache' : 'text/cache-manifest manifest',
Other web servers may require additional configuration. For example, for Apache HTTP Server, you can update the mime.types
file in the conf folder by adding the following line:
text/cache-manifest appcache
If you are using Microsoft IIS, in your website's home, double-click the MIME Types icon, then add the .appcache
extension with MIME type text/cache-manifest
in the Add MIME Type dialog.
The manifest syntax is simple line separated text that starts with CACHE MANIFEST
(as the first line). Lines can end in CR
, LF
, or CRLF
—the format is flexible—but the text must be UTF-8 encoded, which is the typical output for most text editors. Comments begin with the hash symbol and must be on their own lines; you cannot append a comment to other non-comment lines in the file.
CACHE MANIFEST
# files to cache
about.html
html5.css
index.html
happy-trails-rc.gif
lake-tahoe.JPG
#do not cache signup page
NETWORK
signup.html
FALLBACK
signup.html offline.html
/app/ajax/ default.html
Let's look at the different sections.
If no CACHE:
heading is specified, the files that are listed will be treated as files to be cached (caching is the default behavior). The following simple manifest specifies that three files (index.html
, application.js
, and style.css
) must be cached:
Similarly, the following section would do the same (you can use the same CACHE
, NETWORK
, and FALLBACK
headers multiple times in a manifest file if you want to):
CACHE MANIFEST
# Cache section
CACHE:
Index.html
application.js
style.css
By listing a file in the CACHE
section, you instruct the browser to serve the file from the application cache, even if the application is online. It is unnecessary to specify the application's main HTML resource. The HTML document that initially pointed to the manifest file is implicitly included (this is called a Master entry). However, if you want to cache multiple HTML documents or if you would like multiple HTML documents to act as possible entry points for the cacheable application, they should all be explicitly listed in the cache manifest file.
FALLBACK
entries allow you to give alternate paths to replace resources that cannot be fetched. The manifest in Listing 12-5 would cause requests to /app/ajax/
or subpaths beginning with /app/ajax/
to fall back to default.html
when /app/ajax/*
is unreachable.
NETWORK
specifies resources that are always fetched using the network. The difference with simply omitting these files from the manifest is that master entries are cached without being explicitly listed in the manifest file. To ensure that the application requests the file from the server even if the cached resource is cached in the application cache, you can place that file in the NETWORK:
section.
The ApplicationCache API is an interface for working with the application cache. A new window.applicationCache
object fires several events related to the state of the cache. The object has a numerical property, window.applicationCache.status
, which indicates the state of the cache. The six states a cache can have are shown in Table 12-1.
Most pages on the Web today do not specify cache manifests and are uncached. Idle is the typical state for an application with a cache manifest. An application in the idle state has all its resources stored by the browser with no updates in progress. A cache enters the obsolete state if there was at one point a valid cache but the manifest is now missing. There are events (and callback attributes) in the API that correspond to some of these states. For instance, when the cache enters the idle state after an update, the cached event fires. At that time, an application might notify the user that they can disconnect from the network and still expect the application to be available in offline mode. Table 12-2 shows some common events and their associated caches states.
Additionally, there are events indicating update progress, when no update is available, or when an error has occurred:
onerror
onnoupdate
onprogress
window.applicationCache
has an update()
method. Calling update()
requests that the browser update the cache. This includes checking for a new version of the manifest file and downloading new resources if necessary. If there is no cache or if the cache is obsolete, an error will be thrown.
Although creating the manifest file and using it in an application is relatively simple, what happens when you update pages on the server is not as intuitive as you might think. The main thing to keep in mind is that once the browser has successfully cached the application's resources in the application cache, it will always serve those pages from the cache first. After that, the browser will do only one more thing: check if the manifest file has been changed on the server.
To better understand how the process works, let's step through an example scenario, using the manifest file shown in Listing 12-5.
index.html
page for the very first time (while online), say on http://www.example.com
, the browser loads the page and its subresources (CSS, JavaScript, and image files).example.com
site (browsers allow about 5 MB of storage space).Using Application Cache to Boost Performance
In this example application, we will track a runner's location while out on the trail (with intermittent or no connectivity). For example, Peter goes running, and he will have his new Geolocation–enabled phone and HTML5 web browser with him, but there is not always a great signal out in the woods around his house. He wants to use this application to track and record his location even when he cannot use the Internet.
When offline, the Geolocation API should continue to work on devices with hardware geolocation (such as GPS) but obviously not on devices that use IP geolocation. IP geolocation requires network connectivity to map the client's IP address to coordinates. In addition, offline applications can always access persistent storage on the local machine through APIs such as local storage or Indexed Database.
The example files for this application are located on the book's page at www.apress.com
and at the book‘s website in the offline
code folder, and you can start the demo by navigating to the code/offline folder and issuing the command:
Python –m SimpleHTTPServer 9999.
Prior to starting the web server, make sure you have configured Python to serve the manifest files (files with the *.appcache extension) with the correct mime type as described earlier. This is the most common cause of failure for offline web applications. If it does not work as expected, check the console in Chrome Developer tools for possible descriptive error messages.
This starts Python's HTTP server module on port 9999 (you can start it on any port, but you may need admin privileges to bind to ports lower than 1024. After starting the HTTP server, you can navigate to http://localhost:9999/tracker.html
to see the application in action.
Figure 12-3 shows what happens in Firefox when you access the site for the first time: you are prompted to opt in to storing data on your computer (note, however, that not all browsers will prompt you before storing data).
After allowing the application to store data, the application cache process starts and the browser starts downloading the files referenced in the application cache manifest file (this happens after the page is loaded, and, therefore, it has minimal impact on the responsiveness of the page. Figure 12-4 shows how Chrome Developer Tools provide a detailed overview of what is cached for the localhost
origin in the Resources pane. It also provides information in the console about the application cache events that fire while the page and the manifest were processed.
To run this application, you will need a web server serving these static resources. Remember that the manifest file must be served with the content type text/cache-manifest
. If your browser supports the application cache, but the file is served with the incorrect content type, you will receive a cache error. An easy way to test this is to view the events that fire in the Chrome Developer Tools console as shown in Figure 12-4; it will tell you if the appcache file is served with the wrong mime type.
To run this application with complete functionality, you will need a server that can receive geolocation data. The server-side complement to this example would presumably store, analyze, and make available this data. It may or may not be served from the same origin as the static application. Figure 12-5 shows the example application running in offline mode in Firefox. You can use File Work Offline to turn this mode on in Firefox and Opera. Other browsers do not have this convenience function, but you can disconnect from the network. Note, however, that disconnecting from the network does not interrupt the connection to a Python server running on localhost.
First, in a text editor, create the tracker.appcache
file as follows. This manifest file will list the files that are part of this application:
CACHE MANIFEST
# JavaScript
./offline.js
#./tracker.js
./log.js
# stylesheets
./html5.css
# images
This is the basic UI structure of the example. Both tracker.html
and html5.css
will be cached, so the application will be served from the application cache.
<!DOCTYPE html>
<html lang="en" manifest="tracker.appcache">
<head>
<title>HTML5 Offline Application</title>
<script src="log.js"></script>
<script src="offline.js"></script>
<script src="tracker.js"></script>
<link rel="stylesheet" href="html5.css">
</head>
<body>
<header>
<h1>Offline Example</h1>
</header>
<section>
<article>
<button id="installButton">Check for Updates</button>
<h3>Log</h3>
<div id="info">
</div>
</article>
</section>
</body>
</html>
There are a couple of things to note in this HTML that pertain to this application's offline capabilities. The first is the manifest
attribute on the HTML element. Most of the HTML examples in this book omit the <html>
element because it is optional in HTML5. However, the ability to cache offline depends on specifying the manifest file there.
The second thing to note is the button. That will give the user control over configuring this application for offline use.
For this example, the JavaScript is contained in multiple .js
files included with <script>
tags. These scripts are cached along with the HTML and CSS.
<offline.js>
/*
* log each of the events fired by window.applicationCache
*/
window.applicationCache.onchecking = function(e) {
log("Checking for application update");
}
window.applicationCache.onnoupdate = function(e) {
log("No application update found");
}
window.applicationCache.onupdateready = function(e) {
log("Application update ready");
}
window.applicationCache.onobsolete = function(e) {
log("Application obsolete");
}
window.applicationCache.ondownloading = function(e) {
log("Downloading application update");
}
window.applicationCache.oncached = function(e) {
log("Application cached");
}
window.applicationCache.onerror = function(e) {
log("Application cache error");
}
window.addEventListener("online", function(e) {
log("Online");
}, true);
window.addEventListener("offline", function(e) {
log("Offline");
}, true);
/*
* Convert applicationCache status codes into messages
*/
showCacheStatus = function(n) {
statusMessages = ["Uncached","Idle","Checking","Downloading","Update Ready","Obsolete"];
return statusMessages[n];
}
install = function() {
log("Checking for updates");
try {
window.applicationCache.update();
} catch (e) {
applicationCache.onerror();
}
}
onload = function(e) {
// Check for required browser features
if (!window.applicationCache) {
log("HTML5 Offline Applications are not supported in your browser.");
return;
}
if (!navigator.geolocation) {
log("HTML5 Geolocation is not supported in your browser.");
return;
}
if (!window.localStorage) {
log("HTML5 Local Storage not supported in your browser.");
return;
}
log("Initial cache status: " + showCacheStatus(window.applicationCache.status));
document.getElementById("installButton").onclick = checkFor;
}
<log.js>
log = function() {
var p = document.createElement("p");
var message = Array.prototype.join.call(arguments, " ");
p.innerHTML = message;
document.getElementById("info").appendChild(p);
}
In addition to the offline application cache, this example uses geolocation and local storage. We ensure that the browser supports all of these features when the page loads.
onload = function(e) {
// Check for required browser features
if (!window.applicationCache) {
log("HTML5 Offline Applications are not supported in your browser.");
return;
}
if (!navigator.geolocation) {
log("HTML5 Geolocation is not supported in your browser.");
return;
}
if (!window.localStorage) {
log("HTML5 Local Storage is not supported in your browser.");
return;
}
if (!window.WebSocket) {
log("HTML5 WebSocket is not supported in your browser.");
return;
}
log("Initial cache status: " + showCacheStatus(window.applicationCache.status));
document.getElementById("installButton").onclick = install;
}
Next, add an update handler that updates the application cache as follows:
install = function() {
log("Checking for updates");
try {
window.applicationCache.update();
} catch (e) {
applicationCache.onerror();
}
}
Clicking this button will explicitly start the cache check that will cause all cache resources to be downloaded if necessary. When available updates have completely downloaded, a message is logged in the UI. At this point, the user knows that the application has successfully installed and can be run in offline mode.
This code is based on the geolocation code from Chapter 4. It is contained in the tracker.js
JavaScript file.
/*
* Track and report the current location
*/
var handlePositionUpdate = function(e) {
var latitude = e.coords.latitude;
var longitude = e.coords.longitude;
log("Position update:", latitude, longitude);
if(navigator.onLine) {
uploadLocations(latitude, longitude);
}
storeLocation(latitude, longitude);
}
var handlePositionError = function(e) {
log("Position error");
}
var uploadLocations = function(latitude, longitude) {
var request = new XMLHttpRequest();
request.open("POST", "http://geodata.example.net:8000/geoupload", true);
request.send(localStorage.locations);
}
var geolocationConfig = {"maximumAge":20000};
navigator.geolocation.watchPosition(handlePositionUpdate,
handlePositionError,
geolocationConfig);
Next, add the code that writes updates to localStorage
when the application is in offline mode.
var storeLocation = function(latitude, longitude) {
// load stored location list
var locations = JSON.parse(localStorage.locations || "[]");
// add location
locations.push({"latitude" : latitude, "longitude" : longitude});
// save new location list
localStorage.locations = JSON.stringify(locations);
}
This application stores coordinates using HTML5 local storage as described in Chapter 9. Local storage is a natural fit for offline-capable applications, because it provides a way to persist data locally in the browser. The data will be available in future sessions. When network connectivity is restored, the application can synchronize with a remote server.
Using storage here has the added benefit of allowing recovery from failed upload requests. If the application experiences a network error for any reason, or if the application is closed (by user action, browser or operating system crash, or page navigation) the data is stored for future transmission.
Every time the location update handler runs, it checks the connectivity status. If the application is online, it will store and upload the coordinates. If the application is offline, it will merely store the coordinates. When the application comes back online, it can update the UI to show the online status and upload any data is stored while online.
window.addEventListener("online", function(e) {
log("Online");
}, true);
window.addEventListener("offline", function(e) {
log("Offline");
}, true);
The connectivity status may change while the application is not actively running. For instance, the user may have closed the browser, refreshed, or navigated to a different site. To handle these cases, our offline application checks to see if it has come back online on each page load. If it has, it will attempt to synchronize with the remote server.
// Synchronize with the server if the browser is now online
if(navigator.onLine) {
uploadLocations();
}
In this chapter, you have seen how HTML5 Offline Web Applications can be used to create compelling applications that can be used even when there is no Internet connection. You can ensure that all your files are cached by specifying the files that are part of the web application in the cache manifest file and then referencing the files from the main HTML page of the application. Then, by adding event listeners for online and offline status changes, you can make your site behave differently based on whether an Internet connection is available or not.
In the final chapter, we will discuss the future of HTML5 programming.