source: www.flickr.com/photos/stephendann/80558500

Why Does Offline Matter?

  • In the US we generally have good connectivity, but not always
  • Globally connectivity varies
  • Unavailable servers

Why Does Offline Matter To Me?

Hydrocephalus

The Problem

The Solution

  • HTML5 features including:
    • Application cache
    • IndexedDB
    • FileSystem API
    • Canvas
  • Google Chromebooks as deployment devices

Rules for Survival

Rules
  • Live off the grid in a fortified shelter
  • Store your supplies
  • Have adequate weaponry
  • Use your escape plan
source: www.flickr.com/photos/stephendann/80558500

Live off the grid: Application Cache

source: www.flickr.com/photos/jrimages/7688229244

Live off the grid: Application Cache

        <html manifest="manifest.appcache">
            
        CACHE MANIFEST
        #version 1.0
        
        CACHE:
        #files required for offline        
        /css/mycss.min.css
        /images/lol_cat.png
        /js/myjs.min.js
        
        NETWORK:
        #fetch everything else from the network when online
        *
            

Live off the grid: Application Cache

        <html manifest="manifest.appcache.php">
            
        <?php
        function autoVer($filename) {
            //return filename with modified timestamp
        }?>
        CACHE MANIFEST
        #version 1.0  <?php autoVer('js/myjs.min.js'); ?>

        CACHE:
        #files required for offline
        /css/mycss.min.css
        /images/lol_cat.png
        <?php autoVer('js/myjs.min.js'); ?>
                
        NETWORK:
        #fetch everything else from the network when online
        *
            

Application Cache Browser Support

source: caniuse.com/#feat=offline-apps
github.com/mastahyeti/browserstats

Supplies: IndexedDB

source: www.flickr.com/photos/24218656@N03/5934567119
ASYNC all the things

Supplies: IndexedDB

<script>
window.indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;
var myDB, dbVersion = 1, openRequest = window.indexedDB.open('MyDatabase', dbVersion);
openRequest.onerror = function(errorEvent) {
    //Async response, something didn't work
}
openRequest.onsuccess = function(successEvent) {    
    myDB = successEvent.target.result; //Async response, grab the database from the request.
    doStuffAfterOpen();
}
openRequest.onupgradeneeded = function(upgradeEvent) {
    myDB = upgradeEvent.target.result;
    var newObjectStore = myDB.createObjectStore('lolcats', { keyPath: 'id' });
    newObjectStore.createIndex('nameidx', 'name', { unique: true });
    newObjectStore.createIndex('categoryidx', 'category');
}
</script>
            

Supplies: IndexedDB

<script>
function doStuffAfterOpen() { //This will only work after the async call to open the DB    
    var transaction = myDB.transaction('lolcats','readwrite');
    var lolStore = transaction.objectStore('lolcats');
    var addRequest = lolStore.add({
        id: 1,
        name: 'Caturday',
        category: 'Lazy kitty',
        url: 'http://cheezburger.com/6677232128'
    });
    addRequest.onsuccess = function(successEvent) {//Async response, yes you can haz cheezburger};
    addRequest.onerror = function(errorEvent) {//Async response, no cheezburger for you};

    var getRequest = lolStore.get(1); //Get by id
    getRequest.onsuccess = function(successEvent) { //Async response        
        console.log("Funny kitty here: " + getRequest.result.url);
    };
    getRequest.onerror = function(errorEvent) {//Async response, no funny kitties here};
}
</script>
            

Supplies: IndexedDB

<script>
var index = lolStore.index('nameidx'),
    keyRange = IDBKeyRange.bound('C', 'H'),
    idxRequest = index.openCursor(keyRange);

idxRequest.onsuccess = function(successEvent) { //Async response 
    var cursor = successEvent.target.result;
    if (cursor) {
        console.log("Using key range C - H, Found kitty with name of:"+cursor.key);                  
        cursor["continue"]();
    }
};
index = lolStore.index('categoryidx');
keyRange = IDBKeyRange.only('Cat beard');
idxRequest = index.openCursor(keyRange);
idxRequest.onsuccess = function(successEvent) {//Found cat beards!!!!};
</script>
            

Supplies: IndexedDB

Chrome Developer Tools

IndexedDB Browser Support

source: http://caniuse.com/#feat=indexeddb
github.com/mastahyeti/browserstats

IndexedDB Browser Support with Polyfill

source: http://caniuse.com/#feat=indexeddb
github.com/mastahyeti/browserstats

Adequate Weaponry: FileSystem API

source: www.flickr.com/photos/hyperxp/7473652752/

Adequate Weaponry: FileSystem API

var fileSystem, size = 1024*1024; //1MB
function handleError(errorEvent) { 
    console.log('Error with filesystem API'); //Async error handler
}
function openFileSystem() {
    window.requestFileSystem(PERSISTENT, size, handleOpenFileSystem, handleError);
}
function handleOpenFileSystem(newFileSystem) {
    fileSystem = newFileSystem;
    //Save file from <input type="file" name="fileinput" id="fileinput">
    var fileToSave = document.querySelector('#fileinput').files[0];
    fileSystem.root.getFile(fileToSave.name, {create: true, exclusive: true}, function(fileEntry) {        
        fileEntry.createWriter(function(fileWriter) {
            fileWriter.write(fileToSave);
            console.log("file available at:"+fileEntry.toURL());
        }, handleError);
    }, handleError);    
}
window.webkitStorageInfo.requestQuota(PERSISTENT, size, openFileSystem, handleError);
            

Adequate Weaponry: FileSystem API

<script>
function getFileAsDataURL(fileSystem) {
  //First get the file entry representing the call (async response)
  fileSystem.root.getFile('lol_cat.jpg', {}, function(fileEntry) {
    //Next get the actual file (async response)      
    fileEntry.file(function(fileToRead) {
        //Next read the file as dataURL(async response)      
       var reader = new FileReader();
       reader.onloadend = function(readerEvent) {           
         var dataURL = readerEvent.target.result;         
       };
       reader.readAsDataURL(fileToRead);
    }, handleError);
  }, handleError);
}
window.requestFileSystem(PERSISTENT, size, getFileAsDataURL, handleError);
</script>
            

FileSystem Browser Support

source: caniuse.com/#feat=offline-apps
github.com/mastahyeti/browserstats

An Escape Plan

source: www.flickr.com/photos/impactmatt/5158159260

An Escape Plan: navigator.onLine

Trust No One
  • window.navigator.onLine

    "Returns false if the user agent is
    definitely offline (disconnected from the
    network). Returns true if the user agent
    might be online. The events online and
    offline are fired when the value of this
    attribute changes."

  • *Might* be online??
  • XHR is our friend
  • window.online event gives a hint of
    online availability

An Escape Plan: navigator.onLine

//Initialize connection status to navigator.onLine property.
var onlineStatus = navigator.onLine; 
$( window ).on( "online", function( event ) {
  onlineStatus = true;  //Persist offline data to the server
});
$( window ).on( "offline", function( event ) {
  onlineStatus = false;
});
$.ajax({
  cache: false, dataType: "json", url: "/save", timeout: 500, type: "POST",
  data: {"foo": "bar"},
  error: function( jqXHR, textStatus, errorThrown ) {    
    onlineStatus = false; //The ajax call failed, therefore the server is offline to us.
  },
  success: function( data, textStatus, jqXHR ) {    
    onlineStatus = true; //The ajax call succeeded, therefore the server is online to us.
  }
});
            

Thank You!