Offline web applications: a working example
Join the DZone community and get the full member experience.
Join For FreeWhat happens in the debate for a native application vs. web application in the case offline usage comes into play? Surely you can't use a web application if you do not have an active connection for loading it.
With the boatload of innovations in the HTML5 and related specifications, this has changed. In a few Pomodoros (3 half hours) I could learn how to code a web application able to run in a stable browser like Firefox 5 without a network connection.
What this application does?
Since it serves as an example for this post, it simply loads a remote .php file which changes often, and that represents for example a stream of data such as Twitter's newsfeed.
When the connection is not available, the application displays the data from the last time it was able to connect to the server. Of course, while it is offline it could do anything from slicing bread to executing arbitrary JavaScript code: the point was showing a web application working from the cache and able to recognize that it is currently offline.
This is the first time you load it: index.html is downloaded and it makes an Ajax request for the news feed.
If you refresh the page, the browser caches some content I have chosen (index.html), but still the news feed can be downloaded via Ajax:
If now you cut the network cable, and refresh again, the application becomes aware that it's offline and retrieves a saved copy of the news (of course it can do anything you can code in JavaScript):
What technologies you have used?
First of all, the cache manifest from the Offline Web applications part of the HTML5 specification. With this manifest, I was able to specify that some file should have been cached for offline usage (my page containing JavaScript code and its style sheet). At the same time, other files were listed as always to be loaded from the network (my news feed and some other machinery).
Another crucial technology is the Web Storage, in this case the localStorage JavaScript object which serves as a database for storing the last newsfeed retrieved via Ajax.
And of course I've used Ajax both to retrieve the news feed from my cached main page (that is never reloaded), and to ping the server to check if the connection is really available or if we are just in a LAN.
Which browser you used?
I used Firefox 5, and you need to be aware of a few issues if you want to develop an offline web application.
The cache you have to clear in case you want to reset the application after some changes to the code is in Options (or Preferences on Linux) -> Advanced -> Network tab, at the label "The following websites have stored data for offline use".
Firebug will continue to show HTTP requests as if you were connected to a real server, a value in the Remote IP column; even with localhost, an IP address is usually shown when the connection is really established.
You should check File -> Work offline to simulate being offline, since interrupting the connection won't work while loading from localhost or another hostname pointing to 127.0.0.1.
Which resources you have read?
The wonderful online (and not) book Dive into HTML5 by Mark Pilgrim, which has an introductory chapter on the topic.
A guide from Mozilla about offline resources, which has more examples of cache manifest lines.
This Ed Norton's article about detecting the switch between online and offline status with a real ping, since the JavaScript API cannot be trusted in many implementations.
Show us the code!
Yep, I was going to do that. However there's a repository containing it all if you want to play with it.
First of all, I have an .htaccess file which will work on Apache webservers:
AddType text/cache-manifest .manifest ExpiresActive On ExpiresDefault "access"
The first line is necessary to serve the manifest with the right MIME type. The other two are to avoid any caching on the web server (it's already done in the browser), and develop without hassles. It's not a production setting: in that case it should target only the manifest.
Here is my manifest:
CACHE MANIFEST NETWORK: /news.php /ping.js CACHE: /style.css http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js
I only used 2 of the 3 available sections. What is in NETWORK will always be loaded if requested, what is in CACHE will always be cached.
My main page:
<!DOCTYPE HTML> <html manifest="/cache.manifest"> <head> <title>My offline web application</title> <link rel="StyleSheet" type="text/css" href="style.css" /> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script> <script type="text/javascript"> $(document).ready(function () { $(document.body).bind("online", checkNetworkStatus); $(document.body).bind("offline", checkNetworkStatus); checkNetworkStatus(); }); function checkNetworkStatus() { console.log('I am checking'); if (navigator.onLine) { // Just because the browser says we're online doesn't mean we're online. The browser lies. // Check to see if we are really online by making a call for a static JSON resource on // the originating Web site. If we can get to it, we're online. If not, assume we're // offline. $.ajaxSetup({ async: true, cache: false, context: $("#status"), dataType: "json", error: function (req, status, ex) { console.log("Error: " + ex); // We might not be technically "offline" if the error is not a timeout, but // otherwise we're getting some sort of error when we shouldn't, so we're // going to treat it as if we're offline. // Note: This might not be totally correct if the error is because the // manifest is ill-formed. showNetworkStatus(false); }, success: function (data, status, req) { showNetworkStatus(true); }, timeout: 5000, type: "GET", url: "ping.js" }); $.ajax(); } else { showNetworkStatus(false); } } var currentlyOnline = null; function showNetworkStatus(online) { if (online != currentlyOnline) { if (online) { $("#online_status").html("Online"); $('#news').load('/news.php', function (response) { localStorage.setItem('news', response); }); } else { $("#online_status").html("Offline"); $('#news').html(localStorage.getItem('news')); } currentlyOnline = online; } } </script> </head> <body> <p>Hello, world!</p> <div id="news"></div> <div id="online_status"></div> </body> </html>
The <html> element has an attribute pointing to the manifest, and the file loads other resources from the cache like jQuery and the style sheet.
The checkNetworkStatus function is called upon relevant events from the JavaScript API and at the startup: it makes a ping to the web server to verify the connection is open.
The showNetworkStatus function instead executes an action in case the status of the connection changes:
- it updates the Offline/Online label at the bottom of the page.
- In the online case, fills the #news div with updated text retrieved by Ajax (saving it).
- In the offline case, fills the #news div with the last text saved.
ping.js is an empty file:
{}
news.php constantly updates to show the effects of being again online.
<?php echo '<p>Last update: '. date('Y-m-d H:i:s') . "</p>\n"; ?> <p>Lorem ipsum dolor amet...</p>
Conclusion
We're finished. It's feasible and easy, at least for the base case, to deploy to a desktop or mobile platform which can be used even while offline (but of course it would sync with the server only when the connection is available.) Not good times for native applications...
Opinions expressed by DZone contributors are their own.
Comments