How does it basically work?
We use HTML5 Application Cache (see next chapter) to tell the browser which files to cache and make available for offline browsing. We make minimal usage of it by caching only four files as our basic framework:
- jquery.js - for some convenient things like
- scow.js - the main script, cache if online, restore from cache if offline
- index.html - the main entry point of our website
- offline.html - the content of this file will be delivered when offline and not cached
No matter if online or offline, this framework and our script will be loaded.
User is online
This gives a complete snapshot of the current page.
User is offline
The script checks if the requested page is cached by localStorage. If not the content of
offline.html is simply displayed, telling the user that the requested file is not cached.
This results in the same page the user has seen online, but it was dynamically loaded from the cache. Viewing the source code will reveal the content of
HTML5 Applicaiton Cache - Fallback File
Application Cache is the key feature for this tutorial and the demo. Basically we tell the browser which specific files to cache. These files then will be loaded from the application cache, offline and online .
There are really good tutorials about the application cache on the interwebs. For a introduction and further reading check out these:
- Go offline with application cache
- A Beginner's Guide to Using the Application Cache
- How to create offline HTML5 web apps in 5 easy steps
This is the
.appcache file, the offline manifest, for the demo application. It tells the browser to specifically cache the four 'framework' files and use
offline.html as fallback file.
- CACHE MANIFEST
- / offline.html
- # LAST UPDATE
- # 01/17/12
For this tutorial it's crucial to understand the fallback file mechanism.
A example: A user is offline and tries to load a non-cached page
style.html . Since the file is not cached the browser falls back to the content of
offline.html . The path in the browser is still pointing to
style.html and can be read via
Since we know what the visitor originally was trying to load we can restore it from localStorage if it's cached.
And this is how
offline.html looks like. We rely on the cache manifest
scow.js are cached by application cache, so we load them like normally.
Development environment - A small Sinatra app
Testing a offline application can be tricky since application cache will load the files, no matter if online or offline, from the cache.
To reset a cache it's not enough to update single files, you have to update the content of the cache manifest
So for more convenient development I wrote a small Sinatra app that has a simple appcache reset function. When visiting the
/new_cache path a new timestamp is generated resulting in a updated cache manifest file and forced reloading.
It also makes sure the cache manifest is delivered with the proper mimetype
Then we could have a simple link:
That is only shown if we are in development mode, speak working on
If you have Thor installed use the command
thor server:start to start the dev server.
Check out the source code of the server at GitHub .
Clearing localStorage when the application cache is updated
Since we now can re-cache files from the application cache we also need a way to reset the localStorage. Application cache provides several events we can use. We are listening for the
updateready event which will be fired when the browser is done receiving a new offline manifest. At this point we reset the whole localStorage and clear every cached file by our script.
- applicationCache.addEventListener 'updateready', -> # if the manifest files are newly cached
- localStorage.clear() # clear also local store
Checking if the client is online/offline
This is also HTML5 specific .
navigator.onLine will return
true if there is a internet connection, otherwise it returns
- if navigator.onLine # check if there is a internet connection
- @cacheCurrentFile() # if so cache the current file
- @restoreFromCache() # try to restore the requested file from cache
Read/write from/to localStorage
Working with HTML5's localStorage is pretty straight forward. We just use
.getItem() to read from the storage and
.setItem() to write to the storage.
Additionally we use JSON so we can store objects in the localStorage. It will be encoded when storing a value and decoded when reading a value.
- getStorage: (name) -> # helper function to read/decode JSON from local storage
- item = localStorage.getItem(name) # try to read from local storage
- if item isnt null # item was found in storage
- item = JSON.parse(item) # json encoded object
- item # return the object or null if not found
- setStorage: (name, value) -> # helper function to write/encode JSON to local storage
- item = JSON.stringify(value) # create json string
- localStorage.setItem name, item # write json string to local storage
Caching asset files
$.get . When the content is loaded store it in localStorage as simple string.
- loadAssetCallback: (path, content) -> # is called when a file is fully loaded
- unless @isAssetCached(path) # check if asset already cached
- @assets.push path # add path to assets index
- @setStorage path, content # cache the file with the path as key
- @setStorage 'assets', @assets # store the new assets index array
- @updateAssetsIndex() # update the demo list of the assets index
- loadAsset: (path) -> # load a asset with $.get
- $.get path, (content) => # get the file content
- @loadAssetCallback path, content # content loaded, invoke the callback
- loadAssets: (paths) -> # cache either css or scripts
- for path in paths # go through all paths
- unless @isAssetCached(path) # check if asset already cached
- @loadAsset path # start loading the asset
- getAssets: (selector, src) -> # extract the assets from the header and return array
- retArr =  # array to return
- for el in @headEl.find(selector) # all elements matching the selector
- retArr.push $(el).attr(src) # read the attribute containing the source path and store it in array
- retArr # return a array of the file paths
Caching HTML files
.html files is a bit more complicated than storing a simple string in localStorage. We need the content of the file, the title and all the linked assets as index. We create a object that is storing all these information and store it as JSON encoded string.
- cacheCurrentFile: -> # cache the requested .html file
- cssAssets = @getAssets('link[rel="stylesheet"]', 'href') # get array of stylesheets
- cacheObj = # this will hold the cached page and all assets references
- bodyHtml : @bodyEl.html() # cache the content of the current file
- title : document.title # cache the title
- stylesheets: cssAssets # array with all stlesheets
- @loadAssetCallback(@curFileName, cacheObj) # manually invoke the callback and save the object
- @loadAssets cssAssets # begin to load all the css assets
Restoring assets from cache
- restoreHeader: (paths, wrapper) -> # reassemble and include the cached files
- combined = '' # include all in one string
- for path in paths # go through all cached assets
- content = @getStorage(path) # try to get the content from local storage
- if content isnt null # check if the requested file is cached
- combined += content # add the cached content
- $(wrapper) # create a jquery object from the wrapper markup
- .text(combined) # set the content
Restore HTML and title from cache
The original cached content will be inserted into the pages
body element. The title will be restored via the good old
- restoreFromCache: -> # restore requested file and assets from cache
- cached = @getStorage(@curFileName) # get the cached object with body, title and assets refs
- if cached isnt null # check if the file is cached
- @bodyEl.html cached.bodyHtml # restore body with original dom
- document.title = cached.title # restore original title
- # restore all stylesheets via including them into the header
- @restoreHeader cached.stylesheets, '<style type="text/css">'