by Diego Calderón / @codekult
Christian Heilmann. The next UX challenge on the web
We need to think about the advantages and problems of this approach and set a few things to focus on, in order to create a better user experience.
save vs sync. Offline-first advantages
localStorage
and sessionStorage
are both instances of the Storage
object, and function in a similar way, saving data based on named key/value pairs.
localStorage
persists when the browser is closed and reopened, in turn sessionStorage
endures for the duration of the page session.
Set data:
localStorage.setItem('color','beige');
Get data:
localStorage.getItem('color');
Remove/clear data:
localStorage.removeItem('color');
localStorage.clear(); // Clear all
All the data in web storage is saved as strings
We can work around this using JSON.stringify()
and JSON.parse()
methods for objects:
localStorage.setItem('myObj', JSON.stringify(myObj));
JSON.parse(localStorage.getItem('myObj'));
Or coerce the data into the expected JavaScript datatype:
var qty = parseInt(localStorage.getItem('qty'));
We can track when storage area changes using the storage
event, which is fired on window
object whenever setItem()
, removeItem()
, or clear()
is called and actually changes something.
StorageEvent
objectkey
: The changed key.oldValue
: Previous value (now overwritten), or `null` if a new item was added.newValue
: New value, or `null if an item was removed.url
: The page which called a method that triggered this change.IndexedDB is a transactional, object-oriented and noSQL database hosted and persisted in the browser.
First, some preliminal concepts:
Each database has a name and a current version, which starts at 1
but can be specified.
A database can have only version at a given time and we can change it by open the database with a greater number version.
This will start a versionchange
transaction and fire an upgradeneeded
event, and its handler is the only place where the schema of the database can be updated.
A database is composed of one or more:
The mechanism by which data is stored in the database. Holds key-value pair records. Those records are sorted according to the keys in an ascending order.
Must have an unique name within the database and can have a key generator, a key path, both, or none of them.
This is how we interact with the data in a database, everything we change happens in the context of a transaction.
There are three modes of transactions: readWrite
, readOnly
and versionChange
.
A database connection can have multiple transactions at a a time, so long as the transactions don't have overlapping scopes.
var db,
request = indexedDB.open("frontendDatabase");
request.onerror = function (event) {
alert("Error");
};
request.onsuccess = function (event) {
db = event.target.result;
};
This will return an special type of request (IDBOpenDBRequest
) with an error, or a database as result if success.
If the database doesn't exists or if the database exists but and greater version number is specified, an onupgradeneeded
event is fired. We can create the schema for the new database or update the existent one in the onupgradeneeded
event handler.
request.onupgradeneeded = function (event) {
var db = event.target.result;
// Create an objectStore for this database
var objectStore = db.createObjectStore("devs", { keyPath: "dni" });
};
var objectStore = db.createObjectStore("devs", { autoIncrement : true });
Created Object stores persist through versions. Here we can create new object stores, delete unnecesary ones, or change an existing object store (deleting the old and creating a new one, saving the necessary data).
If the onupgradeneeded
event exits successfully, the onsuccess
handler of the open database request will be triggered.
Open a transaction:
var transaction = db.transaction(["devs"], "readwrite");
Transactions can receive DOM events of three different types:error
, abort
, and complete
.
transaction.oncomplete = function (event) {
alert("Done!");
};
transaction.onerror = function (event) {
// Handle errors
};
var objectStore = transaction.objectStore("devs");
for (var i in devData) {
var request = objectStore.add(devData[i]);
request.onsuccess = function (event) {
// event.target.result == devData[i].dni;
};
}
Methods to manipulate data in object stores are:add()
, clear()
, delete()
, get()
, and put()
.
Shorthand example:
db.transaction("devs")
.objectStore("devs")
.get("33222111")
.onsuccess = function (event) {
alert("Name for DNI 33222111 is " + event.target.result.name);
};
An index is a specialized object store for looking up records in another object store, called the referenced object store.
var objectStore = db.createObjectStore("devs", { keyPath: "dni" }),
// Create an index to search devs by name. We may have duplicates
// so we can't use a unique index.
index = objectStore.createIndex("name", "name", { unique: false });
index.get("Alex").onsuccess = function (event) {
alert("Alex's DNI is " + event.target.result.dni);
};
A cursor is a mechanism for iterating over multiple records,
with an optional key range.
var devs = [];
objectStore.openCursor().onsuccess = function(event) {
var cursor = event.target.result;
if (cursor) {
devs(cursor.value);
cursor.continue();
} else {
alert("All devs: " + devs);
}
};
We can build a key range with:only()
, upperBound()
, lowerBound()
, bound()
.
Bounds methods accept a boolean parameter to specify is the bounds is included in the cursor (`false` include and `true` doesn't). Example:
var keyRangeExample = IDBKeyRange.bound("33222111", "34333222", true, true);
Allows a developer to specify which files the browser should cache and make available to offline users.
To enable the application cache for an app, include the manifest attribute on the document's html tag:
manifest="example.appcache"
The manifest attribute should be included on every page of your web application that you want cached.
A manifest file must be served with the mime-type text/cache-manifest in order to to work in older browsers and IE11.
You may need to add a custom file type to your web server or .htaccess
configuration.
A simple manifest looks something like this:
CACHE MANIFEST
index.html
stylesheet.css
images/logo.png
scripts/main.js
http://cdn.example.com/scripts/main.js
And a more complex example:
CACHE MANIFEST
# 2010-06-18:v2
# Explicitly cached 'master entries'.
CACHE:
/favicon.ico
index.html
stylesheet.css
images/logo.png
scripts/main.js
# Resources that require the user to be online.
NETWORK:
*
# static.html will be served if main.py is inaccessible
# offline.jpg will be served in place of all images in images/large/
# offline.html will be served in place of all other .html files
FALLBACK:
/main.py /static.html
images/large/ images/offline.jpg
A manifest can have three distinct sections:
This is the default section for entries. Files listed under this header (or immediately after the CACHE MANIFEST
) will be explicitly cached after they're downloaded for the first time.
Files listed in this section may come from the network if they aren't in the cache, otherwise the network isn't used, even if the user is online. You can white-list specific URLs here, or simply *
, which allows all URLs. Most sites need *
.
An optional section specifying fallback pages if a resource is inaccessible. The first URI is the resource, the second is the fallback used if the network request fails or errors. Both URIs must from the same origin as the manifest file. You can capture specific URLs but also URL prefixes. images/large/
will capture failures from URLs such as images/large/whatever/img.jpg
.
Once an application is offline it remains cached until one of the following happens:
Jake Archibald. Application Cache is a Douchebag
Especially for SPAs.
Service Worker is a kind of Web Worker. It runs in the browser's background, separate from the web page and has the ability to intercept and handle network requests, including programmatically managing a cache of responses.
It cannot access the DOM. Instead, it will use postMessage
to communicate with the pages and make extensive use of promises.
But this is subject of another talk ¯\_(ツ)_/¯
FileSystem API, push notifications, backgroundSync;
and third-parties tools: Hoodie, PouchDB, and so on…
Appcache and Service Worker
Web storage, IndexedDB. Or something like PouchDB or Hoodie.