On the HTML5 Indexed DB API - Part 2 of n
In the previous post in this series we covered the fundamentals of the Indexed DB specification. We continue the journey in this post and take a look at the API itself.
Building an offline note taking app
For the rest of this series of posts, we'll try and build the client side data layer for a fictitious note taking web app. From a data model point of view it's about as simple as it can get. The app allows users to write text notes and tag them with specific key words. Each note will have a unique identifier which will serve as its key and apart from the note text, it will be associated with a collection of tag strings. Here's a sample note object represented in JavaScript object literal notation:
var note = {
id: 1,
text: "Note text.",
tags: ["sample", "test"]
};
We'll build a NotesStore
object that has the following interface:
var NotesStore = {
init: function(callback) {
},
addNote: function(text, tags, callback) {
},
listNotes: function(callback) {
}
};
It should be fairly self-evident as to what each method does. All method calls execute asynchronously and where a result is to be returned to the caller, the interface accepts a reference to a callback that is to be invoked with the result. Let's see what it takes to efficiently implement this object using an indexed database.
Testing for Indexed DB
The root object that you deal with when talking to the indexed DB API is called indexedDB
. In fact you can check for the presence of this object to see whether the current browser supports indexed DB or not. Like so:
if(window["indexedDB"] === undefined) {
// nope, no indexed DB!
} else {
// yep, we're good to go!
}
Alternatively, you can use the Modernizr JavaScript library to test for support for indexed DB like so:
if(Modernizr.indexeddb) {
// yep, go indexeddb!
} else {
// bleh! No joy!
}
Asynchronous requests
The asynchronous API calls work through what are known as "request" objects. When an asynchronous API call is made, it would return a reference to a "request" object which exposes two events - onsuccess
and onerror
. The former is raised when the API executes successfully and the latter is raised when it errors out. Here's what a typical call looks like:
var req = someAsyncCall();
req.onsuccess = function() {
// handle success case
};
req.onerror = function() {
// handle error
};
As you work with the indexedDB API you eventually get to a point where it becomes somewhat hard to keep track of all the callbacks. In a future post I'll explore some ideas I am currently playing with on how we can simplify this further. For now though, to make our exploration of the API somewhat simpler I'll define and use a small utility routine that abstracts the "request" pattern away:
var Utils = {
errorHandler: function(cb) {
return function(e) {
if(cb) {
cb(e);
} else {
throw e;
}
};
},
request: function (req, callback, err_callback) {
if (callback) {
req.onsuccess = function (e) {
callback(e);
};
}
req.onerror = errorHandler(err_callback);
}
};
Now, I can write my async calls like so:
Utils.request(someAsyncCall(), function(e) {
// handle completion of call
});
Creating and opening the database
Creating/opening a database is done by calling the open
method of the indexedDB
object. Here's an implementation of the NotesStore
object's init
method:
var NotesStore = {
name: "notes-db",
db: null,
ver: "1.0",
init: function(callback) {
var self = this;
callback = callback || function () { };
Utils.request(window.indexedDB.open("open", this.name), function(e) {
self.db = e.result;
callback();
});
},
.
The open
method opens the database if it already exists or creates a new one if not. The success callback of the request object returned by open
receives an event object as a parameter whose result
property is a reference to the newly opened database. You can think of this as the object that represents the connection to the database. When this object is destroyed (or if you called its close
method) the connection to the database is terminated.
Coming up
Now that we have the database created, in the next post, we'll go ahead and create the rest of the database objects.