Using “Donut Hole” caching in HTML5 offline apps

HTML Offline Applications allow you to create applications that work independent of an internet connection using technologies native to the web browser. Pages included in an offline application (by being listed in the application manifest) are served from the application cache whether or not a connection to the internet is present.

In the event that a user is viewing a page in an offline application with an available internet connection, you may want to display some data from the server without requiring the user to change pages.

Scott Guthrie introduced the concept of “donut hole” caching in ASP.NET where a cached page may include small windows of content that are updated independent of the cached page. This tutorial demonstrates how to implement an offline page that makes an Ajax call when connected to the web in order to display live data to the user. When offline, the page simply renders default data native to the page.

There are a number of different practical applications for implementing an offline application. While most developers first think toward the mobile context when considering the user of an offline application, there are some ways any website may enjoy the benefit of working independent of connectivity status.

A website’s Home and Contact Us pages are excellent candidates for offline availability so users can visit your website and at least get some basic contact information about the organisation even when working disconnected.

Building the Application

The example in this tutorial demonstrates how to cache a “contact us” page which displays a notification of upcoming event information to the user. When working connected to the web, live event listings are displayed to the user. While working offline a telephone number prompting the user to call for event information is rendered. This approach keeps the user informed and connected with or without access to the public web.

Figure 1 depicts how the page is rendered while offline and Figure 2 shows how the page looks when served from the application cache, but the computer is connected to the web.

 

 

Figure 1: Offline application showing native event information while working offline.

Figure 2: Offline application showing event info from the server while working connected to the internet.

The Manifest

The application manifest file acts as the master list of files included in the offline application. The manifest in this example includes the following items:

CACHE MANIFEST

# version 1

 

CACHE:

contact.htm

style.css

jquery-1.6.3.min.js

map.png

 

NETWORK:

events.htm

The file begins with the requisite CACHE MANIFEST header and includes a versioning comment in order to allow changes in listed files to propagate to the client.

Next, the CACHE section includes reference to the contact page intended to be available to users regardless of connectivity status. The contact page references a style sheet, jQuery and a map image depicting the physical office location.

Finally, the NETWORK section is required in this instance to whitelist access to the events.htm page. The reason this page isn’t included in the CACHE section is because in the real world the events page would be built dynamically as a server-generated page. Caching a page like this would defeat the purpose of making live event data available to the user when connected to the web. Instead, the events page is listed in the NETWORK section so that the Application Cache API does not attempt to cancel the request to the page since it’s not listed in the CACHE section. Ultimately the inclusion in the NETWORK section tells the browser to always attempt to fetch the page from the web regardless of the online connection state.

The full code listing for the manifest file is available in Listing 1. The full code listing for the events page is available in Listing 2.

Note: The manifest file in this case is implemented as an ASPX file in order to disable browser caching of the file. You may choose to disable caching on the web server via configuration settings, but this approach is used in this instance to make the sample code more portable for demonstration purposes.

The Contact Us Page

The contact page’s HTML is implemented in the same manner as you might expect whether or not the page is being optimised for offline access. The most important detail to note in the HTML for the page is the contents of the paragraph with the ID of localMessage. This container holds the content which displays to the user while working disconnected and is replaced on-the-fly when a connection to the internet is available.

<body>

<div id="container">

<h1>Awesome Company</h1>

<h2>Contact Us</h2>

 

<p>

Awesome Company<br />

1800 Main Street<br />

Anytown, CA 90210<br />

(800) 555-5555<br />

<a href="mailto:awesome@company.com">awesome@company.com</a>

</p>

 

<img src="map.png" />

 

<div id="events">

<h2>Events</h2>

<p>We are coming to a city near you!</p>

<p id="localMessage">Give us a call at (800) 555-5555

to hear about our upcoming conference dates.</p>

<div id="eventList"></div>

</div>

 

<div id="version">Version 1</div>

</div>

In the script section of the page, all functions are defined and run nested in the jQuery document ready handler.

$(function () {

...

});

The first order of business is to process any updates to the page loaded in application cache by handling the updateready events. When the application manifest is changed all the files listed in the CACHE section of the file are copied together to the client. Once the new version of the files are available, the updateready event fires and the page can load the latest version of the contact page in from the cache by calling applicationCache.swapCache(). Finally, once the latest version is loaded into memory the page is re-loaded in order to display the changes to the user.

window.applicationCache.onupdateready = function (e) {

applicationCache.swapCache();

window.location.reload();

}

Next, the page needs a mechanism to detect whether or not the computer is working in a connected or disconnected state. The isOnLine function simply wraps up a call to the read-only navigator.onLine boolean property. This encapsulation is valuable because should you want to override the value for testing purposes, you can un-comment the return false line and you can test the page’s offline behavior without having to actually disconnect from the web.

function isOnLine() {

//return false;

return navigator.onLine;

}

Note: Using navigator.onLine as the sole mechanism for detecting online status is a bit rudimentary. For a more robust implementation of detecting online status please read Working Off the Grid with HTML5 Offline by Paul Kinlan.

The showEvent function is responsible for implementing the “donut hole caching” functionality for the HTML offline application. The function first detects the connectivity status of the computer and then either fetches and renders live event data or just shows the event information that already exists in the page.

If the isOnLine function returns true, then the local message is hidden from the user and an Ajax call to the events page is initiated. When a response from the asynchronous call is recognised, then the resulting HTML is fed to the eventList container and the table is styled with zebra striping.

