Sunday, February 5, 2012

HTML5 series part 2: IndexedDB


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.
   };                           
};