IndexedDB – Client Side Database

Author posted by Jitendra on Posted on under category Categories HTML, Tech Tips and tagged as Tags , , with Leave a comment on IndexedDB – Client Side Database

One of many features of HTML5 is capability to store data on client machine with the help of “Indexed Database API“. Using this technique web developers can add offline capabilities to their application. Currently almost every modern browser supports “IndexedDB”.

Unlike traditional RDBMS, IndexedDB doesn’t have table or columns which supports SQL. In RDBMS, it is very hard to change structure of table once schema is decided. IndexedDB actually stores object which can be of any type. Store Object in IndexedDB and process inside Javascript, it is that simple. Every object needs to have unique Index which makes it accessing faster and efficient. Before IndexedDB, we had “Web SQL Database“. However W3C announced that it is depreciated, still few browsers continues support for Web SQL.

Lets start with Example on Indexed DB :

Very first step in IndexedDB is opening database. Below Code Sample will explain the process.

//Open Database
        offline.indexedDB.open = function() {
            var version = 1.0;

            //Open "todos" database, which will be used throughout App
            var request = indexedDB.open("todos", version);

            // We can only create Object stores in a versionchange transaction.
            request.onupgradeneeded = function(e) {
                var db = e.target.result;

                // A versionchange transaction is started automatically.
                e.target.transaction.onerror = offline.indexedDB.onerror;

                if(db.objectStoreNames.contains("todo")) {
                  db.deleteObjectStore("todo");
                }

                var store = db.createObjectStore("todo",
                  {keyPath: "timeStamp"});
            };

            request.onsuccess = function(e) {
                //Code on Success
            };

            request.onerror = function(e) {
                //Code on Error
            };

    }; //End Open

In above code sample, we have opened IndexedDb database named “todos”. You can give any name to your database. For ease of code maintenance, I am using namespace “offline”. All methods and necessary information are encapsulated in this namespace.

open() method of IndexedDB returns “IDBRequest” object. If have defined three methods on this.

  1. onerrr()
  2. onsuccess()
  3. onupgradeneeded()

As name suggests, onsuccess and onerror method will be called if we are able to open IndexedDB connection successfully or not.

Most important method is “onupgradeneeded“. If the open request is successful and database’s version is higher than existing database’s version, then our onupgradeneeded callback is executed. In this callback, a “versionchange” transaction will be started automatically.

The onupgradeneeded callback is the only place in our code that we can alter the structure of the database. In it we can create and delete Object Stores and build and remove indexes. If we want to alter the structure of the database again, it is necessary to upgrade database’s version.

Also, Object Stores are created with a call to createObjectStore().

What is ObjectStore in IndexedDB ?
Data is stored in object stores, which are collections of JavaScript objects whose attributes contain individual values.

Each JavaScript object, sometimes called a record, in an object store has a common attribute called a key path; the value of this attribute is called a key value (or key). Key values uniquely identify individual records within an object store.

Adding Record in IndexedDB
If everything is fine in above code and we are successfully able to open connection to database then we can add record by simply calling “put” method. This method also returns “request” object where we can define success and error callback.

offline.indexedDB.addTodo = function(todoText) {
      var db = offline.indexedDB.db;
      var trans = db.transaction(["todo"], "readwrite");

      //Get Object Store
      var store = trans.objectStore("todo");

      //Add Object into Database
      var request = store.put({
        "text": todoText,
        "timeStamp" : new Date().getTime()
      });

      request.onsuccess = function(e) {
        // Do operation if request success
      };

      request.onerror = offline.indexedDB.onerror;

    }; //End addTodo

In above code we have defined “read-write” transaction.

A transaction manages the context of operations and is used to maintain the integrity of database activities. For example, you can create object stores only in the context of a version change transaction. If a transaction is aborted, all operations within the transaction are canceled.

Deleting record from IndexedDB
Record can be deleted from Indexed DB by calling delete(id) method. This method excepts key which identifies record uniquely.

