I've already blogged about how to set up the offline application cache manifest file and explored which pages do and do not have access to cached assets; so, I won't both explaining the basic configuration. What's important to understand in this post is that as the browser interacts with the cache manifest file and builds the local cache, it triggers a number of events to which we can bind. As this happens, the following application cache events are available:
checking - The browser is checking for an update, or is attempting to download the cache manifest for the first time. This is always the first event in the sequence.
noupdate - The cache manifest hadn't changed.
downloading - The browser has started to download the cache manifest, either for the first time or because changes have been detected.
progress - The browser had downloaded and cached an asset. This is fired once for every file that is downloaded (including the current page which is cached implicitly).
cached - The resources listed in the manifest have been fully downloaded, and the application is now cached locally.
updateready - The resources listed in the manifest have been newly redownloaded, and the script can use swapCache() to switch to the new cache.
obsolete - The cache manifest file could not be found, indicating that the cache is no longer needed. The application cache is being deleted.
error - An error occurred at some point - this could be caused by a number of things. This will always be the last event in the sequence.
To experiment with these events, I am going to cache the same ColdFusion file a number of times. Here is the cache manifest:
Manifest.cfm - Cache Manifest
<!--- 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 invalidate 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 (by the file contents have!). 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.10 ./index.cfm ./jquery-1.4.2.js # Cache our sleeper pages. Notice that these are all the same script, # but are differentiated by their arbitrary IDs. ./sleeper.cfm?id=1 ./sleeper.cfm?id=2 ./sleeper.cfm?id=3 ./sleeper.cfm?id=4 ./sleeper.cfm?id=5 ./sleeper.cfm?id=6 ./sleeper.cfm?id=7 ./sleeper.cfm?id=8 ./sleeper.cfm?id=9 ./sleeper.cfm?id=10 # This is where we can white-list pages that cannot be cached. NETWORK: ./manifest.cfm # This is where we can define fall-backs. FALLBACK: </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 otherwise the client simply will not treat this as a cache manifest. 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 ) ) )#" />
As you can see, we are caching the file - sleeper.cfm - ten times. I am using a URL parameter in order to differentiate the cached asset. Since the cache manifest defines URLs, not script names, the ID=N query string value is enough to turn one file into ten different cache entries. The sleeper.cfm itself will sleep for one thousand milliseconds in order to give our master page time to observe the download events as they happen.
You might notice that I have white-listed (Network) the cache manifest file itself. This is not something you would typically do; I have done this here to help minimizing cache of an asynchronous (AJAX) manifest request later on in the demo.
Sleeper.cfm - Our Cached Asset
<!--- Param the page ID. ---> <cfparam name="url.id" type="numeric" default="0" /> <!--- This page is being cached. Sleep the thread so that it gives us enough time to observe the caching events. ---> <cfset sleep( 1 * 1000 ) /> <!--- Return some content arbitrary test content. ---> <cfoutput> I am sleeper page #url.id#. </cfoutput>
Ok, now that we understand our offline application cache manifest file and the secondary assets that we are going to cache, let's take a look at the demo that makes use of the application cache events. In the following code, we will be binding the previously outlined events using the window.applicationCache object and jQuery's bind() method. As each event fires, we are simply going to output it to the screen.
In addition to binding to the application cache events, we are also binding to the window object in order to listen for the "online" and "offline" events. These events will fire as the browser gains and loses its network connection respectively.
Most of the code in this demo is just basic event binding. The trickiest thing that we're doing is calculating the total number of files that get downloaded with the cache. As the events fire, we can see how many files have been downloaded; however, there is nothing in the event data that indicates how much progress we are making. As such, when the downloading kicks off, I am using an AJAX request to grab the live cache manifest file. Then, using regular expressions, I count the number of assets listed in the "Cache" portions of the manifest file in order to find the total number of files our cache construction will attempt to download.
Each browser has a strategy for making sure that the local cache is up-to-date; however, this cache synchronization is not always as timely as we would like (it is a cache after all). If we want to force the browser to check for cache updates, we can use the window.applicationCache.update() method to initiate an update check manually. When we do this, the browser will check to see if the live cache manifest file has been altered. If it has been, the browser will then start re-downloading all of the files listed in the cache manifest.
Once the manually-initiated cache has been downloaded, we need to explicitly tell the browser to start using the newly cached assets. In order to do that, we can call the window.applicationCache.swapCache() method. This will make the new cache available on the next page refresh.
NOTE: I have found that it is not always necessary to explicitly call the swapCache() method; it appears that some browsers handle this transition more implicitly that others.
I think the basic offline application cache functionality is very cool. I also like the ability to programmatically interact with the cache; however, I'm having trouble coming up with the best use-cases for such an explicit action. In any event, it's just nice to have finally had a chance to look deeper into this.
Want to use code from this post? Check out the license.