Taking Web Apps Offline
- 2. Offline webapps - why?
no network connection required
faster startup
keep data local
- 6. localstorage
Simple API
window.localStorage[‘hello’] = ‘world’;
- 7. localStorage
Can store a JSON string
window.localStorage[‘helloJSON’] =
JSON.stringify({‘key1’: ‘world’,
‘key2’: ‘Lisbon’});
- 8. 92.25% http://caniuse.com/indexeddb
- 9. localStorage limitations
Storage Limits
Spec - 5 Mb
Firefox - can adjusted
Chrome - 2600k chars (5 mb in UTF-16)
IE - 5000k chars
- 12. sql storage
work stopped November ’10
sqlite was used in all implementations
(webkit, opera)
mozilla & microsoft: “not gonna happen”
- 13. 48.32% http://caniuse.com/sql-storage
- 16. indexed db
Async API
var request = indexedDB.doStuff();
request.onerror = function(event) {
...
};
request.onsuccess = function(event) {
...
};
- 17. indexed db
Opening a database
var request = indexedDB.open("MyTestDatabase");
request.onerror = ...
var db;
request.onsuccess = function(event) {
db = request.result;
};
- 18. creating the schema
var request = indexedDB.open("MyTestDatabase", 5);
...
request.onupgradeneeded = function(event) {
var db = event.target.result;
...
};
- 19. creating the schema
# student: cardNumber, name, email, ...
request.onupgradeneeded = function(event) {
var db = event.target.result;
var studentsStore = db.createObjectStore("students",
{ keyPath: "cardNumber" });
studentsStore.createIndex("nameIndex", "name",
{ unique: false });
studentsStore.createIndex("emailIndex", "email",
{ unique: true });
};
- 20. adding data
var transaction
= db.transaction(["students"], "readwrite");
transaction.oncomplete = function(event) {
alert("All done!");
};
...
// transactions go away when you
// return to the event loop
// without making a request
- 21. adding data
...
var objectStore = transaction.objectStore("customers");
aStudent = { ‘studentCard’: ‘44124’,
‘name’: ‘Pedro Morais’,
‘email’: ‘pedro@morais.it'}
var request = objectStore.add(aStudent);
request.onsuccess = function(event) {
// event.target.result == aStuddent.studentCard
};
- 22. updating data
...
var objectStore = transaction.objectStore("customers");
aStudent = { ‘studentCard’: ‘44124’,
‘name’: ‘Pedro Morais with updated name’,
‘email’: ‘pedro@morais.it'}
var request = objectStore.put(aStudent);
request.onsuccess = function(event) {
// event.target.result == aStuddent.studentCard
};
- 23. deleting data
...
var objectStore = transaction.objectStore("customers");
var request = objectStore.delete(‘44124’);
request.onsuccess = function(event) {
// deleted
};
- 24. getting data
var transaction = db.transaction(["students"]);
var objectStore = transaction.objectStore("customers");
var request = objectStore.get(‘44124’);
request.onsuccess = function(event) {
console.log(“Name is “, request.result.name);
};
- 25. using a cursor
....
var request = objectStore.openCursor();
request.onsuccess = function(event) {
var c = event.target.result;
if (c) {
console.log
("Student " + c + " is named " + c.value.name);
cursor.continue();
} else {
console.log("Done!");
}
};
- 26. using an index
...
var index = objectStore.index("name");
index.get("Pedro Morais").onsuccess = function(event) {
console.log("Email=" + event.target.result.email);
};
// if name is not unique
// you get the first entry that matches
- 27. using an index + cursor
...
var index = objectStore.index("name");
var request
= index.openCursor(IDBKeyRange.only("Pedro Morais"));
request.onsuccess = function(event) {
var c = event.target.result;
if (c) {
// c.key is a name, like "Pedro Morais",
// c.value is the whole object.
console.log("Name: " + cursor.key +
“Email: " + cursor.value.email);
cursor.continue();
}
};
- 28. cursor key ranges
IDBKeyRange.only("Pedro Morais")
IDBKeyRange.lowerBound("Pedro")
IDBKeyRange.lowerBound("Pedro", true) // don’t inc Pedro
IDBKeyRange.upperBound("Pedro", true) // don’t inc Pedro
IDBKeyRange.bound("Pedro", "Vanda", false, true);
// include Pedro, don’t include Vanda
- 29. 46.93% http://caniuse.com/indexeddb
- 32. 67.43% http://caniuse.com/offline-apps
- 33. offline web apps
<!DOCTYPE html>
<html manifest=”cache.appcache”>
...
cache.appcache must be served as
text/cache-manifest
- 35. cache with fallback
CACHE MANIFEST
# this is a comment
css/styles.css
js/scripts.js
images/logo.png
FALLBACK:
/ /offline.html
NETWORK:
*
- 36. network access
not using appcache
• user navigates to http://test.com/app.html
• browser check if file “app.html” is in cache
• browser check if it has not expired
• browser checks validity of file using etags (optional)
• browser renders the cached or downloaded app.html
- 37. network access
using appcache
• user navigates to http://test.com/app.html
• browser renders app.html from appcache
• in the background:
• browser checks if manifest has changed
• if it has, browser downloads all files in the manifest
(expires, etags still apply)
• user only gets new version of app.html the next time!
- 39. cache manifest versions
CACHE MANIFEST
css/styles.css?b3c4de
js/scripts.js?adf341
images/logo.png?ef3451
NETWORK:
*
+ far future expires
- 40. cache manifest versions
CACHE MANIFEST
b3c4de/css/styles.css
adf341/js/scripts.js
ef3451/images/logo.png
NETWORK:
*
+ far future expires
- 42. Available today
localStorage
Indexed DB (+ polyfill)
AppCache
Take your web apps offline
- 43. Thanks!
pedro@morais.it
@pedromorais
GDG Portugal DevFest morais on app.net
March ’13