Loading...

Preflight

Press:
  • Space or / to move around
  • Ctrl/Command / or + to zoom in and out if slides don’t fit
  • N to show/hide speaker notes
  • H to highlight important elements in code snippets
  • 3 to toggle 3D

HTML5 Storage

Eric Bidelman < e.bidelman@google.com >
@ebidel

Why?

Keep yourself organized

...instant retrieval of structured data

http://www.flickr.com/photos/squirmelia/2451382877/

Reduce network requests

...bandwidth is expensive!

http://http://www.flickr.com/photos/squirmelia/2451382877/

Performance

http://www.flickr.com/photos/warloofer/4203622857/
Before HTML5
  • Cookies
  • Flash Storage
  • Internet Explorer UserData
  • Google Gears
  • Dojo Storage
  • window.name ( hack )
Storage Options in HTML5
  • Web Storage APIs: localStorage / sessionStorage
  • Web SQL Database
  • IndexedDB
  • Application Cache
  • File* APIs
  • ...Cookies

Web Storage APIs

localStorage / sessionStorage

localStorage

window.localStorage

  • Key / value pairs - hash table
  • Persistent on page reloads
  • Avoids HTTP overhead of cookies
  • Great for storing user preferences
Local Storage API
localStorage.setItem('someKey', someValue);
localStorage.getItem('someKey'); // value

// Can also access as a property, but not recommended.
localStorage.someKey = someValue;
Local Storage API
localStorage.setItem('dateOfBirth', '1984-07-22');
localStorage.getItem('dateOfBirth'); // '1984-07-22'
localStorage.length // num of items stored
localStorage.key(0); // 'dateOfBirth'
localStorage.removeItem('dateOfBirth');
localStorage.clear();
Local Storage API
  • Scoped to an origin
http://example.com:80/
  \       \         \_ port
   \       \_ domain
    \_ scheme
sessionStorage

window.sessionStorage

Same as localStorage but...
  • Lasts as long as browser is open
  • Opening page in new window or tab starts new session
  • Great for sensitive data (e.g. banking sessions)
Using Native JSON
  • Web Storage APIs only store strings!

Solution:

localStorage.setItem('user', JSON.stringify({user: 'john', id: 10}));

var user = JSON.parse(localStorage.getItem('user'));
Example: Auto-save Text
<textarea id="ta" placeholder="Start typing..."></textarea>
document.querySelector('#ta').addEventListener('keyup', function(e) {
  localStorage.setItem('value', this.value);
  localStorage.setItem('timestamp', (new Date()).getTime());
}, false);

Browser Support

Web SQL Database

Web SQL Database
  • Client-side SQL database
  • Asynchronous & Synchronous* API
  • Basically wrapper around SQLite
  • Put processing (sorting, filtering, etc.) on client
openDatabase();
var db = window.openDatabase(
  'MyDB',           // dbName
  '1.0',            // version
  'test database',  // description
  2 * 1024 * 1024,  // estimatedSize in bytes
  function(db) {}   // optional creationCallback
);
openDatabase();
  • Size ( required )
    • Default: 5MB
    • Prompted after that: 5, 10, 50, 100, etc.
  • Versioning ( required )
    • Expected version - empty string means any version is fine.
    • Exception if version mismatch
  • Creation Callback ( optional )
    • Called if database is brand new
    • For updating version and/or schema:
      db.changeVersion('', '1.0', function (tx) { // opt
        // TODO: create schema
      }, opt_errorCallback, opt_successCallback);
  • Returns null if creation is unsuccessful
Transactions
  • readTransaction
    • read-only operations
    • db.readTransaction(function(tx) {
        // executeSql
      }, opt_errorCallback, opt_successCallback);
      
  • transaction
    • read/write operations
    • Locks the entire database
    • db.transaction(function(tx) {
        // executeSql
      }, opt_errorCallback, opt_successCallback);
      
executeSql();
tx.executeSql(sqlStatement, opt_arguments, opt_callback, opt_errorCb);

Create:

tx.executeSql('CREATE TABLE IF NOT EXISTS ' +
    'todo(id INTEGER PRIMARY KEY ASC, task TEXT, added_on DATETIME)');

Insert:

var taskText = 'buy groceries';
tx.executeSql(
  'INSERT INTO todo(task, added_on) VALUES (?,?)', [taskText, new Date()],
  renderFunc, onError);

Delete:

tx.executeSql('DELETE FROM todo WHERE id=?', [id], renderFunc, onError);
executeSql();
tx.executeSql(sqlStatement, opt_arguments, opt_callback, opt_errorCb);

Query:

function renderFunc(tx, results) {
  // results.insertId: last row inserted in db.
  // results.rowsAffected: num of rows changed by the SQL statement.
  var len = results.rows.length;
  for (var i = 0; i < len; ++i) {
    var row = results.rows.item(i);
    console.log(row.task, row.added_on.toJSON());
  }
}

