Showing posts with label IDBRequest. Show all posts
Showing posts with label IDBRequest. Show all posts

Wednesday, January 25, 2012

Indexed DB: Defining your structure, the new way

Some months ago I posted a blog post about defining your database structure. Because Indexed DB is still in draft, specifications can change from time to time. This is the case for the way you want to define your database structure. As I am typing now the only browser that is currently implementing this new specification is FireFox Nightly.
The way you create object stores and indexes hasn’t changed, but the way you can change it did. Changing your database structure still happens in a VERSION_CHANGE transaction, but the developer won’t be able to manually create a new VERSION_CHANGE transaction. Instead, the version of database you want to use needs to be provided when opening a database connection. When the database doesn’t have the version you requested, an onupgradeneeded event will occur. In this event you will be able to handle all your database changes like creating and deleting objectstores, creating and deleting indexes, …
But this all means that the SetVersion method for creating a VERSION_CHANGE transaction will become obsolete. So this means you will always have to define your database structure before you can start using them, even for indexes.
So how does this new way look like?
   1: var req = window.indexedDB.open(databaseName, databaseVersion);
   2: req.onsuccess = function (e) {
   3:     // Opening of the databas in the given version was succesfull
   4: }
   5:  
   6: req.onerror = function (e) {
   7:     // Error while opening the database
   8: }
   9:  
  10: req.onupgradeneeded = function (e){
  11:     // Event for handeling the database structure changes
  12: }
  13:  
  14: req.onblocked = function (e){
  15:     // Handles the database open request while the database gets upgraded
  16: }

The onblocked event was also added with the onupgradeneeded event. The onblock event will handle attempts to open the database while the database is upgrading to a more recent version. This way you can notify the user the request was blocked and they need to retry later.

As last there is an onversionchange event added on the databse object. This event will be called when the database is about to upgraded to a newer version. This way you’ll be able to close your current connection so you don’t get an error later on.


   1: var req = window.indexedDB.open(databaseName, databaseVersion);
   2:  
   3: req.onsuccess = function (e) {
   4:     req.result.onversionchange = function(){
   5:         // Close your connection
   6:     }  
   7: }

Monday, January 2, 2012

Indexed DB: Reading multiple records

In my previous post I have been talking about reading data. Today, I’ll be talking about reading multiple records at once. Here for we will use a cursor. A cursor is a transient mechanism used to iterate over multiple records in a database. The storage operations of the cursor can be used on the underlying index or an object store.

In the IDBObjectStore interface, we have the openCursor method to create a new cursor for retrieving data. In the IDBIndex interface, we have 2 ways to create a new cursor. These methods are openCursor to retrieve the values from the index and openKeyCursor to retrieve the keys. There are 2 optional parameters that can be provided when calling these methods. The first parameter is an IDBKeyRange, with this we will narrow the result by defining the bounds of the keys we want to retrieve. The second parameter is the direction the cursor must navigate trough the results.

IDBKeyrange

