HTML5 Offline Web Application Example
In this example we will talk about writing an Offline Web Application. Offline ? Sounds weird, doesn’t it ?
HTML5 allows us to create a “list” of files accessible after the browser is disconnected.
The browser will always have to download the main HTML file and all the files in this “list” at least once.
Let’s see how this works.
1. The Manifest
The “list” is called the manifest file. This manifest is declared with an attribute of the <html>
Tag like this:
<!DOCTYPE html> <html manifest="cache.manifest"> <body> </body> </html>
The filename does not matter, but as a convention you may use names like : manifest.appcache, myapplicaiton.manifest, …
The important thing is the Content-Type return by the server, it must be: text/cache-manifest
.
In Apache you will have to add a directive:
AddType text/cache-manifest .manifest
to force the server to serve all .manifest file with thetext/cache-manifest
Content-Type.
In NGINX, you will have to edit the
mime.types
file and add the following line:text/cache.manifest manifest;
For IIS, yous will have to add the mime type. Have a look at the answer on this StackOverflow.
And its first line must be: CACHE MANIFEST
. For example:
CACHE MANIFEST # This is a comment index.html css/style.css js/script.js
This file will tell the browser to keep three files in cache: index.html, style.css and script.js.
So now, if you are disconnected and you refresh your browser, you will always be able to use your web page.
2. Manifest Sections
We did not see it in the previous section, but the manifest file is structured with sections:
CACHE
: Files listed in this section are explicitly cached after they are downloaded.NETWORK
: Files listed in this section are explicitly NOT cached.“All requests to such resources bypass the cache, even if the user is offline”
FALLBACK
: This section will specifies fallback pages the browser will use when a resource is not accessible. Entries in this section lines of mapping urls: URL_REQUESTED URL_FALLBACK.
Note : All the files listed outside any sections are considered listed in the explicit
CACHE
section.
So let see a more complex Manifest file
:
CACHE MANIFEST # This is a comment CACHE: index.html css/style.css js/script.js NETWORK: # All requests to the Api need a network connection /api FALLBACK: add.html offline.html
This manifest file will caches 3 files, tell the browser to always try to download resources inside /api, and if the add.html file is requested, the display the offline.html file.
One side effect using application cache is that the browser won’t try to download updates cached files until the manifest file itself is updated …
This can be very disturbing during development, or when files are frequently updated.
To solve this problem we can add a version number in a comment line at the top of the manifest
file to force the browser to update its cache.
For example:
CACHE MANIFEST # V1.0.1 <-- update the version to force the browser to update its cache. # This is a comment CACHE: index.html css/style.css js/script.js NETWORK: # All requests to the Api need a network connection /api FALLBACK: add.html offline.html
3. Application Cache API
The HTML5 Application Cache comes with a JavaScript Api, that will allow us (developers) to interact with the cache.
We can access the Application cache by the window.applicationCache
object.
3.1 Events
Here are the events introduced by the Application Cache:
checking
: Fired when the user agent is looking for updates, or is trying to download the manifest file for the first time. (This is always the first event in the sequence.)error
: The manifest file is not accessible (404), or any other errors when trying to downlod the manifest.noupdate
: The manifest did not change … nothing to update.downloading
: The manifest was found or updated, so the browser starts to download resources listed in it.progress
: Fired during download progress.updateready
: Fired when resources in the manifest have been redownloaded.cached
: Fired when resources have been cached. (Last event in sequence.)
3.2 Properties
The window.applicationCache
object has only one property: status
This property returns the current status of the applicationCache
.
The value of the property is one of this constant:
UNCACHED
: 0 A special value that indicates that an application cache object is not fully initialized.IDLE
: 1 The application cache is not currently in the process of being updated.CHECKING
: 2 The manifest is being fetched and checked for updates.DOWNLOADING
: 3 Resources are being downloaded to be added to the cache, due to a changed resource manifest.UPDATEREADY
: 4 There is a new version of the application cache available. There is a corresponding updateready event, which is fired instead of the cached event when a new update has been downloaded but not yet activated using the swapCache() method.OBSOLETE
: 5 The application cache group is now obsolete.
3.3 Methods
Here are the methods of the window.applicationCache
object.
update()
: Invoke the cache download process, not really necessary, the browser, will generally take care of updating the cache.abort()
: Cancel the cache download process.swapCache()
: Switches to the most recent application cache, if there is a newer one. If there isn’t, throws an InvalidStateError exception.
4. The online / offline events
The API also add two events for Online status detection. These events are: online
and offline
.
Let see a simple example:
window.addEvenListener('online', function(){ console.log('online'); }, false) window.addEvenListener('offline', function(){ console.log('offline'); }, false)
Now we know the necessary for a “real world” example …
5. A Working Example
As an example we can imagine a simple web application, to manage your todo list.
You must not be able to add a task when you’re offline, we will do that by disabling submit button.
NOTE: The example will use the Zepto.js library to facilitate, the ajax calls and DOM manipulation..
5.1. Project structure
So the project structure will be:
/ --/css/ --/css/style.css --/js/ --/js/script.js --/server/server.php --/server/tasks.json --/index.html
The goal is to write an application that will allow you to add / remove / edit tasks, tasks will be saved in a static json file (tasks.json
) on the server (simulated here by the server.php
file).
5.2 The server.php file
As I said this will simulate a server, so do not use this file in production, this is only for testing purpose.
// We we only manage POST Requests if ($_SERVER['REQUEST_METHOD'] === 'POST') { $message = array(); $file = dirname(__FILE__)."/tasks.json"; $content = file_get_contents($file); $tasks = json_decode($content, true); switch($_POST['action']){ case "list": // create the response $message["type"] = "OK"; $message["data"] = $tasks; break; case "add": // create the task $task = array('id'=> uniqid(), "task"=>$_POST["task"]); // push it to the array pf tasks array_push($tasks, $task); // save the file : file_put_contents($file, json_encode($tasks)); // create the response $message["type"] = "OK"; $message["data"] = $task; break; case "remove": // Get the task id $id = $_POST["id"]; $tmpTasks = array(); $found = false; foreach($tasks as $task){ if( $task["id"] !== $id ){ array_push($tmpTasks, $task); } else { $found = true; } } if( $found ){ // Ok task was deleted $message["type"] = "OK"; $message["data"] = $id; file_put_contents($file, json_encode($tmpTasks)); } else { $message["type"] = "KO"; $message['message'] = "Task No Found"; } break; case "update": case "edit": // Get the task id $id = $_POST["id"]; $taskPosted = $_POST["task"]; $tmpTasks = array(); $found = false; foreach($tasks as $task){ if( $task["id"] === $id ){ $task["id"] = $id; $task["task"] = $taskPosted; $found = true; } array_push($tmpTasks, $task); } if( $found ){ // Ok task was deleted $message["type"] = "OK"; $message["data"] = array('id'=>$id, 'task'=>$taskPosted); file_put_contents($file, json_encode($tmpTasks)); } else { $message["type"] = "KO"; $message['message'] = "Task No Found"; } break; case 'reset': // save the file : file_put_contents($file, json_encode(array())); $message["type"] = "OK"; $message["data"] = ""; break; default: $message = array( "type"=>"ERR", "message" =>"Action unknown : ".$_POST["action"]."." ); break; } // Print the result echo json_encode($message); } else { $message = array( "type"=>"ERR", "message" =>"Request should be post, nothing to do ..." ); echo json_encode($message); }
This little php script allow you to use some actions to manage the task list, some of the actions needs some extra post datas, here is the API definition:
action | parameters | description |
---|---|---|
list | none | Get the full list of tasks. |
add | task : STRING | Add the task, will generate a unique id, and return a task :{ "type":"OK", "data":{ "id":"54e390fbc8823", "task":"rere" } } |
remove | id : INT | Remove the task identified by the ID, will return the id as message, or an error message :{ "type":"OK", "data":"54e390fbc8823" } or { "type":"KO", "message":"Task No Found" } |
update or edit | id : INT task : STRING | Update the task identified by the ID with the Task sent, will return the id as message, or an error message :{ "type":"OK", "data":"54e390fbc8823" } or { "type":"KO", "message":"Task No Found" } |
reset | none | Reset the task list (empty list), return an empty message :{ "type":"OK", "message":"" } |
default | none | Should not append, return an error message :{ "type":"KO", "message":"Task No Found" } |
5.3 The main HTML file
Here is the index.html
source file:
<!DOCTYPE html> <html manifest="app.manifest"> <head lang="en"> <meta charset="UTF-8"> <title>Task List Example</title> <link href="css/style.css" rel="stylesheet" /> </head> <body> <div id="wrapper"> <form id="taskform"> <input type="hidden" id="task_id" value="-1" /> <input type="text" id="task" placeholder="Add a Task ..." /> <input type="submit" value="Add" /> </form> <ul id="tasklist"></ul> <button id="reset">Clear All Tasks</button> </div> <script src="js/zepto.min.js"></script> <script src="js/script.js"></script> </body> </html>
Here is the app.manifest
file:
CACHE MANIFEST # version 1 # This is a comment CACHE: index.html css/style.css js/script.js js/zepto.min.js NETWORK: server/server.php
5.4 The JavaScript
I won’t show the full JS file as you can download it, I will only explain some parts of the code.
First of all we need to detect if the browser is onLine or offLine, as the whole code is cached (via the app.manifest file), the application can be opened without being onLine (don’t need to do the first request if it was done once before).
if(navigator.onLine){ // browser is online so I can load the list from the server server.list(init); // set the connection status to true setConnected(true); } else { // Not online, set the connection status to false setConnected(false); }
Here is the function setConnected
function setConnected(isConnected){ var buttons = 'input[type="submit"], button'; if( isConnected ){ $(buttons).removeAttr('disabled', 'disabled'); } else { $(buttons).attr('disabled', 'disabled'); } }
This function only enable or disable the buttons.
Now we need to listen to the online
and offline
events to manage the connection status:
$(window).bind('online', function(){ // If the list was never loaded if( ! loaded ){ // we load the list from the server server.list(init); } // set the connection status to true setConnected(true); }); $(window).bind('offline', function(){ // set the connection status to false setConnected(false); });
I think the above code, is documented enough to understand it.
Here is what the application looks like in “online” mode:
Here is what the application looks like in “offline” mode:
6. Download
You can download the full source code of this example here: HTML5 Offline Web Application Example
Hey great example but you have left out the reference to your manifest file in the index.html of your example.
So it does not work!