Stephan Hochdörfer // October 22 2015
<!DOCTYPE html> <html lang="en">
<!DOCTYPE html> <html lang="en" manifest="cache.manifest">
CACHE MANIFEST # 2015-10-22 js/app.js css/app.css favicon.ico http://someotherdomain.com/image.png
CACHE MANIFEST # 2015-10-22 NETWORK: data.php CACHE: /main/home /main/app.js /settings/home /settings/app.js http://myhost/logo.png http://myhost/check.png http://myhost/cross.png
CACHE MANIFEST # 2015-10-22 FALLBACK: / /offline.html NETWORK: *
// Check if a new cache version is available window.addEventListener('load', function(e) { window.applicationCache.addEventListener('updateready', function(e) { if(window.applicationCache.status == window.applicationCache.UPDATEREADY) { window.applicationCache.swapCache(); if (confirm('New version is available. Load it?')) { window.location.reload(); } } }, false); }, false);
Files are always(!) served from the application cache.
The application cache only updates if the
content of the manifest itself has changed!
If any of the files listed in the CACHE section can't be retrieved, the entire cache will be disregarded.
If the manifest file itself can't be retrieved,
the cache will ignored!
Non-cached resources will not load on a cached page!
The page needs to be reloaded,
otherwise the new resources do not show up!
To avoid the risk of caching
manifest files set expires headers!
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/worker.js').then(function(reg) { // Registration was successful }).catch(function(err) { // Registration failed }); }
var CACHE_NAME = 'my-site-cache-v1'; var urlsToCache = [ '/', '/styles/main.css', '/script/main.js' ]; self.addEventListener('install', function(event) { event.waitUntil( caches.open(CACHE_NAME) .then(function(cache) { return cache.addAll(urlsToCache); }) ); });
self.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request) .then(function(response) { if (response) { return response; } return fetch(event.request); } ) ); });
Very convenient form of offline storage:
simple key-value store
localStorage vs. sessionStorage
function add(item) { try { // for a new item set id if((typeof item.id === "undefined") || (null == item.id) || ("" == item.id)) { item.id = get_lastIndex() + 1; } // store object as string localStorage.setItem(item.id, JSON.stringify(item) ); // update the index set_lastIndex(item.id); } catch(ex) { console.log(ex); } }
function modify(item) { try { // store object as string localStorage.setItem(item.id, JSON.stringify(item) ); } catch(ex) { console.log(ex); } }
function remove (id) { try { localStorage.removeItem(id); } catch(ex) { console.log(ex); } }
function read() { try { var lastIdx = get_lastIndex(); for(var i = 1; i <= lastIdx; i++) { if(null !== localStorage.getItem(i)) { // parse and render item var item = JSON.parse( localStorage.getItem(i) ); } } } catch(ex) { console.log(ex); } }
An offline SQL database based on SQLite,
a general-purpose SQL engine.
var onError = function(tx, ex) { alert("Error: " + ex.message); }; var onSuccess = function(tx, results) { var len = results.rows.length; for(var i = 0; i < len; i++) { // render found todo item render(results.rows.item(i)); } };
// initalize the database connection var db = openDatabase('todo', '1.0', 'Todo Database', 5 * 1024 * 1024 ); db.transaction(function (tx) { tx.executeSql( 'CREATE TABLE IF NOT EXISTS todo '+ '(id INTEGER PRIMARY KEY ASC, todo TEXT)', [], onSuccess, onError ); });
function m1(t){ t.executeSql("create table tbl1...") } function m2(t){ t.executeSql("alter table tbl1...") } function m3(t){ t.executeSql("alter table tbl1...") } if(db.version == "") { db.changeVersion("", "1", m1, null, function() { db.changeVersion("1", "2", m2, null, function() { db.changeVersion("2", "3", m3); }); }); } if(db.version == "1") { db.changeVersion("1", "2", m2, null, function() { db.changeVersion("2", "3", m3); }); } if(db.version == "2") { db.changeVersion("2", "3", m3); }
function add(item) { db.transaction(function(tx) { tx.executeSql( 'INSERT INTO todo (todo) VALUES (?)', [ item.todo ], onSuccess, onError ); }); }
function modify(item) { db.transaction(function(tx) { tx.executeSql( 'UPDATE todo SET todo = ? WHERE id = ?', [ item.todo item.id ], onSuccess, onError ); }); }
function remove(id) { db.transaction(function (tx) { tx.executeSql( 'DELETE FROM todo WHERE id = ?', [ id ], onSuccess, onError ); }); }
function read() { db.transaction(function (tx) { tx.executeSql( 'SELECT * FROM todo', [], onSuccess, onError ); }); }
A nice compromise between Web Storage
and Web SQL Database giving you the
best
of both worlds.
// different browsers, different naming conventions var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB; var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction; var IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange;
var db = null; var request = indexedDB.open("todo"); request.onfailure = onError; request.onsuccess = function(e) { db = request.result; var v = "1.0"; if(v != db.version) { var verRequest = db.setVersion(v); verRequest.onfailure = onError; verRequest.onsuccess = function(e) { var store = db.createObjectStore("todo", { keyPath: "id", autoIncrement: true }); e.target.transaction.oncomplete = function() {}; }; } };
function add(item) { try { var trans = db.transaction(["todo"], IDBTransaction.READ_WRITE); var store = trans.objectStore("todo"); var request = store.put({ "todo": item.todo, }); } catch(ex) { onError(ex); } }
function modify(item) { try { var trans = db.transaction(["todo"], IDBTransaction.READ_WRITE); var store = trans.objectStore("todo"); var request = store.put(item); } catch(ex) { onError(ex); } }
function remove(id) { try { var trans = db.transaction(["todo"], IDBTransaction.READ_WRITE); var store = trans.objectStore("todo"); var request = store.delete(id); } catch(ex) { onError(ex); } }
function read () { try { var trans = db.transaction(["todo"], IDBTransaction.READ); var store = trans.objectStore("todo"); var keyRange = IDBKeyRange.lowerBound(0); var cursorRequest = store.openCursor(keyRange); cursorRequest.onsuccess = function(e) { var result = e.target.result; if(!!result == false) { return; } // @TODO: render result.value result.continue(); }; } catch(ex) { onError(ex); } }
try { var index = db.openIndex('todo'); var item = index.get(1337); } catch(ex) { onError(ex); }
try { // create non-unique index db.createIndex("Created", "createdDate", {"unique": false}); // create unique index db.createIndex("OtherKey", "otherField", {"unique": true}); // create multi-column index (not working in IE10!) db.createIndex("MultiIndex", ["field1", "field2"]); } catch(ex) { onError(ex); }
var onError = function(e) { var msg = ''; switch(e.code) { case FileError.QUOTA_EXCEEDED_ERR: msg = 'QUOTA_EXCEEDED_ERR'; break; case FileError.NOT_FOUND_ERR: msg = 'NOT_FOUND_ERR'; break; case FileError.SECURITY_ERR: msg = 'SECURITY_ERR'; break; case FileError.INVALID_MODIFICATION_ERR: msg = 'INVALID_MODIFICATION_ERR'; break; case FileError.INVALID_STATE_ERR: msg = 'INVALID_STATE_ERR'; break; default: msg = 'Unknown Error'; break; }; alert("Error: " + msg); };
// File system has been prefixed as of Google Chrome 12 window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder; var size = 5 * 1024*1024; // 5MB
// request quota for persistent store window.webkitStorageInfo.requestQuota( PERSISTENT, size, function(grantedBytes) { window.requestFileSystem( PERSISTENT, grantedBytes, function(fs) { // @TODO: access filesystem } } } }
function add(item) { window.webkitStorageInfo.requestQuota( PERSISTENT, size, function(grantedBytes) { window.requestFileSystem( PERSISTENT, grantedBytes, function(fs) { writeToFile(fs, item); }, onError ); }, function(e) { onError(e); } ); }
function writeToFile(fs, item) { fs.root.getFile( 'todo.txt', { create: true }, function(fileEntry) { fileEntry.createWriter( function(fileWriter) { var blob = new Blob([JSON.stringify(item)+"\n"]); fileWriter.seek(fileWriter.length); fileWriter.write(blob); }, onError ); }, onError ); }
function read() { window.webkitStorageInfo.requestQuota( PERSISTENT, size, function(grantedBytes) { window.requestFileSystem( PERSISTENT, grantedBytes, function(fs){ readFromFile(fs); }, onError ); }, function(e) { onError(e); } ); }
function readFromFile(fs) { fs.root.getFile( 'todo.txt', { create: true }, function(fileEntry) { fileEntry.file(function(file){ var reader = new FileReader(); reader.onloadend = function(e) { if (evt.target.readyState == FileReader.DONE) { // process this.result } }; reader.readAsText(file); }); }, onError ); }
Web Storage | Web SQL DB | IndexedDB | File API | |
IE | 8.0 | 10.0 | 10.0 | - |
Firefox | 11.0 | 11.0 | 11.0 | 19.0 |
Chrome | 18.0 | 18.0 | 18.0 | 18 |
Safari | 5.1 | 5.1 | - | - |
iOS | 3.2 | 3.2 | - | - |
Android | 2.1 | 2.1 | - | - |
Web Storage | Web SQL DB | IndexedDB | File API | |
IE | 10 MB | 500 MB | 500 MB | |
Firefox | 10 MB | 50 MB | 50 MB | |
Chrome | 5 MB | ∞ | ∞ | ∞ |
Safari | 5 MB | 5 MB | 5 MB | |
iOS | 5 MB | 5 MB | 5 MB | |
Android | 5 MB | ? | ? |
document.body.addEventListener("online", function () { // browser is online! } document.body.addEventListener("offline", function () { // browser is not online! }
$.ajax({ dataType: 'json', url: 'http://myapp.com/ping', success: function(data){ // ping worked }, error: function() { // ping failed -> Server not reachable } });
PouchDB, the JavaScript Database that syncs!
var db = new PouchDB('todo'); db.put({ _id: 1, todo: 'Get some work done...', }); db.replicate.to('http://myapp.com/mydb');
Thank you! Questions?
Do not forget to rate the talk:
https://joind.in/15625