A key range is a continuous interval over some data type used for keys. A key range can have one of the following situations:

  • lower bounded: The keys must have a value smaller than the provided lower bound
  • upper bounded: The keys must have a value larger than the provided upper bound
  • lower and upper bounded: The keys must have a value between the lower and the upper bound
  • unbounded: All keys will be valid
  • Single value: The key must be the provide value

    The upper and lower bound can be open, this means the value of the bound won’t be included, or closed, this means the value of the bound will be included.

    More information about the IDBKeyRange interface can be found here. You will also find some more information about the methods to create a key rage. If you want to use an unbounded key range, you don’t need to provide a key range.

    Retrieving data with a cursor

    As for all actions preformed on the database a transaction is also needed in case of reading data with a cursor.

       1: var txn = dbconnection.transaction([“ObjectStoreName”]);
       2: var objectStore = txn.objectStore(“ObjectStoreName”);
       3: var cursorReq;
       4: // IE 10, Chrome and Firefox implementation
       5: if(window.msIndexedDB || window.mozIndexedDB || window.webkitIndexedDB){
       6:     cursorReq = store.openCursor();
       7: }
       8: // IE Indexed DB Prototype implementation
       9: else{
      10:     cursorReq = store.openCursor(IDBKeyRange.lowerBound(0));
      11: }
      12:  
      13: handleCursor(cursorReq, txn, success, error);

    First things first, we define the cursor. One of the first thing you notice is that the IE Indexed DB prototype requires a key range. This means it won’t be possible to use the unbounded key range. Because I’m using an auto increment key for my object store, I can take use of the lowerbound method to create a key range from 0 to forever. After the cursor is defined, I use a method to handle the reading of the cursor.



  •    1: function handleCursor(cursorReq, txn, success, error){
       2:     cursorReq.onsuccess = function (event) {
       3:         if (event.result) {
       4:             var cur = event.result;
       5:             if (cur) {
       6:                 cursor_get_record(cur);
       7:             }
       8:         }
       9:         else if (cursorReq.result) {
      10:             var cur = cursorReq.result;
      11:             // Present the data
      12:             cur.continue();
      13:         }
      14:     cursorReq.onerror = error
      15: }

    Again the IE Indexed DB prototype has a different implementation for handling a cursor. I’ll start with explaining the correct way. If the request object contains a result. If this is empty, you reached the end of the cursor. If the result is an object, the value of the current record in the cursor can be found in the value field. When you handled the value, you can navigate to the next record in the cursor by calling the continue function on the result object.


    When working with the Indexed DB prototype, you have to make another approach. That’s why I created a recursive function for it. (cursor_get_record). The only parameter I need to pass is the result object I get out of the parameter that is provided when the call for the cursor was successful.



       1: function cursor_get_record(cur){  
       2:     cur.move(); 
       3:     if(cur.value)
       4:     {
       5:         // Present the data
       6:         cursor_get_record(cur);
       7:     }
       8: }

    With the move method we fetch the next record from the cursor. If the cursor contains a value, you can handle it and afterwards you call the current method to fetch the next record of cursor.

    Wednesday, November 9, 2011

    Indexed DB: Manipulating data

    The first thing we need to do when we want to read data, is to make sure we have data available in our database. So this post will handle inserting and updating data into the database.

    For inserting data we have 2 possibilities: we have an add and a put method available. Both methods take the same parameters, but there is a slight difference between them. When we use the add method, we need to be sure that no other object with the same key is already added in the database. If it does, an constraint error (CONSTRAINT_ERR) will occur. So the add method will only be used when we want to add data that won’t be overwritten when it is already present with the same key in the database.

    The put method we will usually use when we want to update data. If the data isn’t present yet with the same key, the data will be inserted. If you want some more information about the steps for storing a record into an object store, you can find this here on the W3C specs site.

    Make sure when you are starting a new transaction to manipulate data, you will use a read_write transaction. Otherwise you will receive an READ_ONLY_ERR when calling the methods.

    The put and the add method are both available in the IObjectStore interface. This means once we have created the transaction where we will work in, we need to retrieve the object store we want to work on. We do this by calling the ObjectStore method on the transaction. The only thing we need to pass is the name of the object store.
    var txn = dbconnection.transaction([“ObjectStoreName”]
                                                     , IDBTransaction.READ_WRITE);
    var objectStore = txn.objectStore(“ObjectStoreName”);
    Once we have the object store object, we can start adding data to it. The result of the add operation is an IDBRequest object. This means the onsuccess will be called when the operation was successful and an onerror will be called when something went wrong.
    When the onsuccess function gets called, the result of this action will be the key of the object in the object store
    var addresult = objectStore.add(data);
    addresult.onsuccess = function (event) {
          // Adding data is successful
          // Commit the transaction when using IE9
          var key;

          // IE 9 implementation
          if(event.result){
                key = event.result
          }
          // IE 10, Chrome and Firefox implementation
          else if (addresult.result){
                key = addresult.result;
          }
    };
    addresult.onerror = function (event) {
          // Handle the error
    }
    If we use a put for adding or changing data, we get the same structure only we will use the put method instead.
    var putresult = objectStore.put(data);
    putresult.onsuccess = function (event) {
          // Putting data is successful
          // Commit the transaction when using IE9
    };
    putresult.onerror = function (event) {
          // Handle the error
    }
    Now that we have added some data to our database, we can start retrieving it and present it to our users…

    Wednesday, November 2, 2011

    Indexed DB: Transactions

    Today, I’ll handle the transaction subject. As said in previous posts, every request made to the database needs to be done in a transaction. So for every read or write request we need to create a new transaction. There for we need a database connection and 2 argument that we will pass to the transaction method.

    The first argument will define the scope of the transaction. Here we pass all the object stores we want to use during the transaction. We do this by passing the object store names in an array. Providing an empty array will allow to use all available object stores in the database.

    The second argument is the mode we want to use to access the data. This is an optional parameter and if not provided the transaction will be created read only by default. If you want to manipulate data, you’ll need to pass IDBTransaction.READ_WRITE. There is also a third mode, CHANGE_VERSION, but this type of transaction can only be created in the setVersion method. More about this method can be found in my post about Indexed DB: Defining the database structure

    var txn = dbconnection.transaction([], IDBTransaction.READ_WRITE);

    txn.oncomplete = function () {
         // Transaction successful
    };
    txn.onabort = function () {
        // Code to handle the abort of the transaction
    };
    txn.onerror = function () {
    // Code to handle the error
    };

    For a transaction we have 3 possible outcomes. The first one is that the transaction is committed and so got completed. This function will be called if the transaction was successful and in the case we were reading data, we can here write the code to show the retrieved data it in the browser. Keep in mind that a transaction in the Indexed DB API is committed by default, but in IE9 you still need to do this manually.

    The onabort function will be called when we manually call the abort function on the transaction object. This means the transaction must do a rollback.

    The last one will handle all the errors that can occur within the transaction, this will also mean that the transaction must rollback.

    Now that we know how to create a transaction, we can start retrieving and manipulating data from our database. This will be the next subject of my future posts.

    Sunday, October 2, 2011

    Indexed DB: Creating, opening and deleting a database

    In my last post I provided some more information about the structure of the Indexed DB database. Today, I will dive into code and tell you how to create, open and delete a database. There are some small differences between the browsers, but I will handle that at the specific parts of code.

    The code I will show will be the Asynchronous implementation of the API. In my previous post I have said no browser does implement the synchronous API, but I was wrong. The IE 10 Preview 3 also implements the Synchronous implementation, but you should only use it in combination with web workers, because it freezes the UI if you don’t.

    Initializing the Indexed DB API

    First things first: the Indexed DB API is still in draft, so this means the unified Indexed DB property on the window element isn’t implemented yet. Every browser uses his on prefix to use the API, but we can assign this to the Indexed DB property. This gives us a way to us the Indexed DB on the same way.

    Initializing the Firefox implementation

    if (window.mozIndexedDB) {
                window.indexedDB = window.mozIndexedDB;
                window.IDBKeyRange = window.IDBKeyRange;
                window.IDBTransaction = window.IDBTransaction;
    }

    Initializing the Chrome implementation

    if (window.webkitIndexedDB) {
                window.indexedDB = window.webkitIndexedDB;
                window.IDBKeyRange = window.webkitIDBKeyRange;
                window.IDBTransaction = window.webkitIDBTransaction;
    }

    Initializing the IE 10 Preview 3 implementation (You’ll need to be running the Windows 8 Developer Preview to run this)

    if (window.msIndexedDB) {
                window.indexedDB = window.msIndexedDB;
    }

    Initializing the IE prototype implementation

    if (navigator.appName == 'Microsoft Internet Explorer') {
                window.indexedDB = new ActiveXObject("SQLCE.Factory.4.0");
                window.indexedDBSync = new ActiveXObject("SQLCE.FactorySync.4.0");

                if (window.JSON) {
                    window.indexedDB.json = window.JSON;
                    window.indexedDBSync.json = window.JSON;
                } else {
                    var jsonObject = {
                        parse: function (txt) {
                            if (txt === "[]") return [];
                            if (txt === "{}") return {};
                            throw { message: "Unrecognized JSON to parse: " + txt };
                        }
                    };
                    window.indexedDB.json = jsonObject;
                    window.indexedDBSync.json = jsonObject;

                }

                // Add some interface-level constants and methods.
                window.IDBDatabaseException = {
                    UNKNOWN_ERR: 0,
                    NON_TRANSIENT_ERR: 1,
                    NOT_FOUND_ERR: 2,
                    CONSTRAINT_ERR: 3,
                    DATA_ERR: 4,
                    NOT_ALLOWED_ERR: 5,
                    SERIAL_ERR: 11,
                    RECOVERABLE_ERR: 21,
                    TRANSIENT_ERR: 31,
                    TIMEOUT_ERR: 32,
                    DEADLOCK_ERR: 33
                };

                window.IDBKeyRange = {
                    SINGLE: 0,
                    LEFT_OPEN: 1,
                    RIGHT_OPEN: 2,
                    LEFT_BOUND: 4,
                    RIGHT_BOUND: 8
                };

                window.IDBRequest = {
                    INITIAL: 0,
                    LOADING: 1,
                    DONE: 2
                };

                window.IDBTransaction = {
                    READ_ONLY: 0,
                    READ_WRITE: 1,
                    VERSION_CHANGE: 2
                };

                window.IDBKeyRange.only = function (value) {
                    return window.indexedDB.range.only(value);
                };

                window.IDBKeyRange.leftBound = function (bound, open) {
                    return window.indexedDB.range.leftBound(bound, open);
                };

                window.IDBKeyRange.rightBound = function (bound, open) {
                    return window.indexedDB.range.rightBound(bound, open);
                };

                window.IDBKeyRange.bound = function (left, right, openLeft, openRight) {
                    return window.indexedDB.range.bound(left, right, openLeft, openRight);
                };
    }

    Creating and opening a database

    Once we have our Indexed DB initialized, we can start the real thing. Creating a database. There is no specific method to create a new database, but when we call the open method, a database will be created if it doesn’t exists. After that a connection to the database will be opened. To open or create a database, the only thing you need is a name.

    var dbreq = window.indexedDB.open(“database name”);

    dbreq.onsuccess = function (event) {
            var dbConnection;
            // IE prototype Implementation
            if (event.result) {
                    dbConnection = event.result;
            }
            //IE 10 Preview 3, Firefox & Chrome implementation
            else {
                    dbConnection = dbreq.result;
            }
    }

    dbreq.onerror = function (event) {
           // Log or show the error message
    }

    Calling the open method, will return an IDBRequest object. On this object we can attach an onerror function and an onsuccess function. The onerror event will provide handling the error returned by the open function, here you can provide logging or error handling. In the onsuccess event, the database connection will be opened. We can now start using the database connection.

    As you can see in code, IE prototype implementation is handling this on an other way then IE 10 Preview 3, Firefox and Chrome. In the IE prototype implementation we get the result (the database connection) out the parameter that is passed trough with the onsuccess function. For IE 10 Preview 3, Firefox and Chrome, we get the result from the IDBRequest object. This is the implementation that was defined by the W3C.

    Deleting a database

    Deleting the database will happen almost the same way as opening a database. You provide the name of the database you want to delete, and the database will get deleted. The error event will only be called when something goes wrong. Providing a database name that doesn’t exists, will not result in an error and the onsuccess function will get called.

    var dbreq = window.indexedDB.deleteDatabase(“database name”);
            dbreq.onsuccess = function (event) {
                 // Database deleted
            }
            dbreq.onerror = function (event) {
                // Log or show the error message
            }

    Now we can create, open and delete a database. In one of my next posts, I will show you how you can create/change your database structure.