Implementing Offline Web Content with Gears LocalServer API
I spent the afternoon playing around with Google Gears in order to get a basic feel for how it works. Specifically, I wanted to see how much work it would take to add support for offline viewing for tostring.org (an online book site).
I used the Gears LocalServer API which “allows a web application to cache and serve its HTTP resources locally, without a network connection”. The implementation can be though of as a small web server that intercepts requests for remote resources and serves them (seamlessly) from a local cache. This has a couple of advantages:
- Can access content offline
- Even when online, content is pulled from cache, so access is very fast
The main disadvantages are related to #2. Since content is pulled from the local cache, even when online, users may view stale content. This can also make it a bit of a hassle to develop, as you have to remember to clear the Gears cache of the content when trying to view changes.
Gears will periodically check for updated content and sync anything that is updated. It also provides an API to allow developers to force checks for new content, as well as fine grained control over what content is cached / synced locally.
It took me most of the afternoon to implement the functionality, but the vast majority of that time was spent writing python and Django code (which I was a little rusty on), and learning some of the jQuery JavaScript library which I used to provide visual feedback while the syncing was occurring. The actual Gears implementation was pretty trivial and took maybe a total of 30 minutes (which included learning the APIs and implementing them).
Here is the complete JavaScript code (half of which provides feedback to the user while the pages are being synced).
function hasGears() {
return window.google && google.gears;
}
function updateProgressField(msg) {
$("#progress").text(msg);
}
function onSyncProgress(event) {
updateProgressField(
Math.ceil((event.filesComplete / event.filesTotal) * 100) + "%"
);
}
function onSyncComplete() {
updateProgressField("Sync Complete.");
}
function onSyncError(event) {
updateProgressField("Error Syncing.");
}
function storeForOffline() {
var localServer = google.gears.factory.create("beta.localserver");
var store = localServer.createManagedStore("tostring-store");
store.manifestUrl = "/gearsmanifest";
store.onerror = onSyncError;
store.oncomplete = onSyncComplete;
store.onprogress = onSyncProgress;
store.checkForUpdate();
}
function onStoreClick(event) {
event.preventDefault();
storeForOffline();
}
function onReady() {
if (hasGears()) {
$("#offline_span").css("visibility", "visible");
$("#offline_span").click(onStoreClick);
}
}
$(document).ready(onReady);
When a user who has Gears installed visits the site they are be presented with a link titled “Save Offline” (top right). Users who do not have Gears will not see this link. If the user clicks the link, they are presented with a Gears dialog asking for permission (I can customize this, but did not). If they allow the action, then the Gears library loads a JSON based manifest file with information on how the site should be synced (including which specific resources should be included).
You can view the full manifest for tostring.org at:
http://www.tostring.org/gearsmanifest
Here is a snippet:
{
"betaManifestVersion": 1,
"version": "2008-08-27 14:18:04",
"entries": [
{ "url": "/" },
{ "url": "/translating/" },
{ "url": "/about/" },
{ "url": "/dmedia/books/scripts/grids-min.css" },
{ "url": "/dmedia/books/css/styles.css" },
{ "url": "/dmedia/books/scripts/jquery.min.js" },
{
"url": "/books/adobe-air-for-javascript-developers-pocketguide/1.0/sv/introduktion-till-adobe-air/"
}
]
}
Gears remembers the version string, and periodically checks to see if it has changed. If it has, it will resync the content (you can do some more advanced stuff with server responses to tell Gears that a particular piece of content has not been updated).
While the content is syncing, I present a small progress indicator to the user.
Once the sync is complete, the content is viewed from the cache, regardless of whether the user is online or offline. You can test this in Firefox via File > Work Offline.
All in all, it was very easy to implement and I am impressed with the results.
Couple of issues:
- There does not appear to be a way to detect whether the page is running online or offline, and it doesnt look like Google plans to add it. It is definitely a tricky problem, but I think that Adobe AIR handles it well, and at least makes it easier for the develop.
- There is not a way to detect whether content has already been cached (at least not without prompting another security dialog)
- There is no indication or UI indicating whether you are viewing “live” content or cached content.
- You can only cache / sync pages from the same domain that the web page originated from. This means is you are loading any remote libraries or resources (such as remote JavaScript libraries), you will need to start hosting them yourself if you want them to be synced with your content.
It seems that calling any of the APIs will result in a security / permission dialog being raised (even though some of the docs suggest otherwise). Which ist the best experience when using multiple APIs.
One interesting thing about the API is that it can be used for more than just making web content available offline. You could also use to to cache frequently used assets to:
- Speed of loading of web pages and applications
- Reduce load and bandwith on the server
Indeed, WordPress is using Gears to help make their admin application more responsive.
The next step will be to add a desktop shortcut for the application using the Desktop API. This also looks to be pretty trivial although I am concerned that the user might be presented with two permission dialogs.
If you have Gears installed, you can see this example in action at tostring.org.
If you have any questions or suggestions, or find any errors in my code (likely), then post them in the comments.