offline.indexedDB.deleteTodo = function(id) {
      var db = offline.indexedDB.db;
      var trans = db.transaction(["todo"], "readwrite");
      var store = trans.objectStore("todo");

      var request = store.delete(id);

      request.onsuccess = function(e) {
        // Code on Success
      };

      request.onerror = offline.indexedDB.onerror;
    };

Complete code :

<html>
<head>
<title>Getting started with IndexedDB </title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
</head>
<div id="todoItems">
    <!-- Data will be loaded here -->
    <ul> <li> sds </li> </ul>
</div>

<input type="text" id="todo" name="todo" placeholder="Add To do Item here" style="width: 200px;" />
<input type="submit" value="Add Todo Item" onclick="addTodo(); return false;" />

<script>
    //Get DOM Elements in variable with JQuery
    $todoItems = $("#todoItems").find("ul");
    $todo = $("#todo");

    //offline is namespace, so that all our code is encapsulated
    var offline = {};
    offline.indexedDB = {};

    offline.indexedDB.db = null;

        //Open Database
        offline.indexedDB.open = function() {
            var version = 1;

            //Open "todos" database, which will be used throughout App
            var request = indexedDB.open("todos", version);

            // We can only create Object stores in a versionchange transaction.
            request.onupgradeneeded = function(e) {
                var db = e.target.result;

                // A versionchange transaction is started automatically.
                e.target.transaction.onerror = offline.indexedDB.onerror;

                if(db.objectStoreNames.contains("todo")) {
                  db.deleteObjectStore("todo");
                }

                var store = db.createObjectStore("todo",
                  {keyPath: "timeStamp"});
            };

            request.onsuccess = function(e) {
                offline.indexedDB.db = e.target.result;
                offline.indexedDB.getAllTodoItems();
            };

            request.onerror = offline.indexedDB.onerror;

    }; //End Open

    //define error method
    offline.indexedDB.onerror = function(err) {
        //write error on Javascript Console
        console.log(e.value);
    } ; //onerror

    offline.indexedDB.addTodo = function(todoText) {
      var db = offline.indexedDB.db;
      var trans = db.transaction(["todo"], "readwrite");
      var store = trans.objectStore("todo");
      var request = store.put({
        "text": todoText,
        "timeStamp" : new Date().getTime()
      });

      request.onsuccess = function(e) {
        // Re-render all the todo's
        offline.indexedDB.getAllTodoItems();
      };

      request.onerror = offline.indexedDB.onerror;

    }; //End addTodo

    offline.indexedDB.getAllTodoItems = function() {
      $todoItems.html("");

      var db = offline.indexedDB.db;
      var trans = db.transaction(["todo"], "readwrite");
      var store = trans.objectStore("todo");

      // Get everything in the store;
      var keyRange = IDBKeyRange.lowerBound(0);
      var cursorRequest = store.openCursor(keyRange);

      cursorRequest.onsuccess = function(e) {
        var result = e.target.result;

        if(result == null || result == false)
          return;

        renderTodo(result.value);
        result.continue();
      };

      cursorRequest.onerror = offline.indexedDB.onerror;
    };//GetAllTodo

    function renderTodo(row) {

       var $li = $("<li><span></span><a></a></li>");
       $todoItems.append($li);

       $("span",$li).html(row.text);
       $("a",$li).attr("href", "#");
       $("a",$li).html("- Del");

       $("a",$li).click(function(e) {
          offline.indexedDB.deleteTodo(row.timeStamp);
        });

    }

    offline.indexedDB.deleteTodo = function(id) {
      var db = offline.indexedDB.db;
      var trans = db.transaction(["todo"], "readwrite");
      var store = trans.objectStore("todo");

      var request = store.delete(id);

      request.onsuccess = function(e) {
        // Refresh the screen
        offline.indexedDB.getAllTodoItems();
      };

      request.onerror = offline.indexedDB.onerror;
    };

    function init() {
      // open displays the data previously saved
      offline.indexedDB.open();
    }

    function addTodo() {
      offline.indexedDB.addTodo($todo.val());
      $todo.val('');
    }

    //Start Application
    init();
</script>

Output :

HTML5 IndexedDB Demo
HTML5 IndexedDB Demo

Related posts

Leave a Reply

Your email address will not be published. Required fields are marked *