If, on the other hand, the computer is working offline, then the local message is displayed to the user.

function showEventInfo() {

if (isOnLine()) {

$("#localMessage").hide();

 

$.get("/events.htm", function (data) {

$("#eventList").html(data);

$("#eventList table tr:odd").addClass("alt");

});

}

else {

$("#localMessage").show();

}

}

Finally, the manifest file is referenced at the top of the HTML page by populating the manifest attribute of the html element pointing to the manifest file.

<html manifest="manifest.aspx">

The full HTML listing for the contact page is available in Listing 3 and the full definition of the page’s style sheet is available in Listing 4.

Conclusion

Even though pages loaded into the application cache are served from the cache regardless of whether or not the computer is connected to the internet, you can implement your pages to take advantage of online resources when available. “Donut hole caching” is possible by making an Ajax call to the server when a connection is available and then rendering the results to the user. If the page is working in a disconnected state, then the page quietly renders data already available on the page – it’s the best of both worlds!

Resources

·         HTML Offline Web Applications Specification

·         Using Application Cache

·         Go Offline with Application Cache

·         Tip/Trick: Implement "Donut Caching" with the ASP.NET 2.0 Output Cache Substitution Feature

·         A Beginner's Guide to Using the Application Cache

·         Working Off the Grid with HTML5 Offline

·         HTML5 Rocks: Offline Tutorials

Listing 1: Manifest File (manifest.aspx)

CACHE MANIFEST

# version 1

 

CACHE:

contact.htm

style.css

jquery-1.6.3.min.js

map.png

 

NETWORK:

events.htm

 

<%@ Page

ContentType="text/cache-manifest"

ResponseEncoding = "utf-8"

Language="C#" AutoEventWireup="true"

CodeBehind="manifest.aspx.cs"

Inherits="ConditionalCaching.manifest" %>

 

<script language="CS" runat="server">

void Page_Load(object sender, System.EventArgs e)

{

Response.Cache.SetCacheability(HttpCacheability.NoCache);

}

</script>

Listing 2: Events Page (events.htm)

<table border="1" cellpadding="2" cellspacing="0">

<tr>

<td>Aug 15</td>

<td><a href="/events/anaheim">Anahiem Convention Center</a></td>

</tr>

<tr>

<td>Sept 5</td>

<td><a href="/events/los-angeles">Los Angeles Convention Center</a></td>

</tr>

<tr>

<td>Oct 3</td>

<td><a href="/events/las-vegas">Las Vegas Convention Center</a></td>

</tr>

<tr>

<td>Nov 14</td>

<td><a href="/events/denver">Colorado Convention Center</a></td>

</tr>

</table>

Listing 3: Contact Page (contact.htm)

<!DOCTYPE html>

<html manifest="manifest.aspx">

<head>

<title></title>

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

<script src="jquery-1.6.3.min.js" type="text/javascript"></script>

<script>

 

$(function () {

 

window.applicationCache.onupdateready = function (e) {

applicationCache.swapCache();

window.location.reload();

}

 

function isOnLine() {

//return false;

return navigator.onLine;

}

 

function showEventInfo() {

if (isOnLine()) {

$("#localMessage").hide();

 

$.get("/events.htm", function (data) {

$("#eventList").html(data);

$("#eventList table tr:odd").addClass("alt");

});

}

else {

$("#localMessage").show();

}

}

 

showEventInfo();

});

 

</script>

</head>

<body>

<div id="container">

<h1>Awesome Company</h1>

<h2>Contact Us</h2>

 

<p>

Awesome Company<br />

1800 Main Street<br />

Anytown, CA 90210<br />

(800) 555-5555<br />

<a href="mailto:awesome@company.com">awesome@company.com</a>

</p>

 

<img src="map.png" />

 

<div id="events">

<h2>Events</h2>

<p>We are coming to a city near you!</p>

<p id="localMessage">Give us a call at (800) 555-5555

to hear about our upcoming conference dates.</p>

<div id="eventList"></div>

</div>

 

<div id="version">Version 1</div>

</div>

</body>

</html>

Listing 4: Style Sheet (style.css)

body

{

font-family:Segoe UI, Arial, Helvetica, Sans-Serif;

background-color:#eee;

}

h1

{

text-transform:uppercase;

letter-spacing:-2px;

width:400px;

margin-top:0px;

}

#container

{

position:relative;

margin-left:auto;

margin-right:auto;

padding:20px;

width:700px;

-webkit-box-shadow: 3px 3px 7px #999;

box-shadow: 6px 6px 24px #999;

-moz-border-radius: 10px;

-webkit-border-radius: 10px;

border-radius: 10px;

margin-top:20px;

background-color:#fff;

}

 

#events

{

position:absolute;

width:210px;

right:20px;

top:255px;

border:1px solid #ccc;

padding:10px;

font-size:.7em;

background-color:#eee;

}

 

#events h2

{

margin:0px;

}

 

#version

{

font-size:.7em;

color:#ccc;

}

 

table

{

border-collapse:collapse;

}

 

table, tr, td

{

border:1px solid #999;

}

 

.alt

{

background-color:#ccc;

}

Related Stories

Leave a comment

Alternatively

This will only be used to quickly provide signup information and will not allow us to post to your account or appear on your timeline.