Chapter 6. Network and location APIs – Hello! HTML5 & CSS3: A user-friendly reference guide

Chapter 6. Network and location APIs

 

This chapter covers

  • Finding the user’s location and proximity to places and people of interest
  • Communicating directly with content from other servers
  • Having the server push information to the user rather than rely on pull
  • Building websites that work when there’s no network connectivity

 

The previous chapter discussed HTML5 APIs that worked directly with the content in the browser, but one of the key features of the web is that it’s not about standalone computers: it’s about a connected network. HTML5 has a number of APIs related to connectivity and communication, and you’ll learn about them in this chapter.

Finding yourself with the Geolocation API

Location-aware services and applications are a hot topic at the moment. Most of us are now familiar with navigation devices in our cars that constantly update position information using the Global Positioning System (GPS) network. These days, many mobile phones and other portable devices come with built-in GPS technology, as well as other positioning services, and the HTML5 Geolocation API exposes these to your web pages.

Google already uses the Geolocation API if you access its site from your mobile phone. The screen-shot at left shows that Google is aware of my current location. One of my options upon searching is to choose Local, which provides search results that are tailored to my current location.

Browser support quick check: geolocation   Standard
5.0
3.5
9.0
10.6
5.0

Finding your location

The first thing the user will see when they run this code is the browser asking permission to share their location. Here are examples in Firefox, Chrome, and the Android browser.

After the user accepts, the browser looks up the location. When it finds the location, the function is called and the coordinates are displayed.

Finding your location more accurately

Notice that the two screenshots at the end of the previous section are reporting different coordinates—they’re about two miles apart. At the time when these were taken, my phone and my laptop were lying side by side on the same table—considerably closer than two miles! This discrepancy is because the browser on my laptop and the browser on my phone use different location service providers. There are four common ways of identifying location.

Here’s a new version of the previous example, this time displaying the accuracy.

Finding your location continuously

What if you want to continuously track the user’s position? You could just call getCurrentPosition() repeatedly, but that’s a waste if the user is stationary, and it could drain battery life on hand-held devices. A better option is to have the browser tell you when there’s an updated location. For this, the Geolocation API provides the watchPosition() method.

This screenshot shows my progress through North London over a couple of hours one afternoon. All I had to do to get this information was open the page in the phone’s browser and then keep the phone in my pocket. When new geolocation information was available, the browser activated the callback function.

Practical uses for geolocation

Now that you’ve seen the basics of acquiring position information, let’s consider how you might use the Geolocation API in practice. We’ll look at two simple examples: calculating how far the user is from a given point, and showing the user on a map.

The first example calculates how far the user is from the birthplace of Tim Berners-Lee. Although this example uses a single fixed point for simplicity, the techniques involved will work just as well for more advanced scenarios—working out how far apart two users of your website are, for example.

Rather than learn all that math for yourself, you can use an excellent set of JavaScript utilities from Chris Veness, available at www.movabletype.co.uk/scripts/latlong.html. This library allows you to create LatLon objects that have several useful methods available. Starting with a blank HTML5 document, you can create the following page with five simple steps.

Start with an empty HTML5 page with a link to the LatLon.js library. All the JavaScript code goes in the empty init function: <!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Geolocation 4</title>
<script src="LatLon.js"></script>
<script> function init() { } </script>
</head>
<body onload="init();">
</body>
</html>
For convenience, create a LatLon object for Tim Berners-Lee’s birthplace, East Sheen in London: var eastSheen = new LatLon(Geo.parseDMS('51?27\'49"N'),
Geo.parseDMS('0?15\'49"W'));
As usual, add a template in your HTML to fit the data into: <h1>
You are <span id="accuracy"></span>
<span id="distance"></span>
kilometres from the birthplace of Tim Berners-Lee
</h1>
Add a function to update the template: function writeLoc(message, accuracy) {
document.getElementById('distance').innerHTML = message;
if (accuracy > 100) {
document.getElementById('accuracy').innerHTML =
'approximately';
}
}
Take the usual geolocation boilerplate code, and adapt it so that it creates a LatLon object for the user’s current location. You can then use the distanceTo method of LatLon to get the distance between the two points: if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function
(position)
{
var you = new LatLon(position.coords.latitude,
position.coords.longitude);
writeLoc(you.distanceTo(eastSheen),
position.coords.accuracy);
});
}

