Stephan Hochdörfer // 28.06.2014
<!DOCTYPE html> <html lang="en">
<!DOCTYPE html> <html lang="en" manifest="cache.manifest">
CACHE MANIFEST #2011-13-14 js/app.js css/app.css favicon.ico http://someotherdomain.com/image.png
CACHE MANIFEST # 2013-11-14 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 # 2013-11-14 FALLBACK: / /offline.html NETWORK: *
// events fired by window.applicationCache window.applicationCache.onchecking = function(e) { log("Checking for updates"); } window.applicationCache.onnoupdate = function(e) { log("No updates"); } window.applicationCache.onupdateready = function(e) { log("Update ready"); } window.applicationCache.onobsolete = function(e) { log("Obsolete"); }
window.applicationCache.ondownloading = function(e) { log("Downloading"); } window.applicationCache.oncached = function(e) { log("Cached"); } window.applicationCache.onerror = function(e) { log("Error"); } window.applicationCache.onprogress = function(e) { log("Progress: Downloading file " + counter++); };
// 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!
<!DOCTYPE HTML> <html> <head> <title>The Data URI scheme</title> <style type="text/css"> ul.checklist li { margin-left: 20px; background: white url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==') no-repeat scroll left top; } </style> </head> <body> </body> </html>
<!DOCTYPE HTML> <html> <head> <title>The Data URI scheme</title> </head> <body> <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red dot"> </body> </html>
Data URIs are not separately cached from their containing documents. The encoded data is downloaded every time the containing documents are re-downloaded.
Content must be re-encoded and re-embedded
every time a change is made.
Internet Explorer 7 lacks support.
Internet Explorer 8 limits the data size to 32K.
Base64-encoded data URIs are 1/3 times
larger in size than their binary equivalent.
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); } }
Replace „localStorage“ with „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 sessionStorage.setItem(item.id, JSON.stringify(item) ); // update the index set_lastIndex(item.id); } catch(ex) { console.log(ex); } }
var value = "my value"; // method call localStorage.setItem("key", value); // Array accessor localStorage[key] = value; // Property accessor localStorage.key = value;
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); }
Use db.js as a wrapper!
var server; db.open( { server: 'todo', version: 1, schema: { todo: { key: { keyPath: 'id' , autoIncrement: true}, indexes: { Created: { } } } } }).done(function(s) { server = s; });
server.todo.add( { todo: 'This is a sample todo item' }).done(function(item) { // item stored });
server.todo.query() .execute() .done(function(results) { // do something with the results });
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!
Do not forget to rate the talk:
https://joind.in/10903