This is an IndexedDB adapter for Backbone.js.
Warnings
It lacks a lot of documentation, so it's good idea to look at the tests if you're interested in using it.
Browser support and limitations
In Firefox, backbone-indexeddb.js
should work from 4.0 up; but it
- won't work at all with local files (
file:///
URLs). As soon as you try to create the database it will raise an errorPermission denied for <file://> to create wrapper for object of class UnnamedClass
- will ask the user for permission before creating a database
- requests permission again when the database grows to a certain size (50MB by default). After this the disk is the limit, unlike the fairly concrete and currently fixed limit (5MB by default) that
localStorage
gets (which will just fail after that with no way to ask the user to increase it).
Chrome 11 and later are supported. (Chrome 9 and 10 should also work but are untested.) In Chrome 11, backbone-indexeddb.js
- will work with
file:///
URLs, but - poses some hard size limit (5MB? quantity untested) unless Chrome is started with
--unlimited-quota-for-indexeddb
, with apparently no way to request increasing the quota.
IE10 support has been added thanks to lcalvy.
Other browsers implementing the Indexed Database API Working Draft should work, with some of these limitations possibly cropping up or possibly not. Support and ease of use is expected to improve in upcoming releases of browsers.
Tests
Just open the /tests/test.html
in your favorite browser. (or serve if from a webserver for Firefox, which can't run indexedDB on local file.)
Node
This is quite useless to most people, but there is also an npm module for this. It's useless because IndexedDB hasn't been (yet?) ported to node.js. It can be used in the context of browserify though... and this is exactly why this npm version exists.
Implementation
Database & Schema
Both your Backbone model and collections need to point to a database
and a storeName
attributes that are used by the adapter.
The storeName
is the name of the store used for the objects of this Model or Collection. You should use the same storeName
for the model and collections of that same model.
The database
is an object literal that define the following :
id
: and unique id for the databasedescription
: a description of the database [OPTIONAL]migrations
: an array of migration to be applied to the database to get the schema that your app needs.
The migrations are object literals with the following :
version
: the version of the database once the migration is applied.migrate
: a Javascript function that will be called by the driver to perform the migration. It is called with aIDBDatabase
object, aIDBVersionChangeRequest
object and a function that needs to be called when the migration is performed, so that the next migration can be executed.before
[optional] : a Javascript function that will be called with the database, before the transaction is run. It's useful to update fields before updating the schema.after
[optional] : a Javascript function that will be called with the database, after the transaction has been run. It's useful to update fields after updating the schema.
Example
var database = {
id: "my-database",
description: "The database for the Movies",
migrations : [
{
version: "1.0",
before: function(next) {
// Do magic stuff before the migration. For example, before adding indices, the Chrome implementation requires to set define a value for each of the objects.
next();
}
migrate: function(transaction, next) {
var store = transaction.db.createObjectStore("movies"); // Adds a store, we will use "movies" as the storeName in our Movie model and Collections
next();
}
}, {
version: "1.1",
migrate: function(transaction, next) {
var store = transaction.db.objectStore("movies")
store.createIndex("titleIndex", "title", { unique: true}); // Adds an index on the movies titles
store.createIndex("formatIndex", "format", { unique: false}); // Adds an index on the movies formats
store.createIndex("genreIndex", "genre", { unique: false}); // Adds an index on the movies genres
next();
}
}
]
}
Models
Not much change to your usual models. The only significant change is that you can now fetch a given model with its id, or with a value for one of its index.
For example, in your traditional backbone apps, you would do something like :
var movie = new Movie({id: "123"})
movie.fetch()
to fetch from the remote server the Movie with the id 123
. This is convenient when you know the id. With this adapter, you can do something like
var movie = new Movie({title: "Avatar"})
movie.fetch()
Obviously, to perform this, you need to have and index on title
, and a movie with "Avatar" as a title obviously. If the index is not unique, the database will only return the first one.
Collections
I added a lot of fun things to the collections, that make use of the options
param used in Backbone to take advantage of IndexedDB's features, namely indices, cursors and bounds.
First, you can limit
and offset
the number of items that are being fetched by a collection.
var theater = new Theater() // Theater is a collection of movies
theater.fetch({
offset: 1,
limit: 3,
success: function() {
// The theater collection will be populated with at most 3 items, skipping the first one
}
});
You can also provide a range applied to the id.
var theater = new Theater() // Theater is a collection of movies
theater.fetch({
range: ["a", "b"],
success: function() {
// The theater collection will be populated with all the items with an id comprised between "a" and "b" ("alphonse" is between "a" and "b")
}
});
You can also get all items with a given value for a specific value of an index. We use the conditions
keyword.
var theater = new Theater() // Theater is a collection of movies
theater.fetch({
conditions: {genre: "adventure"},
success: function() {
// The theater collection will be populated with all the movies whose genre is "adventure"
}
});
You can also get all items for which an indexed value is comprised between 2 values. The collection will be sorted based on the order of these 2 keys.
var theater = new Theater() // Theater is a collection of movies
theater.fetch({
conditions: {genre: ["a", "e"]},
success: function() {
// The theater collection will be populated with all the movies whose genre is "adventure", "comic", "drama", but not "thriller".
}
});
You can also selects indexed value with some "Comparison Query Operators" (like mongodb) The options are:
- $gte = greater than or equal to (i.e. >=)
- $gt = greater than (i.e. >)
- $lte = less than or equal to (i.e. <=)
- $lt = less than (i.e. <)
See an example.
var theater = new Theater() // Theater is a collection of movies
theater.fetch({
conditions: {year: {$gte: 2013},
success: function() {
// The theater collection will be populated with all the movies with year >= 2013
}
});
You can also get all items after a certain object (excluding that object), or from a certain object (including) to a certain object (including) (using their ids). This combined with the addIndividually option allows you to lazy load a full collection, by always loading the next element.
var theater = new Theater() // Theater is a collection of movies
theater.fetch({
from: new Movie({id: 12345, ...}),
after: new Movie({id: 12345, ...}),
to: new Movie({id: 12345, ...}),
success: function() {
// The theater collection will be populated with all the movies whose genre is "adventure", "comic", "drama", but not "thriller".
}
});
You can also obviously combine all these.
Optional Persistence
If needing to persist via ajax as well as indexed-db, just override your model's sync to use ajax instead.
class MyMode extends Backbone.Model
sync: Backbone.ajaxSync
Any more complex dual persistence can be provided in method overrides, which could eventually drive out the design for a multi-layer persistence adapter.