Although it’s neat that you can perform calculations on the coordinates you get from the Geolocation API, most users probably aren’t bothered by exactly how far they are, as the crow flies, from Tim Berners-Lee’s birthplace, or any other famous landmark. It’s also likely that, outside of geocachers, most people aren’t too interested in their exact latitude and longitude. They’re far more likely to want to know where they are in some sort of sensible context—in other words, on a map.

The Google Maps API requires some parameters as part of a URL. The URL can be built in the writeLoc() function and then set to be the source of an image element. For convenience, create an empty image element in your page where the map is to appear:

<img id="location">

In the callback function, set the URL of the image to the appropriate Google Maps API call:

function init() {     if (navigator.geolocation) {
  navigator.geolocation.getCurrentPosition(function (position){
  writeLoc(position.coords);    });}
function writeLoc(coords) {
    var l = 'http://maps.google.com/maps/api/staticmap?center=' +
    coords.latitude + ',' + coords.longitude +
    '&zoom=12&size=440x440&maptype=roadmap' +
    '&markers=color:red|color:red|label:a|' +
    coords.latitude + ',' + coords.longitude +
    '&sensor=false';
    document.getElementById('location').src = l;
    document.getElementById('debug').innerHTML = l;
}

When the image URL is set, the browser will load the appropriate map from the Google Maps API.

Communication in HTML5

The communication model in HTML4 is pretty much the same as it was in the first version of HTML. The user requests information from a server, and then the server delivers it. Although innovations like the XMLHTTPRequest object allow us to do some cunning things, the underlying model is the same. In addition, content loaded from different servers is usually shielded from other servers—a policy known as same origin restriction.

Enabling more secure integration with cross-document messaging

In many situations, people like to use widgets from other websites inside their own pages. Common scenarios where this happens are Facebook’s Like buttons, external commenting systems such as Disqus, and Google Ads. There are two basic approaches for this:

  • The <iframe>—An embedded window inside your page into which another web page is loaded. The page inside the <iframe> is completely separate from the page containing the <iframe>, and standard browser security prevents them from communicating with each other.
  • JavaScript include—An inline <script> element in the host page, which is allowed to create elements and fetch content from the server from which it came. The script is completely integrated in the host page and has access to all the information the host page does.
Browser support quick check: cross-document messaging   Standard
 
3.0
8.0
9.5
4.0

The problem is that there are two extremes. With the <iframe>, you can guarantee that the widget doesn’t have access to any private data about your users that you happen to be manipulating with JavaScript, because it doesn’t have access to anything in the host page, including any information that might be useful for the script. With the JavaScript include, the opposite is true: the script has access to everything in the page, including any cookies that may be set and any forms the user is filling in. What’s needed is a solution that maintains the privacy and security allowed by <iframe>s but allows a controlled flow of infor-mation between the two domains. This is what cross-document messaging provides.

 

Faking multiple domains

Experimenting with multiple domains requires that you have multiple domains available. If you don’t happen to be one of those people who collect domain names, you can fake it on your local machine by editing your hosts file. On Windows, this file is usually located at C:\Windows\System32\Drivers \etc\hosts (note that the filename doesn’t have an extension) and on Linux and Unix systems at /etc/hosts. The file format is an IP address followed by a number of aliases:

127.0.0.1 myfirstfakedomain.com
127.0.0.1 myotherfakedomain.com

If these two lines are added to your hosts file, then browsing to either http://myfirstfakedomain.com or http://myotherfakedomain.com will direct a request to a web server running on your local machine.

 

To experiment with cross-domain messaging, you’ll need two files. The first, the parent page, contains an <iframe> element that will load the second child page:

<iframe width="600px"
 height="200px" src="child-1.html">
</iframe>
<textarea id="message">
  This is a message in
  the parent frame
</textarea>

This page also contains a <button> that will initiate communication with the child:

<button onclick="update_child()">
  Update child
</button>

The update_child() function attempts to directly edit the contents of the child page:

function update_child() {
  var el = document
    .getElementsByTagName(
      'iframe'
    )[0];
  var tb = el.contentDocument
    .getElementById('message');
  tb.value = 'Updated from parent';
}

The child page is similar:

<textarea id="message">
  This is a message in the child frame
</textarea>

But this time, the <button> attempts to communicate with the parent:

<button onclick="update_parent()">Update parent</button>

When both pages are on the same domain, it’s possible to access the elements of the child page directly and do the same in reverse (access the parent from the child):

function update_parent() {
  var tb = parent.document
    .getElementById('message');
  tb.value = 'Updated from child';
}

But look what happens if the pages are served from different domains:

<iframe width="600px"
height="200px"
  src="http://www.boogdesign.com/
   examples/messaging/child-
2.html">
</iframe>

The pages are otherwise identical, but now when you click Update Child, the browser reports an error:

Exception: Permission denied for <http://localhost:8000> to get
property HTMLDocument.getElementById from <http://www.boogdesign.com>.

The cross-document messaging API allows you to work around this security restriction, but it’s slightly more complex than accessing the documents directly. The first step is to add a listener to the page for the message event that will call a function when a message is received:

window.addEventListener('message', receiver, false);
function receiver(e) {
  document.getElementById('message').value = e.data;
}

This event listener can be added in both the parent and child pages. The update_child() function then needs to be changed to call the post-Message() method:

function update_child() {
  var el =
    document.getElementsByTagName(
      'iframe')[0];
  el.contentWindow
    .postMessage(
      'Updated from parent', '*'
    );
}

The same postMessage() method can be called from the child to the parent:

function update_parent() {
  parent
    .postMessage(
      'Updated from child',
      '*'
    );
}

In the following code, the receiver() function has been modified to check the origin of the onmessage event:

function receiver(e) {
    if (e.origin == 'http://www.boogdesign.com') {
        document.getElementById('message').value = e.data;
    } else {
      alert('Unauthorized');
    }
}

Similarly, the update functions should be modified to send the origin:

function update_parent() {
    parent.postMessage('Updated from child', 'http://
  www.boogdesign.com');
}

Note that the origin argument here should be the parent’s origin, not the child’s.

Communicating between documents across domains is just one of the new communication features in HTML5. It also offers new options for communicating with the server. The next section will look at the most exciting of these technologies: WebSockets.

Real-time communication with the WebSocket API

WebSockets are a lightweight protocol, new to HTML5, allowing a server to communicate directly with a web browser without waiting for a request. You may be thinking that the web has always had a way for the browser and server to communicate, and that it’s a fairly fundamental property, but it’s always had a request-response model. This means that to receive a response from the server, the browser must first make a request. Although this is fine for web pages, it has limitations as far as web-based applications are concerned. If an application relies on frequent updates from the server—for instance, if it’s a multiplayer game or a chat application—the browser could end up not requesting an update as it becomes available, or wasting bandwidth requesting updates when none are available.

The WebSocket API solves this problem by allowing the server to initiate a response to the browser without the browser asking for it. Updates can now be delivered as they’re ready and only when they’re ready, as the following diagram shows.

In a traditional AJAX model, updates from the server are sent when the client asks for them. If the client doesn’t request an update, it sits on the server. With WebSockets, the server is in control of sending updates. They can be sent to the client as soon as they’re available.
Browser support quick check: WebSockets   Standard
4.0
4.0[*]
10.0
11.0
5.0

* The WebSocket API is disabled by default in Firefox 4 and 5 and Opera due to a security concern. It must be enabled manually.

 

A server for WebSockets: Node.js

Experimenting with WebSockets requires a server. The following example uses Node.js, a new server written using JavaScript. Download Node.js from http://nodejs.org, and follow the instructions for installing it on your operating system. The server-side files used for the example are ch06/messaging/server-1.js and ch06/messaging/server-2.js.

 

The first step on the client side is to create a new WebSocket instance:

var socket =
 new WebSocket(
       "ws://localhost:8080/"
     );

Then, assign event handlers to the socket object. In this first example, the server will wait 10 seconds and then close the connection, so you just need to watch the onopen and onclose events:

socket.onopen = function () {
  log("Socket has been opened!");
}
socket.onclose = function () {
  log("Socket has been closed");
}

Now let’s look at what happens when the server sends a message. In the browser, you have to handle the onmessage event:

socket.onmessage = function(msg) {
  log(msg.data);
}

The data attribute of the event handler argument contains the message from the server; in this case, the current data and time every 10 seconds.

You can also control the connection from the browser with the close method on the socket object:

socket.close();

Finally, you may want to send a message to the server. This is done with the send() method on the socket object, passing the message as the parameter:

function send() {
  var msg = document
   .getElementById('message').value;
  socket.send(msg);
}

The server is set up to echo back any message it receives.

Offline web applications

Although in the modern world we sometimes take connectivity for granted, there are plenty of situations where you might want access to web applications when you’re offline—particularly when, as the authors of the HTML5 specification hope, more and more of the applications you use every day are web applications. For a web application to be available offline, there are two basic requirements:

  • A mechanism for storing the pages and other files (images, scripts, and stylesheets) required by the application
  • A place to store the user’s data as it’s accessed and updated while the user is offline

In this section, you’ll learn about HTML5 technologies for the first of these requirements: the application cache—a way of placing a copy of your web app on your user’s machine. In the following section, you’ll learn about the offline storage of data.

Before going into the application cache, you’ll set up a development environment and then get a reminder of how web applications work when they’re online.

Setting up a development environment

SimpleHTTPServer records a line like this every time a request is made (it’s been split into three sections so it fits better on a single page):

Browser support quick check: offline apps   App cache Local storage
4.0 4.0
3,5 3,5
- 8.0
10.6 10.5
5.0 5.0

The only parts we’re interested in are the request and the status code, so I’ll elide the extra details in the examples that follow.

In order to understand what’s going on with offline web applications, you first need a good understanding of what normally happens as the browser and the web server communicate in order to display a web page. Let’s first examine the interaction between the web browser and the server for a simple two-page website, without enabling any of HTML5’s offline features. Here are the two pages you’ll use.

ch06/offline-example/offline-1-a.html

ch06/offline-example/offline-1-b.html

<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8">
  <title>
    Offline Web Applications 1
     - Page A
  </title>
  <link rel="stylesheet"
     href="offline-1.css">
</head>
<body>
  <h1>Page A</h1>
  <p>
    <a href="offline-1-b.html">
      Go to page B
    </a>
  </p>
</body>
</html>
<!DOCTYPE HTML>
<html>
<head>
  <meta charset="utf-8">
  <title>
    Offline Web Applications 1
    Page B
     </title>
  <link rel="stylesheet"
     href="offline-1.css">
</head>
<body>
  <h1>Page B</h1>
  <p>
    <a href="offline-1-a.html">
      Go to page A
    </a>
    </p>
  <img src="example.png"
     alt="An example image">
</body>
</html>

First, start the local web server:

# python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...

If you load the page in the browser, the server records the following requests:

"GET /offline-1-a.html HTTP/1.1" 200 -
"GET /offline-1.css HTTP/1.1" 200 -

The page itself is requested, as is anything required to display that particular page (in this case the stylesheet), but none of the links within the page are loaded.

When you click the link, the server records the following two requests:

"GET /offline-1-b.html HTTP/1.1" 200 -
"GET /example.png HTTP/1.1" 200 -

Again, the page itself is requested as well as the image embedded in the second page.

Most browsers, in their default configurations, cache recently accessed pages. If you use the Back and Forward buttons in your browser at this point, no new requests will be made to the server.

Stop the server (press Ctrl-C/Cmd-C in the terminal):

^CTraceback (most recent call last):
 ...
KeyboardInterrupt

In the browser, try going back and forward again. You’ll see that the pages are still there in the browser’s temporary cache. But if you try to reload the page, the browser tries to contact the server, finds it’s unavailable, and shows an error.

The application cache

The application cache is a service provided by HTML5 web browsers in which you can store your web apps for offline use. To change the previous example into an offline application, it requires one small change and an additional file. At the top of the home page, you need to add a reference to a manifest file, like this from ch06/offline-example/example-2-a.html:

<!DOCTYPE HTML>
<html manifest="offline-2.appcache">

Then the manifest file itself needs to be created. This is a text file that begins with the words CACHE MANIFEST and then lists all the files in your web application, one per line:

CACHE MANIFEST
offline-2-a.html
offline-2-b.html
offline-2.css
example-2.png

Note that it’s not necessary to list the file where the reference to the manifest appears, because the file that references the manifest is automatically added to the cache; but doing so may save you headaches later if you end up with a large application and you change the file from which the manifest is referenced. Also, note that paths are relative to the manifest file, not the file from which the manifest is referenced.

 

Manifests and MIME types

The file extension used here for the manifest file is .appcache. This is recommended by the HTML5 spec. Initially, manifest files were given a .manifest extension, but it was found that Microsoft was already using this extension for another purpose. To avoid collisions, the recommended file extension was changed. But the file extension isn’t as important to the browser as the MIME type the server sends along with the file in the headers. The correct MIME type for manifest files is text/cache-manifest. MIME types were discussed in chapter 4, when we looked at video.

 

Start SimpleHTTPServer again, and access the new page. As before, the browser requests two files from the server:

"GET /offline-2-a.html HTTP/1.1" 200
"GET /offline-2.css HTTP/1.1" 200

But this time there’s a difference in the browser—it’s asking for permission to store files for offline use.

If you click Allow, the browser immediately makes several more requests:

"GET /offline-2.appcache HTTP/1.1" 200
"GET /offline-2-b.html HTTP/1.1" 200
"GET /example-2.png HTTP/1.1" 200
"GET /offline-2.appcache HTTP/1.1" 200

The entire website is now available offline. You can test this by again stopping SimpleHTTPServer and then, when no server is running, visiting the second page.

Even though you’ve never visited that page and the server is unavailable, the browser can display the page to you.

Let’s try a little experiment. Start your local web server again, but edit the offline-2-a.html file:

<h1>Edited Page A</h1>

Now visit the page in the browser again. Notice that your edit isn’t visible. This is because you’ve told the browser to store the page locally. Changes you make to the file on the server aren’t seen because the browser doesn’t go back to the server for the file, even if you reload.

If you look in the console, you’ll see that the only request the browser made is for the manifest file:

"GET /offline-2.appcache HTTP/1.1" 200 -

In order to make the browser fetch a new version of any cached file, the manifest needs to be updated. Any change will do. The best approach is to add a comment with a version number:

CACHE MANIFEST
#v1
offline-2-a.html
offline-2-b.html
offline-2.css
example-2.png

When you reload, the edited page appears. If you check the console, you’ll see that all the files in the manifest have been downloaded again.

 

Beware the browser cache

There is a certain amount of caching built into HTTP, and browsers are often configured to minimize network traffic by serving pages out of local cache instead of downloading them again. This isn’t the same as having them in the application cache; there are no guarantees or control for the web author. But the browser cache can interfere with the application cache, because the browser may not look on the network for new versions of files even if it detects that the manifest file has been updated. In this case, the application cache is updated with files from the browser cache. The situation is even worse if the manifest file is loaded into the browser cache—then the user might never see your application updates. You can avoid issues by explicitly setting caching values in the headers of your files in the server configuration. Here’s the required line for Apache:

ExpiresByType text/cache-manifest       "access plus 0 seconds"

 

In case you missed that last part, if you want to update a single file in your application, all the files in your application will be downloaded again.

HTML5 provides several options for storing an application’s dynamic data, as you’ll see in the section “Storing data for offline use.” In the meantime, the next section will cover the other key features of the application cache that you need to know about.

Managing network connectivity in offline apps

In this section, you’ll learn about detecting the status of your offline application: is it currently online or offline? To understand how to do this, you’ll need to explore some additional features of the manifest file; but first you’ll see why the built-in Offline API isn’t appropriate.

HTML5 provides the Offline API to detect whether the browser is offline or online. It consists of the ononline and onoffline events and a Boolean property, navigator.onLine. These would be an excellent way of managing when to attempt to sync data with the server—if they worked.

That’s a little harsh—the API does work, but not in a way that’s useful to web authors. The offline state in HTML5 is tied to the state of the browser, not the state of your network connection or the availability of the server. Here are the basics of the API:

  • navigator.onLine—This property is true if the browser thinks it’s online and false if the browser thinks it’s offline.
  • window.online—This event is fired whenever the browser changes from an offline state to an online state.
  • window.offline—This event is fired whenever the browser changes from an online state to an offline state.

A function can be attached to the events either by declaring an ononline or onoffline attribute on the body element, or by binding an event listener to the window object in the standard way, as in the following example.

window.setInterval(
  function () { log('onLine: ' + navigator.onLine); },
  10000
);
window.addEventListener('online',
  function () { log('online event fired'); } ,
  false
);
window.addEventListener('offline',
  function () { log('offline event fired'); } ,
  false
);

The log function writes a message on the screen so you can see what’s going on. The full listing is in ch06/offlineexample/offline-events.html.

In the screenshot, the local web server was started and the page loaded. After a short time, the local web server was stopped. As you can see, it made absolutely no difference to the output in the browser.

The reason is that these events and properties aren’t designed to track what’s going on with the network connection or the availability of the remote server. Instead, they’re plumbed directly into a menu item in the browser UI: the Work Offline entry, which is usually on the File menu.

If the local server is started up again and the page reloaded, you can see the effect of selecting and then deselecting Work Offline in the menu.

This is perfectly reasonable behavior from the browser if you think about it—there are so many reasons the server may not be contactable, that it can’t be tied to a single property in the browser or the operating system, or linked to a simple event.

Before you get too disappointed, there’s an alternative approach that relies on detecting the property that an offline application really cares about: whether it can connect to the server. To understand this approach, you need to learn about some further features of the manifest file: sections introduced with the keywords FALLBACK or NETWORK. A NETWORK section lists resources that will always be fetched from the network—they won’t be available when offline. The FALLBACK section lists replacements for certain files or directories when the user’s offline. Here’s ch06/offline-example/offline-3.appcache, which has a FALLBACK section:

CACHE MANIFEST
#v1
offline-3-a.html
offline-3-b.html
offline-3.css

FALLBACK:
example-3.png dust-puppy-3.png

Let’s see what difference that makes in what files are requested when the browser makes an initial request for offline-3-a.html:

"GET /offline-3-a.html HTTP/1.1" 200
"GET /offline-3.css HTTP/1.1" 200
"GET /offline-3.appcache HTTP/1.1" 200
"GET /offline-3-b.html HTTP/1.1" 200
"GET /dust-puppy-3.png HTTP/1.1" 200
"GET /offline-3.appcache HTTP/1.1" 200

The file dust-puppy-3.png is fetched from the server even though it isn’t listed in the opening section of the manifest file.

When offline-3-b.html is visited, it looks the same as before. Because the server is still running, the example-3.png image is loaded as normal.

But if the web server is stopped and the page reloaded, then, depending on browser settings, the dust-puppy-3.png fallback image is shown instead.

The significant browser settings are the ones to do with caching outside of the application cache. The browser may still choose to show the image out of the browser cache even when the server’s unavailable—see the sidebar “Beware the browser cache” for a discussion of this issue. If the fallback image isn’t shown when the page is reloaded, try a hard reload: Ctrl+F5 on Windows or Linux.

Listing ch06/offline-example/offline-4.appcache has a few more new features:

CACHE MANIFEST
#v1
offline-4.html

FALLBACK:
example-4.png dust-puppy-4.png

CACHE:
offline-4.css
FALLBACK:
headshots/ dust-puppy-4.gif

NETWORK:
*

In addition to multiple FALLBACK sections and a NETWORK section, this listing has two CACHE sections. There are two because CACHE is the default assumption at the start of the manifest file. It’s possible to switch between sections at any point by adding one of the three keywords on a line by itself. This means the manifest file can be arranged to suit the application rather than forcing everything to fit into three sections.

The other interesting feature of this manifest file is that it demonstrates the pattern-matching and wildcard ability of the FALLBACK and NETWORK sections. The NETWORK section has a star in it, which means “match anything”—anything that isn’t listed is requested from the network. This is the default behavior, so it’s not necessary to include it here, but if you were developing real offline applications you’d include URL patterns for your API in this section.

The second FALLBACK section includes a directory. It’s saying, “For anything in the folder headshots, fall back to dust-puppy-4.gif when offline.”

This screenshot shows ch06/offlineexample/offline-4.html when online. Here’s the markup for this example:

<img src="example-4.png"
  alt="An example image">
<p>Example starring:</p>
<img src="headshots/pitr.gif"
  alt="Pitr">
<img src="headshots/mike.gif"
  alt="Mike">
<img src="headshots/stef.gif"
  alt="Stef">

And here’s what the same page looks like when offline. Each image from the headshots folder has been replaced by the fallback image without having to explicitly list each image.

It’s worth noting that in this particular example, the alt text no longer corresponds to the images displayed, so it might be a good idea to override the alt text with JavaScript if you can detect that the page is offline. As discussed at the beginning of the section, it’s possible to do this now that you know how FALLBACK works.

There are several possible approaches to using a fallback to detect the application’s online status, but they all boil down to the same thing: having a pair of files in the FALLBACK section of the manifest that have an easily detectable difference between the online and fallback versions. The complete listings for the two files this example uses are shown in the following table.

ch06/offline-example/online.txt

ch06/offline-example/offline.txt

ONLINE OFFLINE

It would certainly be possible to add more complexity; but the online.txt file will be fetched from the server frequently, so the shorter the better. In real life, it would be best to stop pandering to human readability and use values of 1 and 0. Now add these two files in the FALLBACK section of the manifest file:

CACHE MANIFEST
#v1
offline-5.html
offline-5.css
offline-checker.js

FALLBACK:
online.txt offline.txt

These files can then be used to determine the online status. In this example it’s linked to a button: each time the button is clicked, the online status is checked and reported.

The full listing is in the files ch06/offlineexample/offline-5.html and ch06/offlineexample/offline-checker.js. The key parts of the code are shown next.

Here’s the function that’s called when the button is clicked. It calls another function in the external JavaScript file, passing two functions as parameters. The first function is executed if the server’s available, the second if the server’s offline:

function display_online_status() {
    check_online(
                 function() { log('online'); },
                 function() { log('offline'); }
                );
    return false;
}

In a real application, the functions passed in would do something useful, such as synchronize the application data with the server or queue it for later delivery. But in this example, all they do is log the state of the connection to the page.

Finally, here’s the function that does all the real work. Most of this is standard AJAX boilerplate; using any one of the popular JavaScript libraries will reduce the function to about four lines of code. The key line is about halfway down, where req.responseText is checked to see if it contains the string 'OFFLINE':

function check_online(online_fn, offline_fn) {
    var currentTime = new Date()
    req = window.XMLHttpRequest ?
            new XMLHttpRequest() :
            new ActiveXObject("MSXML2.XMLHTTP.3.0");
    var freshUrl = 'online.txt?brk=' + currentTime.getTime();
    req.open("GET", freshUrl, true);
    req.onreadystatechange = function() {
        if (req.readyState == 4) {
            if (req.status == 200) {
                if (req.responseText.indexOf('OFFLINE') > -1) {
                    offline_fn();
                } else {
                    online_fn();
                }
            } else {
                offline_fn();
            }
        }
    }
    req.send(null);
}

Storing data for offline use

In this section, you’ll learn about the Web Storage API, a convenient way to store data in the browser. Although web storage is crucial for any sort of offline application, it’s also useful for providing quick access to data in the browser without having to repeatedly request it from the server. Web storage comes in two flavors: local storage, which is persistent across browser sessions, and session storage, which is lost when the user ends their browsing session. The storage APIs are also available to offline apps, making them extremely useful for caching your user’s data for access while they’re offline. The API for each is identical, so this section concentrates on local storage and then provides a quick comparison with session storage before finishing with a look at using web storage in an offline application.

Local storage

For many years, the only option web authors have had for storing data on the client has been cookies. These are small strings stored in the client browser along with an expiry date and a key to reference them by. They’re then passed back by the browser along with any HTTP request made to the server that set them.

The local storage APIs create a client-side key-value store so that data doesn’t have to be repeatedly fetched from the server. Cookies are useful for tracking things like whether a user is logged on, but they’ve been forced into a role where they end up storing a significant amount of data. This is a problem because each request the browser makes contains the full set of cookies it has. Local storage replaces cookies by providing a simple in-browser service for associating keys with values. The data stays in the browser and doesn’t need to be sent back to the server.

This section builds a simple to-do list application using local storage. First you need some markup for a text input and a button:

<input type="text" id="new_item">
<button onclick="add_item()">
  Add
</button>
<ul id="todo_list">
</ul>

To add an item to the to-do list, the user must type a description into the text input and click the Add button. When the user clicks Add, three steps are required: add the item to the list element on the page, add the item to local storage, and finally clear the text input ready for the next item:

function add_item() {
  var new_item =
   document.
     getElementById('new_item');
  add_listitem(new_item.value);
  add_storageitem(new_item.value);
  new_item.value = '';
}

This calls functions to add the item to the list on the page and to add the same item to local storage. The function that adds an element to the page is straightforward, using the same DOM scripting techniques that everyone’s been using for years with HTML4:

function add_item() {
    var new_item = document.getElementById('new_item');
    add_listitem(new_item.value);
    add_storageitem(new_item.value);
    new_item.value = '';
}

The interesting thing is the call to the add_storageitem function:

function add_storageitem(item) {
  var key = new Date();
  window.localStorage.setItem(
    key.getTime(),item
  );
}

The localStorage object is available from the window object. In this case, you call the setItem method that adds the provided key-value pair to the storage. It doesn’t matter what the key is—it just has to be unique. If you call setItem with a key that already exists, it will overwrite the previously stored item, so the current time in milliseconds is used. The value has to be a string, and in this case the value is whatever the user has typed into the text input. The full code for this first example is in ch06/offline-example/local-storage-1.html.

Now that your to-do items are in local storage, they’ll be there the next time you load the page. Restart your browser and load the page again to check. You should see something like the screenshot here.

You’ve not been lied to, your items are in local storage, but that doesn’t mean they’ll appear in your page automatically. You have to write application logic to grab the contents of localStorage and display it, just as you had to write code to add the to-do items to the page in the first place.

You can find the complete listing in ch06/offline-example/local-storage-2.html. Look for the init() function for the previous code.

There are some other functions you’ll need for a complete application. A to-do list isn’t much use if it’s impossible to remove a task after completion. To delete a single item from localStorage, pass its key to the removeItem() method:

window.localStorage.removeItem(key);

This depends on knowing which element on the page is associated with which key in local storage. If a data-* attribute is used to store that information, then removal is straightforward. That attribute can be created in the add_listitem() function if it’s modified to accept both the key and the item as parameters:

function add_listitem(key, item) {
    var li = document.createElement('li');
    li.appendChild(document.createTextNode(item));
    li.setAttribute("data-key", key);
    var but = document.createElement('button');
    but.appendChild(document.createTextNode('Delete'));
    but.onclick = remove_item;
    li.appendChild(but);
    document.getElementById('todo_list').appendChild(li);
}

Another change is required to support this. Previously it didn’t matter what the key was when adding a new item, but now it needs to be added to the list item as entries are created. The key is created in the add_item() method and passed in to both add_listitem() and add_storageitem():

function add_item() {
    var new_item = document.getElementById('new_item');
    var key = new Date();
    add_listitem(key.getTime(), new_item.value);
    add_storageitem(key.getTime(), new_item.value);
    new_item.value = '';
}

Finally, a user may want to delete everything in local storage rather than individual items one at a time. This is easy using the clear() method:

window.localStorage.clear();

You can look at the finished listing in ch06/offline-example/local-storage-3.html.

Session storage

So far, the example has used local storage, but the section introduction also mentioned session storage. Local storage and session storage have exactly the same API: if you go back through the example and replace every instance of localStorage with sessionStorage, it will still work. Here’s the sessionStorage version of the add and remove functions:

function add_storageitem(key, item) {
    window.sessionStorage.setItem(key, item);
}
function remove_storageitem(key) {
    window.sessionStorage.removeItem(key);
}

There’s a full sessionStorage example in the listing ch06/offlineexample/session-storage-1.html.

The only difference between the two types of storage is the length of time the items are stored. Session storage is only guaranteed to last as long as the browser process; if the browser’s closed, then any data stored is lost (unless the session-restore features of the browser are enabled). Local storage lasts until your application clears it, or until the user manually deletes the data, no matter how many times the browser’s closed in the meantime.

One note before we proceed: while the capacity of local and session storage is much larger than that of cookies, it’s still finite, and varies substantially between browsers. Web storage can be a useful way to store large amounts of data on the client side but, like all client-side technologies, you should never rely on it being available all the time.

Putting it all together

Now that the to-do list app is functional, it would be nice to enable it to work offline. Because the entire thing is self contained—no external files of any kind, just the single HTML file—you just need an empty manifest file

CACHE MANIFEST
#v1

and a reference to that manifest in the markup:

<!DOCTYPE HTML>
<html manifest="storage.appcache">

Remember that the file that references the manifest is always cached and doesn’t need to be listed in the manifest file explicitly. If you have a set of single-page applications on the same site, then they can all reference this one manifest file and they’ll be cached the first time the user visits them. You can try it for yourself on your local web server with the example file in ch06/offline-example/local-storage-4.html.

Browser support

Browser support for most of the APIs discussed in this chapter is very good. Only Internet Explorer lets things down by not supporting Web-Sockets or the application cache, but support is being considered for a future release.

 

  12 14 4 6 8 9 10 11.5 12 5 5.1
Geolocation  
Cross-document messaging
WebSockets    
App cache    
Session storage
Local storage  
Key: • Complete or nearly complete support Incomplete or alternative support
Little or no support

Summary

This chapter has covered a selection of the most interesting HTML5 APIs associated with networking and connectivity. You should now be able to build apps that

  • Take advantage of the user’s location, thanks to the Geolocation API
  • Communicate in a controlled way with pages on other domains
  • Write real-time chat and game apps with WebSockets
  • Build apps that work even when there’s no internet connection
  • Store data in the browser with the storage APIs

The best way to learn more is to try coding for yourself. Download the book’s sample code to get started.