tx.executeSql('SELECT * FROM todo', [], renderFunc, onError);
Example: Todo List

    See the generated database:
    Developer > Developer Tools > Resources

    Browser Support

    Indexed Database

    http://www.flickr.com/photos/31408547@N06/4671916278
    IndexedDB

    window.indexedDB

    • Best of localStorage/sessionStorage and Web SQL DB:
      • Object based data store
      • In-order retrieval by index or locate by key
    • Asynchronous & Synchronous API
      • For the browser and for Web Workers
    Creating an Object Store
    var db = window.indexedDB.open('FriendDB', 'My Friends!');
    
    if (db.version != '1') {
      // User's first visit, initialize database.
      db.createObjectStore('Friends', // name of new object store
                            'id',      // key path
                            true);     // auto increment?
      db.setVersion('1');
    } else {
      // DB already initialized.
    }
    
    • Key path must be the name of an enumerated property of all objects being stored in the object store
    • DB versioning up to caller
    Stocking the Store
    var store = db.openObjectStore('Friends');
    
    var user = store.put({name: 'Eric',
                           gender: 'male',
                           likes: 'html5'});
    
    console.log(user.id); // Expect 1
    
    • Auto-increment keys get assigned automatically
    • Schema flexible, store anything
    Finding things

    Retrieving by key ( indexes ):

    db.createIndex(indexName, keyPath, unique?);
    // 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);
    }
    
    Demo
    var idbRequest = window.indexedDB.open('Database Name');
    idbRequest.onsuccess = function(e) {
      var db = this.result;
      var transaction = db.transaction(
          ['ObjectStoreName'],IDBTransaction.READ_ONLY);
      var curRequest = transaction.objectStore('ObjectStoreName').openCursor();
      curRequest.onsuccess = ...;
    };
    

    Browser Support
    Important Issues
    • Web Database vs. IndexedDB
    • Schema migration
    • Private browsing
    • User clearing private data
    • Same domain policy

    Application Cache

    http://www.flickr.com/photos/jtyerse/3954240271/
    Application Cache
    • Caches entire web app locally!
    • Why?
      1. HTML, CSS, and JS stay fairly consistent
      2. Native browser caching is unreliable
      3. Caching resources creates speedier apps!
        • Used by iPhone & Android GMail app
    • Decent mobile support
    Cache Manifest File
    <html manifest="example.appcache">... </html>
    
    CACHE MANIFEST
    # 2010-11-17-v0.0.1
    
    # Explicitly cached entries
    CACHE:
    index.html
    stylesheet.css
    images/logo.png
    http://img.example.com/logo2.png
    scripts/main.js
    
    # static.html will be served if the user is offline
    FALLBACK:
    / /static.html
    
    # Resources that require the user to be online.
    NETWORK:
    *
    # login.php, http://api.twitter.com, etc.
    

    Server with mime-type: text/cache-manifest

    JavaScript API
    var cache = window.applicationCache;
    
    cache.addEventListener('cached', handleCacheEvent, false);
    cache.addEventListener('checking', handleCacheEvent, false);
    cache.addEventListener('downloading', handleCacheEvent, false);
    cache.addEventListener('error', handleCacheError, false);
    cache.addEventListener('noupdate', handleCacheEvent, false);
    cache.addEventListener('obsolete', handleCacheEvent, false);
    cache.addEventListener('progress', handleCacheEvent, false);
    cache.addEventListener('updateready', handleCacheEvent, false);
    
    // When a new manifest is successfully downloaded, swap the new cache and reload page.
    cache.addEventListener('updateready', function(e) {
      if (cache.status == cache.UPDATEREADY) {
        cache.swapCache();
        if (confirm('A new version of this site is available. Load it?')) {
          window.location.reload();
        }
      }
    }, false);
    
    Debugging AppCache

    about:appcache-internals

    Browser Support

    File APIs

    http://www.flickr.com/photos/daddo83/3406962115/
    File APIs
    Getting File Handles: File / FileList
    <input type="file" id="files" name="files[]" multiple />
    <output id="list"></output>
    
    document.querySelector('#files').onchange = function(e) {
      var files = e.target.files; // FileList
    
      var output = [];
      for (var i = 0, f; f = files[i]; ++i) {
        output.push('<li><b>', f.name, '</b> (', f.type || 'n/a',
            ') - ', f.size, ' bytes, last modified: ',
            f.lastModifiedDate.toLocaleDateString(), '</li>');
      }
      document.querySelector('#list').innerHTML =
          '<ul>' + output.join('') + '</ul>';
    };
    

    Getting File Handles: Directory Upload
    <input type="file" webkitdirectory />
    • Files in the FileList will have a webkitRelativePath property
    FileReader

    Asynchronously read binary data into memory: Demo

    var reader = new FileReader();
    
    reader.readAsBinaryString(File|Blob);
    
    reader.readAsText(File|Blob, opt_encoding); // default UTF-8
    
    reader.readAsDataURL(Blob|File);
    
    reader.readAsArrayBuffer(Blob|File);

    Reading byte ranges: Demo

    var blob = file.slice(startingByte, length); // Blob
    reader.readAsBinaryString(blob);
    Example: Drag & Drop Files
    var reader = new FileReader();
    reader.onload = function(e) {
      document.querySelector('img').src = e.target.result;
    };
     
    function onDrop(e) {
      reader.readAsDataURL(e.dataTransfer.files[0]);
    };
    Drop image files from your desktop
    Monitoring a Read
    reader.onerror = errorHandler;
    
    reader.onprogress = updateProgress;
    
    reader.onabort = cancelRead;
    
    reader.onloadstart = updateProgress;
    
    reader.onloadend = function(e) {
      // e.target.readyState == FileReader.DONE == 2
    };
    
    reader.onload = readFile;

    Demo

    Blob URLs
    A unique (temporary) handle describing a resource:
    if (file.type.match(/image.*/)) {
      var blobURL = window.URL.createObjectURL(file);
    
      var img = document.createElement('img');
      img.src = blobURL; // blob:blobinternal:///d8c2c85e-ab1b-4b7b3d37bff6
      thumbnails.appendChild(img);
    
      // Clean-up: window.URL.revokeObjectURL(img.src);
    }
    
    var bb = new BlobBuilder();
    bb.append('<marquee>Flashy Lights!!!</marquee>');
    
    var iframe = document.createElement('iframe');
    iframe.src = window.URL.createObjectURL(bb.getBlob('text/html'));
    document.body.appendChild(iframe);
    
    Example: Inline Worker
    <!DOCTYPE html>
    <html>
    <body>
      <script id="worker1" type="text/plain">
        onmessage = function(e) {
          ...
        };
      </script>
      <script>
        var bb = new BlobBuilder();
        bb.append(document.querySelector('#worker1').textContent);
    
        var worker = new Worker(
            window.URL.createObjectURL(bb.getBlob()));
        worker.onmessage = function(e) {
          ...
        };
        worker.postMessage(); // Start the worker.
      </script>
    </body>
    </html>
    
    Browser Support

    FileSystem APIs

    http://www.flickr.com/photos/redjar/113152393/
    requestFileSystem();
    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
    );
    • TEMPORARY storage
      • Meant for caching
      • Data can be deleted at the UA's convenience
      • (e.g. to deal with a shortage of disk space)
    • PERSISTENT storage
      • UA should require permission to use
      • Data should not be deleted by UA w/o user intervention
        • ...but app can delete at will
    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) { /* TODO: Use FileReader */ }, errorHandler);
    
        // fileEntry.remove(function() {}, errorHandler);
        // fileEntry.moveTo(...);
        // fileEntry.copyTo(...);
        // fileEntry.getParent(function(dirEntry) {}, errorHandler);
    
      }, errorHandler);
    
    }
    
    Fetching a directory by name
    function initFs(fs) {
    
      fs.root.getDirectory('myFolder', {create: true, exclusive: true},
        function (dirEntry) {
    
          // dirEntry.isDirectory == true
    
          // Remove the directory
          dirEntry.removeRecursively(function() {
            console.log('Well, that was pointless!');
          }, errorHandler);
    
          // dirEntry.getFile(...);
          // dirEntry.getDirectory(...);
    
        }, errorHandler);
    
    }
    
    Reading directory contents
    function initFs(fs) {
    
      var root = fs.root;  // A DirectoryEntry itself
    
      var dirReader = root.createReader(); // DirectoryReader
      dirReader.readEntries(function(entries) {
        for (var i = 0, entry; entry = entries[i]; ++i) {
          console.log(entry.name, entry.isFile, entry.isDirectory);
        }
      }, errorHandler);
    
    }
    
    Writing a file ( FileWriter )
    function initFs(fs) {
    
      fs.root.getFile('logFile.txt', {create: true}, function(fileEntry) {
    
        fileEntry.createWriter(function(writer) {  // FileWriter
    
            writer.onwrite = function(e) {
              console.log('Write completed.');
            };
    
            writer.onerror = function(e) {
              console.log('Write failed: ' + e);
            };
    
            var bb = new BlobBuilder();
            bb.append('Lorem ipsum');
            writer.write(bb.getBlob('text/plain'));
    
        }, errorHandler);
      }
    
    }
    
    Browser Support

    Google Chrome Frame

    Telling IE to render with Chrome Frame
    1. Add a single meta tag to your site:
      • if !installed: direct users to download
      • else: your site 'just works'
      • <meta http-equiv="X-UA-Compatible" content="chrome=1">
    2. Add a response header:
      • X-UA-Compatible: chrome=1
    Modernizr.com
    Detecting Support
    if (Modernizr.localstorage) { ... }
    if (Modernizr.sessionstorage) { ... }
    if (Modernizr.websqldatabase) { ... }
    if (Modernizr.indexeddb) { ... }
    if (Modernizr.applicationcache) { ... }
    
    Modernizr.addTest('file', function() {
      return !!window.File && !!window.FileList;
    });
    Modernizr.addTest('filereader', function() {
      return !!window.FileReader;
    });
    Modernizr.addTest('blob', function() {
      return !!window.Blob && !!window.BlobBuilder &&
             !!window.URL.createObjectURL;
    });
    Modernizr.addTest('filesystem', function() {
      return !!window.requestFileSystem;
    });
    
    caniuse.com
    html5rocks.com

    Thanks!

    Questions?