Offline

As your world used to be

  -  

Who?

+


@


Blog: bit.ly/ido-blog


Slides: bit.ly/offline-11

Summary

Before HTML5

Why offline?

Store Dynamic Data

Web Storage (localStorage/sessionStorage)

IndexedDB/WebSQL

localStorage

localStorage for simple key-value storage

if (!localStorage.getItem("checkins")) {
  localStorage.setItem("checkins", JSON.stringify([]));
}

Easy API

setItem()
getItem()
removeItem()
clear()

sessionStorage

window.sessionStorage

Web Storage APIs only store strings!

Solution:

var userstr = JSON.stringify({
  user: 'john',
  id: 10
});

localStorage.setItem('user', userstr);

var user = JSON.parse(localStorage.getItem('user'));

Web Storage API

  http://example.com:80/
    \       \         \_ port
     \       \_ domain
      \_ scheme
  

Example: Auto-save Text

document.querySelector('#ta').addEventListener('keyup', function(e) {
  localStorage.setItem('value', this.value);
  localStorage.setItem('timestamp', (new Date()).getTime());
}, false);

IndexedDB

Currently only Chrome and Firefox have implemented IndexedDB, however, the major browser vendors have indicated an intention to support it.

Today, it's supported via vendor prefixes. Let's simplify this:

window.indexedDB =
    window.indexedDB ||
    window.webkitIndexedDB ||
    window.mozIndexedDB;

IndexedDB

Finding things

Retrieving by key ( indexes ):

// db.createObjectStore("Friend", "id", true);
db.createIndex("FriendNames", "name", false);
var index = db.openIndex('FriendNames');
var id = index.get('Eric');

Querying ( cursors ):

// Restrict to names beginning A-E
var range = new KeyRange.bound('A', 'E');
var cursor = index.openObjectCursor(range);

while (cursor.continue()) {
  console.log(cursor.value.name);
}
var idbRequest = window.indexedDB.open('Database Name');
idbRequest.onsuccess = function(event) {
  var db = event.srcElement.result;
  var transaction = db.transaction([], IDBTransaction.READ_ONLY);
  var curRequest = transaction.objectStore('ObjectStore Name').openCursor();
  curRequest.onsuccess = ...;
};

WebSQL?

Store Static Resources

App Cache

<html manifest="example.appcache">... </html>
CACHE MANIFEST
# 2010-11-17-v0.0.1

# Explicitly cached entries
CACHE:
index.html
stylesheet.css
images/logo.png

# static.html will be served if the user is offline
FALLBACK:
/ /static.html

# Resources that require the user to be online.
NETWORK:
*

App Cache Gotchas

Load twice workaround

applicationCache.addEventListener('updateready', function(e){
  if (applicationCache.status == 
         applicationCache.UPDATEREADY){
    if (confirm('Load new content?')) {
      ...
    }
  }
});

DEMO: Apps using offline features

Store Binary Data And Modify It

Opening the file system

window.requestFileSystem(
  TEMPORARY,        // persistent vs. temporary storage
  1024 * 1024,      // size (bytes) of needed space
  initFs,           // success callback
  opt_errorHandler  // opt. error callback, denial of access
);

1, 2, 3, Ways to generate URLs to files

var img = document.createElement('img');

// filesystem:http://example.com/temporary/myfile.png
img.src = fileEntry.toURL();
document.body.appendChild(img);

Retrieve a file by its filesystem URL:

window.resolveLocalFileSystemURL(img.src, function(fileEntry) { ... });

Fetching a file by name

function initFs(fs) {

  fs.root.getFile('logFile.txt', {create: true}, function(fileEntry) {

    // fileEntry.isFile == true
    // fileEntry.name == 'logFile.txt'
    // fileEntry.fullPath == '/logFile.txt'

    // Get a File obj
    fileEntry.file(function(file) { ... }, errorHandler);

    // fileEntry.remove(function() {}, errorHandler);
    // fileEntry.moveTo(...);
    // fileEntry.copyTo(...);
    // fileEntry.getParent(function(dirEntry) {}, errorHandler);

  }, errorHandler);

}

Duplicating user-dropped files

document.querySelector('#terminal').ondrop = function(e) {
  var files = e.dataTransfer.files;

  window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) {

    Array.prototype.slice.call(files || [], 0).forEach(function(file, i) {
      fs.root.getFile(file.name, {create: true, exclusive: true}, function(fileEntry) {
        fileEntry.createWriter(function(fileWriter) {
          fileWriter.write(f); // Note: write() can take a File | Blob.
        }, errorHandler);

      }, errorHandler);
    });

  }, errorHandler);

};

DEMO: Peephole Extension


Example page

Install

Data Synchronization

Data Synchronization

Data Synchronization: Shared Web Workers

Web Workers supports the classic store-and-forward paradigm. Instead of directly communicating with a server, app communicates with a Worker that buffers changes locally and communicates with the server when online.

A Shared Worker can:

navigator.onLine - know when you're all alone

if (navigator.onLine) {
  console.log('ONLINE!');
} else {
  console.log('Connection flaky');
}
window.addEventListener('online', function(e) {
  // Re-sync data with server.
}, false);

window.addEventListener('offline', function(e) {
  // Queue up events for server.
}, false);

Increase app performance

Use offline features to increase performance

Use offline features to increase performance

"Google and Bing break up their JavaScript and CSS into smaller blocks and save them in localStorage on mobile devices. Simultaneously they set a cookie so that the server knows not to send that payload on subsequent searches, a savings of 150-170 kB before gzipping."

App cache & localStorage survey - Steve Souders

Debug Offline Features

Quota

Notion of Temporary storage and Persistent Storage
(*) All above is on Google Chrome

Quota API

// Request Status
webkitStorageInfo.queryUsageAndQuota( 
    webkitStorageInfo.TEMPORARY,   // or PERSISTENT 
    usageCallback, 
    errorCallback); 

// Request Quota
webkitStorageInfo.requestQuota( 
    webkitStorageInfo.TEMPORARY,   // or PERSISTENT 
    quotaCallback, 
    errorCallback);

Quota API - Tips

Support

FireFox Safari Chrome.png Opera IE
Web StorageYYYYY (8+)
IndexedDBYNYNN
WebSQLNYYYN
App CacheYYYYN
File System APINNYNN

Support (Mobile)

FireFox Safari Chrome On Android Opera IE
Web StorageYYY (2+)YY
IndexedDBYNNNN
WebSQLNYY (2+)YN
App CacheYYY (2.1+)YN
File System APINNY (3+)NN

Package your app to get more quota (if you need it)

For most apps, you can get them packaged up for distribution in a matter of a minute or two. Just go to appmator.appspot.com!

Because we're using the application cache, you may want to request the "unlimitedStorage" permission. Just paste it in manually to the manifest.json file.

Offlining your apps

{
  "name": "Great App Name",
  "description": "Pithy description",
  "version": "0.0.0.1",
  "icons": {
    "128": "icon_128.png"
  },
  permissions : [ "unlimitedStorage" ],
  "offline_enabled": true,
  "app": {
    ...
  }
}

Polyfills

Web Storage (LocalStorage and SessionStorage)

More Resources

Thank you!

Questions?