Thursday, March 3, 2011

IndexedDB – The Store In Your Browser


Cross Posted from a Script Junkie Article

If you use your web apps as much as your desktop applications, I am sure you hate the part where you have to be online to get them to work. Well, the HTML5 set of standards may have a solution. With the ability to make disconnect applications possible, this is yet another boundary that been blurred between the web and native applications. Among other things, the “capability-hungry” web applications have started asking for the equivalent of a database on the browser and the IndexedDB standard promises to fill that gap. Whether it is to let you work while disconnected, or simply to load you application faster by caching resources locally, offline support is an important aspect of the next generation of web applications.
IndexedDB would be used by different classes of applications like
  • Intermittently connected apps that should run in the offline mode also. Examples include mail and calendar clients that are synced when the connection is restored.
  • Disconnected applications that do not require a server. “To Do” Lists or Sticky Notes are an example of this.
  • Applications with large amounts of data may prefer to cache it at the client, using the database. This could be any regular website that can store user profile or shopping catalogs on the browser.

Exploring the (other) Options

Cookies were the way to store user data in the good old days, but that was for the last decade. Cookies are tiny and just not powerful enough. LocalStorage is promising, but the powerful web applications of today need a real database as a storage option. The WebDatabase API seemed to be the answer, but as Mozilla points out, standardizing SQL may not be easy. A simpler storage API based on indexed sequential access mode (ISAM) is the basis of the IndexedDB API.

Hello IndexedDB

Simply put, IndexedDB is a persistent store that lets you save and retrieve JavaScript objects. The objects are indexed based on a “key” or an “index” property of the object. The API also allows for iterating over the objects. Hence, IndexedDB is not really relational in the sense of a traditional database; relationships between different object stores are not explicit. The storage is also subject to the same origin policy.
The IndexedDB APIs are available in two flavors –
  • The Synchronous APIs, available with WebWorkers, wait for the database operation to complete and then return with the result of the operation.
  • The Asynchronous API, available on the global window object, does not wait for the database operation to complete and returns a “request” immediately. The onsuccess or onerror event handlers on the request are executed once the database operations are complete with the status of the operation usually is in the “result” property of the event.
This post discusses the Asynchronous API.

Browser Support Today

The IndexedDB specification is still a working draft, but the major browser vendors have released implementations to let end users play with the API and provide feedback on the API. IndexedDB support is available in
IndexedDB is exposed using vendor prefixes like moz_indexedDB and webkitIndexedDB in Firefox and Chrome respectively. In case of IE, you need to register the ActiveX object and initialize it. To set a common ground across browsers, you may have to assign the vendor prefixes to the global indexedDB. You may have to do this for vendor specific constants (like IDBRange, etc) also.
More browser specific information is available at http://caniuse.com/indexeddb.

The Database [IE] [FF] [CH]

With the above mentioned properties initialized, the object store is ready to be used. The first step is to open the database, using the open method. The code is self-explanatory.

var request = window.indexedDB.open("DatabaseName""DatabaseDescription");
request.onsuccess = function(event){
  var database = event.result;
  write("Database Opened", database);
};
request.onerror = function(e){
  writeError(e);
};
The onsuccess is invoked once the database is opened. It is created if it does not exist. The event.result holds result of the current operation, which is the database in this case.

A Store of Objects [IE] [FF] [CH]

The operations like get or put in IndexedDB are transactional. These operations can be in one of the transaction scopes (read only, read-write, snapshot read or version change) that follow simple rules to prevent operations from overstepping each other.
Without going into theory about transactions, an Object Store (logical equivalent of a table in the relational world) can to be created to hold individual objects.
Note that a new object store can only be created or deleted in the “version change” transaction scope.
var request = db.setVersion(1);
request.onsuccess = function(e){
    write("Version changed to ", db.version, ", so trying to create a database now.");
    var objectStore = db.createObjectStore("BookList""id", true);
};
A second parameter in the call specified the key path to be indexed on, while the third mentions if it has to be auto-generated. If no key is specified, automatic (out-of-line) keys are used. The createObjectStore returns the object store immediately.
An existing object store can be opened using a transaction created in the required scope. The last line in the example below returns the object store.
var transaction = db.transaction(["BookList"], 0 /*Read-Write*/1000 /*Time out in ms*/);
transaction.oncomplete = function(e){write("===== Transaction Complete");};      
var objectStore = transaction.objectStore("BookList");

Put it in the sack, and get it back [IE] [FF] [CH]

The Object Store provides methods to fetch or save objects across browser sessions. As in this case, auto-generated keys are should not be a part of the object to be saved.

var data = {"bookName" : "Name""price" : 100"rating":"good"};
var request = objectStore.add(data);
request.onsuccess = function(event){
    document.write("Saved with id ", event.result)
    var key = event.result;
};
This key can be used later to retrieve the object, even after the browser is restarted.
var request = objectStore.get(key);
request.onsuccess = function(event){
    document.write("Got data ", event.result)
};
Apart for saving and retrieving, the API provides a method called “delete” to delete the object, based on the key. Existing objects can be modified using the on the “put” method. Firefox also provides a getAll and a clear method to get all the objects, and clear the object store.

Let’s go over it [IE] [FF] [CH]

A “cursor” lets you iterate over the objects in the store. After opening the cursor, a continue method is called repeatedly for the iteration.

keyRange = new IDBKeyRange.bound(110, true, false);
var request = DAO.objectStore.openCursor(keyRange);
request.onsuccess = function(event){
    var cursor = event.result;
    write(cursor.key , cursor.value); // cursor.key is the object key, cursor.value is the actual object.
    cursor["continue"]();
};
A range can also be passed to the openCursor call to limit or filter the range. In the above example, all values of the key between 1 and 10 can be iterated over. The last 2 parameters signify if the lower and upper bounds of the range should be included or not.

“Index” path in IndexedDB [IE] [FF] [CH]

The objects can also be iterated over non-key properties of the object. An “index” (like a secondary key) needs to be created over the required properties and can only be done in the version change transaction scope.
var index = objectStore.createIndex("priceIndex""price", true);
Existing indexes in an Object Store can also be fetched.
var index = objectStore.index("priceIndex");
To iterate over the indexes, the familiar cursors can be opened over the indexes, just like they are on object stores.
var keyCursor = index.openKeyCursor(); //key=index property, value = primary key
var objectCursor = index.openCursor(); //key=index property, value = object
Once the cursor is available, they can be iterated over, like the cursor example above. The first statement above opens a cursor where the value is the primary key of the object. In the second statement the value is the object itself. In both cases, the key is “price” property that is the index.

Looking ahead

The examples above illustrate the most basic examples to open a database, save and fetch objects, and iterate over them. Without the complexity of SQL, the IndexedDB APIs are powerful enough to allow real world applications to use database capabilities in the browser. On a side note, many have voiced concerns over the lack of familiar querying capabilities with abilities to perform better filtering or “table joins”. JavaScript libraries may be built on top of this API for this functionality.
Though it may not be ready for production yet due to lack “deployed” browser support, this is one capability you application can look forward to.

Links

Some links to learn more about IndexedDB include

No comments:

Post a Comment