After playing with Safari's SQLite support for use with creating client-side databases, I thought I would try looking into HTML5's "Cache Manifest" for creating offline web applications. The Cache Manifest is a text file that lists out all of the application resources that need to be cached in order for the given application to work without an internet connection. The files listed in the cache manifest get stored in the "Application Cache". The Application cache is like the browser's standard disk cache, but much more robust. Once a resource is stored in the application cache, the browser will never request it from the web until the cached item is de-cached or the cache manifest is invalidated.
When it comes to using the cache manifest, I'm less interested in the "offline" aspect of it and more interested in the beasty caching mechanism that comes with it. Once the browsers sees the cache manifest file and gets your approval to create an offline cache, the browser then proceeds to progressively download all cachable items in the background. As such, you can hit your homepage (for example) and end up getting the entire application cached. And, once the application resources are cached, navigating around the application essential becomes a local operation, not a remote one.
To experiment with the HTML5 Cache Manifest, I created a very simple photo gallery page that lists out a few links to view higher resolution photos. Each of the "view" pages is a ColdFusion page that dynamically loads a given photo based on the photo ID. The HTML for the list page is rather straightforward:
<!--- Use an HTML5 doc-type. This is required for some browsers to work properly with the offline-cache. ---> <!DOCTYPE HTML> <!--- Add a file to the cache manifest. NOTE: Any file that calls a cache manifest file will be automatically cached, even if it is in the WHITELIST portion of the cache manifest. ---> <html manifest="./cache_manifest.cfm"> <head> <title>Cache Manifest For Offline Pages</title> <!--- These are the two graphics (logo and startup screen) use for "app mode" on the iPhone (when the web page is put on the main screen as its own logo). NOTE: This part has nothing to do with the cache manifest stuff - this is strictly related to runnin a website in "App Mode" on an iPhone. Feel free to remove this entirely from the example. ---> <link rel="apple-touch-icon" href="icon.png"/> <link rel="apple-touch-startup-image" href="home.png" /> </head> <body> <h1> Cache Manifest For Offline Pages </h1> <h3> Pictures of Alisha Morrow </h3> <ul> <cfoutput> <!--- Notice that we are dynamically generating this page. It will only be generated on the first request. After that, this page will be coming out of the cache. ---> <cfloop index="pictureIndex" from="1" to="5" step="1"> <li> <a href="view.cfm?id=#pictureIndex#" >View Image #pictureIndex#</a> </li> </cfloop> </cfoutput> </ul> <p> Now: <cfoutput> #timeFormat( now() , "hh:mm:ss TT" )# </cfoutput> </p> </body> </html>
When it comes to the cache manifest, there are two crucial things going on here. First, because cache manifest is part of HTML5, we need to use the HTML5 doctype:
While this is not required for all browsers (supposedly), it is recommended for cross-browser comparability. The second, and perhaps more important thing that we are doing is including a link to the actual cache manifest file. This link is defined in the HTML tag:
This file, cache_manifest.cfm, is the file that lists out all of the cachable resources in our application:
Cache Manifest (cache_manifest.cfm)
<!--- Define the Cache Manifest content. I'm doing it this way since the "CACHE MANIFEST" line needs to be the first line in the file and storing it in a buffer allows us to TRIM later without having ugly line breaks. ---> <cfsavecontent variable="cacheManifest"> <!--- NOTE: Cache Manifest must be the very first thing in this manifest file. ---> CACHE MANIFEST <!--- When a cache manifest is reviewed by the browser, it uses a complete byte-wise comparison. As such, we can use COMMENTS to defunk a previously used cache manifest. In this way, we can use a version-comment to indicate change even when the file list has not changed. NOTE: If ANY part of this file is different from the previous cache manifest, ALL of the files are re-downloaded. ---> # Cache Manifest Version: 1.3 <!--- Let's list the file that get cached. The URLs to these files are relative to the cache manifest file (or absolute). ---> # Core files. ./index.cfm # iPhone App files. ./icon.png ./home.png # View Pages. Notice that these can be dynamic pages as long # as they are valid URLs. ./view.cfm?id=1 ./view.cfm?id=2 ./view.cfm?id=3 ./view.cfm?id=4 ./view.cfm?id=5 # Images that get viewed. ./images/1.jpg ./images/2.jpg ./images/3.jpg ./images/4.jpg ./images/5.jpg </cfsavecontent> <!--- ----------------------------------------------------- ---> <!--- ----------------------------------------------------- ---> <!--- Let's reset the output and set the appropriate content type. It is critical that the manifest file be served up as a type "text/cache-manifest" mime-type. NOTE: We need to be careful about the whitespace here since the very first line of the file must contain the phrase, "CACHE MANIFEST". As such, we must TRIM() the content. ---> <cfcontent type="text/cache-manifest" variable="#toBinary( toBase64( trim( cacheManifest ) ) )#" />
When it comes to the cache manifest, there are few essential points to understand. One, the phrase, "CACHE MANIFEST", must be the very first piece of content in the cache manifest file. Two, the file must be served up as the mime-type, "text/cache-manifest". And three, the entire cache manifest file is versioned by byte. This last point means that if any part of the cache manifest changes, including any of the comments (#), that cache is invalidated. As this point - when the cache is invalidated - the entire list of cachable resources must be downloaded again.
Just as in a CSS file, the resource paths listed in the cache manifest can be full URLs or URLs relative to the location of the cache manifest file. Because we are caching dynamic pages, each "dynamic version" of the page needs to be individually listed out in the cache manifest. There are other ways to do this, but for my experiment, this was the lowest-hanging-fruit solution.
The "view" page that I am linking to does nothing more than take the given ID and dynamically generate an IMG tag.
<!--- Param the image ID that we are going to view. ---> <cfparam name="url.id" type="numeric" /> <!--- Use an HTML5 doc-type. This is required for some browsers to work properly with the offline-cache. ---> <!DOCTYPE HTML> <!--- Add a file to the cache manifest. NOTE: Any file that calls a cache manifest file will be automatically cached, even if it is in the WHITELIST portion of the cache manifest. ---> <html manifest="./cache_manifest.cfm"> <head> <title>View Image</title> </head> <body> <h1> View Image </h1> <p> « <a href="./index.cfm">Back to List</a> </p> <p> <cfoutput> <img src="./images/#url.id#.jpg" width="300" /> </cfoutput> </p> </body> </html>
I am including the cache manifest link in the view page as well; although, I don't believe it is required. As long as a user hits the list page first, the cache manifest will be downloaded and the view pages should be cached automatically. I simply kept it here for good measure.
In the list page, you may have noticed a few bizarre LINK tags. These LINK tags are specific to the Apple iPhone and are used to define the Icon and Startup screen graphics to be used when the web application is running in "App Mode". This is the full-screen mode that can be achieved when the given URL is added to the home screen of the iPhone. These link tags are not relevant to the cache manifest; but, they are indicative of where I want to go with this type of HTML5 exploration - building native-esque web applications for the iPhone.
The cache manifest seems like a very cool feature of HTML5. I was able to get this example running in FireFox, Safari, and on my iPhone (didn't test any other browsers). I didn't do too much with my desktop Safari research, but it was mighty awesome to see this working in Offline mode in FireFox and in Airplane mode on the iPhone. As I have stated before, where I'd like to go with this research ultimately, is building my Dig Deep Fitness web application to run as if it were a native application on the iPhone.
NOTE: When I first started playing around with the cache manifest, I was having the hardest time getting the offline cache to be recognized. For two days I kept checking my spelling and my linking to no avail. Then, I restarted my FireFox and blam - it starts working. So, if this doesn't work right off the bat, you might have to restart your browser.
Want to use code from this post? Check out the license.