7. Client Storage
Working offline is great, but there’s a problem: where
should a web application store vital statistics and other information
when the device is not connected to the Internet? And if the device is
not connected to the Internet, how can our applications access helpful
databases or information? Client storage solutions come to our
assistance, in two flavors: key/value storage and SQL databases (yes,
from JavaScript, without server interaction).
Of course, we also have cookies, but they are simpler (only string
storage), and we know they are not guaranteed to survive in the
browser.
7.1. Key/value storage
HTML 5 defines a key/value store through two objects:
localStorage and sessionStorage. They are pretty much the
same, but the scopes are different: while the local store is used for
long-term storage, the session store doesn’t persist after the user
closes the tab or window.
Note:
We should use try/catch blocks when saving items, in case
problems occur with the storage or the maximum available space is
exceeded.
Both stores are used in the same way:
// Save an object or variable in the store
localStorage.setItem("name_in_the_storage", object_to_store);
// Read an object from the store
var object = localStorage.getItem("name_in_the_storage");
We can also delete all the objects using clear or delete one key using removeItem. There is also a storage event that we can listen for with
window.addEventListener that will
be fired when the contents of the local or session stores have
changed.
7.2. SQL database
Having a relational database available in JavaScript
sounds powerful. The main method that defines the availability of the
JavaScript SQL database is the window.openDatabase method. This method has
the following signature:
var db = window.openDatabase(shortName, version, displayName, sizeExpectable);
If we use a try/catch, we can capture errors during the
operation. This method opens the database if it exists and creates it
if it is the first time we’ve used it. To execute non-recordset
sentences (CREATE TABLE, INSERT, etc.) we can use a transaction using
the transact method, which receives
a function as parameter. As a transaction, if one sentence fails, the
others will not execute:db.transact(function(t)) {
t.executeSql('CREATE TABLE countries (id INTEGER NOT NULL PRIMARY KEY
AUTOINCREMENT, name TEXT NOT NULL)', [], function() {}, errorHandler);
});
The array parameter after the query string is an array of
parameters to be replaced in the query (using ? inside), the next parameter is the data
handler function (not used in a non-recordset query), and the last
parameter is a function handler for errors in the query.
Note:
HTML 5 doesn’t define which database engine should be used.
Mobile browsers use SQLite, the open source database engine, so
check the SQLite documentation for data type and SQL syntax
support.
To create a typical SELECT
statement with recordset looping, we can use the following
template:
db.transact(function(t)) {
t.executeSql('SELECT * FROM countries', [], countriesHandler, errorHandler);
});
function countriesHandler(transaction, data) {
var record;
var id;
var name;
for (var i=0; i<data.rows.length; i++) {
// We get the current record
record = data.rows[i];
id = record['id'];
name = record['name'];
// Do something with record information
}
}
function errorHandler(transaction, error) {
alert('Error getting results');
}
Note:
JavaScript databases support versioning, allowing us to change
schema on newer versions of our applications and detect what the
current version installed on the client is, to create a migration
procedure.
If you were working offline you should implement a
synchronization method, using Ajax to download changes from and upload
them to the server.
Note:
Safari on iOS accepts up to 5 MB in offline storage without
user intervention. If you try to save more than 5 MB, the user will
need to approve it (an automatic alert will appear).
7.2.1. Gears storage
Google Gears (http://code.google.com/apis/gears) is an open source
project that enhances web browsers and JavaScript with more
functionality, much of which is part of the HTML 5 draft. Android
1.X supports only Gears, and we can also use SQL databases with
Gears. The normal usage is simple and synchronous, as the following
sample demonstrates:
<script type="text/javascript" src="gears_init.js"></script>
<script type="text/javascript">
var db = google.gears.factory.create('beta.database');
db.open('countries');
db.execute(CREATE TABLE countries (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL)');
var rs = db.execute('SELECT * FROM countries');
while (rs.isValidRow()) {
var id = rs.field(0);
var name = rs.field(1));
// Do something
rs.next();
}
rs.close();
</script>
Fortunately, Google is working on an open source project that
gives us an abstraction layer for both HTML 5 databases and Gears
databases using the same code. The project is called the Web Storage Portability Layer and it is available at
http://code.google.com/p/webstorageportabilitylayer.
Note:
Safari on iOS allows you to view stored databases and delete
them from the device using Settings→Safari→Databases.
With this new layer, we can use the following code for select
queries in both frameworks:
var database = google.wspl.DatabaseFactory.createDatabase('countries',
'http://yourdomain/dbworker.js');
var statement = google.wspl.Statement('SELECT * FROM countries;');
database.createTransaction(function(tx) {
tx.executeAll([statement], {onSuccess: function(tx, resultSet) {
// Statement succeeded
for(; resultSet.isValidRow(); resultSet.next()) {
var id = resultSet.getRow()['id'];
var name = resultSet.getRow()['name'];
}
}, onFailure: function(error) {
// Statement failed
}});
}, {onSuccess: function() {
// After transaction commits, before any other starts
}, onFailure: function(error) {
// After transaction fails, before any other starts
}});
7.3. Client JSON store
Prepared for mobile devices, Lawnchair is a local client JSON
store that uses HTML 5 features behind a very simple API. You can
download the library from http://brianleroux.github.com/lawnchair.
To create a store, we just use code like this:
var countries = new Lawnhair('countries');
Then, we can save an object synchronously:
countries.save({id: 5, name: 'Spain'});
// Object saved
or asynchronously:
countries.save({id: 5, name: 'Spain'}, function() {
// Object saved
});
We can also save it using a key/value mechanism for easy
retrieval:
countries.save({key: 5, value: 'Spain'});
var spain = countries.get(5);
And we can easily get all the documents using:
countries.all(function(country) {
// We receive every country in this function
});
We can also remove documents, clear all the storage, and create
iterators for easy filtering.
In 2010, the W3C created a test to score mobile browsers in
terms of their RIA, Ajax, and HTML 5 support. The Web Compatibility Test for Mobile Browsers, available
at http://www.w3.org/2010/01/wctmb2, verifies
compatibility with Ajax, the canvas HTML tag, the contenteditablevideo and audio HTML 5 tags, Web Workers, local
storage, session storage, and @font-face. There are currently no mobile
browsers that score 100%. attribute, geolocation,
HTML 5 input forms, the offline AppCache, the Some results are: |