In
the previous article, we explored localstorage(DOM Storage) API. One
of the coolest features of local storage is that it is damn simple to
use. basically you are dealing with bunch of getter and setter
methods to get the benefit of localstorage. Its actually scaled up
version of cookies. But the limitations are quite obvious when we
need to operate on relatively large amount data.
firstly,
values are unstructured in localstorage. searching and
filtering will be on the order of O(n).
secondly,
everything is string here. you cannot store objects unless you are
using JSON.stringify and JSON.parse. both SQL and recently popular
NoSQL based solutions were proposed. the solutions came in the form
of web SQL database and IndexedDB respectively.
web
SQL database:
SQL
based web SQL database didn't get wide acceptance among the browser
developers. Though browsers like Chrome, Safari and Opera supported
web SQL database but popular browser Firefox was more interested in NoSQL
based IndexedDB. as a result Firefox doesn't even have minimal
support for web SQL database. Finally, W3C wrote the death
certificate of web SQL database API by keeping it off active
maintenance. they cited lack of independent implementations as being
the reason because most of the browser relied on SQLite as the
underlying database. web SQL database brought real relational
database implementation on browsers. data could be stored in a
very structured way. it supported JOIN operation to give quick
access.
IndexedDB:
It
looks like IndexedDB is going to be the standard for large scale
storage in client. Structurally IndexedDB sits somewhere between true
SQL and unstructured localStorage systm. basic features include:
1. values are stored in javascript objects
2. quick search and filtering is supported by separate
indexing system.
3. both synchronous and asynchronous APIs are available in the
specification (Though no browser
yet implemented synchronous API). But for the
time being, its mostly anynchronous
4. Joining is also possible.
5. object-oriented.
6. based on transactional database model. every operation
is executed in a particular context of
transaction (Three types of
transactions: read/write transactions, read only and snapshot).
this
comes handy when you open two instances of same
web application in two different browser tabs.
7. data stored as key-value pairs.
8. uses DOM based event system to notify when result is ready
for an operation (typical for
asynchronous paradigm)
9. Like AJAX, IndexedDB obeys same origin policy to enforce
data security. each origin ( or
website) has its own set of databases that are
not allowed to accessing data of database sets loaded
a different domain or website. origin is differentiated
using the combination of protocol, domain
name and port number.
http://www.example.com and http://www.example.com/en/ both
have same origin but https://www.example.com
and http://www.example.com spawned from two different
origin. (differs in protocol)
Synchronous
API:
HTML5
supports Concurrency through worker API. Synchronous API of
IndexedDB is intended to be used with Worker API.
Asynchronous
API:
everything
is asynchronous here. you need to make sure to wrap the codes that
depends on the query result, in a callback function. every operation
must have callback functions that will be executed in response to the
event fired as a result of the operation.
Object
Stores:
Every
database is named and contains one or more named Object Stores.
Object stores can be grossly compared to tables in relational
database parlance. every stored object must contain(explicitly
or implicitly) any properties defined by Object Stores . values
stored in Object Store are structured and doesn't have rigidly
defined schema. Object Store can contain one or more Indexes for easy
filtering .
Cross-browser API instances:
Because
of the difference in the implementation across vendors, obtaining
instance is a bit different vendor-wise.
if("webkitIndexedDB" in window){ window.indexedDB = window.webkitIndexedDB; window.IDBTransaction = window.webkitIDBTransaction; window.IDBKeyRange = window.webkitIDBKeyRange; }else if("moz_indexedDB" in window){ window.indexedDB = window.moz_indexedDB; } if(!window.indexedDB){ alert("browser doesn't support indexedDB."); }
why
not simple namespacing:
var exampledb = {}; exampledb.indexedDB = {};
Database
creation is asynchronous:
exampledb.indexedDB.open = function(){ var result = window.indexedDB.open( "mydb", // database name/ID "my sample db" // tiny description ); result.onsuccess = functnion(e) { // the result attribute of the event, e holds the reference to database exampledb.indexedDB.db = e.target.result; ..... // do as needed. }; result.onerror = function(e){ .... }; }
Database can be versioned and its asynchronous too:
result.onsuccess = functnion(e) { // the result attribute of the event, e holds the reference to database exampledb.indexedDB.db = e.target.result; var db = exampledb.indexedDB.db; if("" == db.version){ // user is visiting the page for the first time. lets setup database
// by creating // necessary Object Stores and populating default data. var setVrequest = db.setVersion("1.0"); // setting a version creates an implicit transaction which ensures
// everything in the callback // succeeds or everything fails. setVrequest.onfailure = function(e) { ...... }; setVrequest.onsuccess = function(e) { ...... // we are good to setup database. }; } ..... // do as needed. };
Creating Object Stores and Indexes:
setVrequest.onsuccess = function(e) { // Object Store creation is atomic and can only take place inside version
// changing transaction var store = db.createObjectStore( "name":"mysamplestore", // The store's name "key":"id", // The property used as key. "autoIncrement":true // yes it should be auto incrementing ); store.createIndex( "sampleIndex", // name of the index "id", // the property to be indexed. true // its unique constraint ); };
Write operation is asynchronous:
exampledb.indexedDB.writeToDB = function(dataToWrite){ var db = exampledb.indexedDB.db; // create a transaction var writeTrans = db.transaction( ["mysamplestore"], // Object Store to lock IDBTransaction.READ_WRITE // Lock type ); //open the store for writing var store = writeTrans.objectStore("mysamplestore"); var writeRequest = store.put({ "name": "binnash", // can be object "url": "http://www.binnash.blogspot.com" // can be object too }); writeRequest.onerror = function(e){ writeTrans.abort(); // undo and roll back }; writeRequest.onsuccess = function(e){ console.log(e); }; };
Reading data is also asynchronous:
exampledb.indexedDB.readRecord = function (){ var db = exampledb.indexedDB.db; // create a transaction var readTrans = db.transaction( ["mysamplestore"], // Object Store to lock IDBTransaction.READ_only // Lock type ); //open the store for writing var store = readTrans.objectStore("mysamplestore"); var readCursor = store.openCursor(); readCursor.onerror = function(e){ // }; readCursor.onsuccess = function(e){ if(e.result){ console.log(e.target.result.value.url); e.target.result.continue(); }else{ // you've reached the end of the cursor's list. } }; }
Querying in the Object Store:
as
usual Querying is also asynchronous.
exampledb.indexedDB.queryStore = function (){ var db = exampledb.indexedDB.db; // create a transaction var readTrans = db.transaction( ["mysamplestore"], // Object Store to lock IDBTransaction.READ_only // Lock type ); //open the store for writing var store = readTrans.objectStore("mysamplestore"); var bounds =new IDBKeyRange.bound( "a", // Lower Bound "z", // Upper Bound true, // include lower bound? true // include upper bound? ); var readCursor = store.openCursor(bounds); readCursor.onerror = function(e){ // }; readCursor.onsuccess = function(e){ if(e.result){ console.log(e.target.result.value.url); e.target.result.continue(); }else{ // you've reached the end of the cursor's list. } }; };
Deleting record:
exampledb.indexedDB.deleteItem = function (id){ var db = exampledb.indexedDB.db; // create a transaction var readTrans = db.transaction( ["mysamplestore"], // Object Store to lock IDBTransaction.READ_WRITE // Lock type ); //open the store for writing var store = readTrans.objectStore("mysamplestore"); var request = store.delete(id); readCursor.onerror = function(e){ console.log(e); }; readCursor.onsuccess = function(e){ // update screen. }; };