A while ago I released IDBWrapper. If you don’t know it, it’s a wrapper for IndexedDB, a current specification (in draft status) for an in-browser object store. It’s implemented in Firefox and Chrome,
and somehow (as a plugin of sorts) also in IE, but, honestly, I don’t care about that too much. Update: IE10 has an IndexedDB implementation!
It is mainly meant to serve as an example implementation, so that you could have a look at the code and see how to work with IndexedDB. But I figured that people are also interested in actually using it, as it abstracts away many of the tedious internals of IndexedDB (like transactions) – and it is perfectly fine to use IDBWrapper for all non-overly-complex scenarios.
So here’s a tutorial about how to work with IDBWrapper and a little background info about IndexedDB internals every now and then, instead of writing Yet-Another-Super-Technical-IDB-Blargh. Part one will cover some info about what IndexedDB is, getting IDBWrapper to run and how to read and write data to a store. Part two is be about querying the store.
What is IndexedDB – why is it special?
You can download/clone/fork IDBWrapper at GitHub. To include it in your page/project, you have two options: You can either include the IDBStore.js file via a script tag, or you can require it using an AMD loader like RequireJS.
In this tutorial, we’ll create a people store where we store information about humans – a group of customers, for example.
Step one: open the store
A note on the properties we pass to the constructor:
- storeName: just choose a meaningful name
- dbVersion: start with 1, and only change this when the structure of your store changes
- keyPath: the property you want as unique primary key
- autoIncrement: set this to true if you do not want to take care of the uniqueness of the primary key yourself
- onStoreReady: a function to be called when your store is ready to work with
IDBwrapper will open the database, check if a store with the given name exists and if not, it will create it using the above properties. You’ll then get an IDBStore instance back that you can use to work with the store. Once the store is created, it’s persistent. But, if you need to delete and re-create it (for example, if you happened to pick the wrong value for keyPath), you can delete the store using the
Inside IndexedDB: IDBWrapper works with one store; when you do a
new IDBStore() it represents one store in an IndexedDB. If you want multiple stores inside of the same IndexedDB, you’d have to do a
new IDBStore() for each store. This is an artificial limitation of IDBWrapper; if you work with IndexedDB directly, you can lock multiple stores when starting a transaction. This be useful when you want to update one store depending on the information found in another store, and you want to prevent this other store’s contents to change during your update action.
Step 2: storing a customer
Note how we don’t have an
id property on the dude object, as we specified autoIncrement as true, so the database will take care of this. In the
onsuccess callback we’ll get the id of the newly inserted record back. Also, see how we attached an array of emails to the record? In a relational database you’d set up a different table for email addresses – no need to do that in IndexedDB.
Inside IndexedDB: Make sure to wait until the store is ready before operating on it! The whole nature of IndexedDB is asynchronous. Whatever you do, you will have to work with callbacks. There is, however, also a draft for a specification of a synchronous API, but that is not implemented in any browser (and I don’t think this is going to happen anytime soon).
Step 3: reading the customer back from the store
We just pass the id we got after inserting to the get method, and there we have our record from the store. It’s exactly the same thing we stored earlier, with one exception: the object now has the
id property which has been attached to it by the database.
Inside IndexedDB: What we pass as first parameter to the
get method is the keyPath value. That means when we pass 1 to it, the database will look for an object that has 1 as value in it’s keyPath property, which we set to ‘id’ when we created the store.
Step 4: updating data
To update a record, you also use the put method: If there already is an object in the store with the same keyPath value (the id), it will simply overwrite it. Note that it will really, really overwrite the existing object, which means you cannot simply put a modified property of it, but have to put the whole object, including all properties.
Inside IndexedDB: keyPath values are also Type-sensitive. That means, if the id of our dude is 1 and of type Number, you have to pass numeric 1 to the get method to retrieve the dude, and for updates the value of the id property needs to be numeric 1. If you use “1” as String, IndexedDB will think of it as a different value.
Step 5: getting all items
Nothing really to say about it. Just one note: IDBStore has default error handlers for every async method that prints potential errors to the console. So if you’re just playing around you don’t need to pass your own handler to the methods.
Step 6: deleting an item
Different browsers and versions return different values to the success callback; that’s why there is an extra check in the success callback.
Step 7: clearing the store
If you need to clear the store from all stored entries, you can use the clear method. Note that this won’t reset Chrome’s autoIncrement counter.
That’s it, thanks for listening!
Now you know everything to do basic IndexedDB data operations with IDBWrapper. Make sure to check out the examples and the documentation!
If you want to dig deeper and learn about querying the store, go on to Part two of the IDBWrapper tutorial.
I can relate to why you need a wrapper. I think that the API can quickly get too verbose. I wrote a similar wrapper, with some semantic differences.
A jquery plugin for IndexedDB – this uses jquery deferreds and tries to use smart defaults –
Demo : http://nparashuram.com/IndexedDB/jquery/index.html
Blog : http://blog.nparashuram.com/2011/04/indexeddb-jquery-plugin.html
Since IndexedDB does not have a querying language, I thought of writing one up –
Let me know what you think of the API .. :)
Some questions on the implementation
1. When a store does not exist and I do a new IDBStore, and then store it, does it all happen in the VERSION_CHANGE transaction
2. When getting all items, all data seems to be in an array. What if the amount of data is huge ? Why not have an iterator like syntax?
3. Noticed that Index functionality is not available. Any specific reason for that.
4. What is the default transaction type you use for get vs store ? Do you explicitly change from READ to READ_WRITE ?
I really like the LINQ syntax! A nice, human-readable way of retrieving data.
1. Yes, store creation happens in a VERSION_CHANGE transaction.
2. Yes, getAll returns an Array. FF already has a non-standard impl of getAll, in Chrome I do iterate over all stored entries. So, yeah, if the data is huge, this is going to be an intense operation :) Iteration is in the making and will be there for the second part of this tutorial; you can have a sneak peek at the iterate() method in the develop branch of IDBWrapper on GitHub.
3. Index functionality will also be covered in part two, but you can already check it out in the develop branch.
4. Yes, get() and getAll() use a READ_ONLY transaction, other methods READ_WRITE. iterate() will probably offer both depending on a flag you can set, defaulting to READ_ONLY, as most ppl won’t need write access in cursors.
Also, some questions on transactions.
– Is there a way in the API to say that perform a write and another operation in hte same transaction ?
– What happens if one tab deletes is doing a version transaction deleting an object store while the other writes to that store ? Standard errors, right ?