Showing posts with label IDBTransaction. Show all posts
Showing posts with label IDBTransaction. Show all posts

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

    Tuesday, October 25, 2011

    Indexed DB: Defining the database structure

    In previous post I have been talking about the basic concepts in the Indexed DB and how you can open/create a database and a database connection. Today I will tell you how you can construct your own database structure. This includes defining the object store where we can store our objects and defining indexes. These indexes will be used when searching into the objects we store in our database.

    version_change transaction

    There exists only one way to define your object stores and indexes, and this is in a VERSION_CHANGE transaction. We can start this transaction by calling the setVersion method on the database connection. When a database is initially created, the version of the database is an empty string. By passing a string as version to the setVersion method, we add object stores and indexes. This we do by defining the onsuccess event. We can also provide error handeling trough the onerror event.

    var dbreq = dbConnection.setVersion(version);
    dbreq.onsuccess = function (event) {
          // Here we put code to create object stores or indexes
    }

    dbreq.onerror = function (event) {
          // Here we put code to handle an error while changing the version
    }

    dbreq.onabort = function (event) {
          // Here we put code to handle an abort when changing the database version
    }

    If we are using the IE 9 Indexed DB prototype, we need to commit the transaction. In all other browsers this is not necessary because the implementation of Indexed DB determines that every transaction is committed by default when closed. If a transaction should be aborted you have to do this explicitly.

    dbreq.onsuccess = function (event) {
           var txn;
           if (event.result) {
                 txn = event.result;
           }
           // Here we put code to create object stores or indexes
          
           if(typeof(txn.commit) != "undefined")
           {
                txn.commit();
           }
    }

    Because a database can’t have multiple versions at one time, the only way you can change the version is by closing all other connections to the database. This also means no new connections can’t be opened until the VERSION_CHANGE transaction is completed. A blocked event will be raised when a connection to the database is still open when changing the version.

    dbreq.onblocked = function (event) {
    // Here we put code to handle an abort when changing the database version
    }

    Creating an objectStore

    Once we started the VERSION_CHANGE transaction, we can start adding or deleting object stores. We can do this by calling the createObjectStore method. As first parameter we need to pass the name of the object store. Optionally you can can add two extra parameters. The first one is JSON object with two fields. The first field is a keyPath, when this is provided, the key of the object store will be determined by the field that has the name given in the string of the key path. The second field determines if the object store should have a key generator.

    The last parameter we pass is the same as the autoIncrement field in the second parameter. We do this to enable the object store to have a key generator in the IE9 prototype. When using Firefox, Chrome and IE 10, you don’t need to pass this parameter.

    The following combinations of a KeyPath and autoIncrement are possible:

    Key path Auto increment Description
    Empty False This object store can hold any kind of value, even primitive values like numbers and strings. You must supply a separate key argument whenever you want to add a new value to the object store.
    Provided False This object store can only hold JavaScript objects. The objects must have a property with the same name as the key path.
    Empty True This object store can hold any kind of value. The key is generated for you automatically, or you can supply a separate key argument if you want to use a specific key.
    Provided True This object store can only hold JavaScript objects. Usually a key is generated and the value of the generated key is stored in the object in a property with the same name as the key path. However, if such a property already exists, the value of that property is used as key rather than generating a new key.

    dbConnection.createObjectStore("ObjectStoreName"
                                                  , { "keyPath": "keyPath"
                                                    , "autoIncrement": true }
                                                  , true);

    removing an object store

    When we want to remove an object store, the only thing we need to pass is the name of the object store we want to remove.

    dbConnection.deleteObjectStore("ObjectStoreName");

    Creating an INDEX

    Once we created our object store, we can add indexes to it. These indexes are object stores as well, but the key of these key/value pairs is the key path of the index. This means that in this object store that multiple keys with the same value are allowed. When we want to create an index we need to provide a name and a key path for them. Optionally we can provide a JSON object with a unique and multiplerow field.

    Field Description Support
    unique A boolean value indicating whether the index allows duplicate values. If this attribute is "true", duplicate values are not allowed. Duplicate values are allowed when this attribute is "false" (the default value). This means when you add an object to an object store where an index with a unique value on "true". You will get an exception when you add an object with a duplicate value in the field of the key path of the index. IE 10
    FF
    Chrome
    multiplerow A boolean value that describes determines the results when multiple rows in the object store match individual key values. When this happens, the resulting object is an array. If this parameter is "true", the resulting array contains a single item; the key of the item contains an array of the matching values. When this parameter is "false" (the default), the result array contains one item for each item that matches the key value. IE 10

    store.createIndex("IndexName"
                             , "keyPath"
                             , { unique: false });

    If we want to create an index in IE 9 Indexed DB prototype, we need to pass the boolean value for the uniqueness as parameter and not as a JSON object.

    store.createIndex("IndexName"
    , "keyPath"
    , false);

    Deleting an index

    The last action we can do in the VERSION_CHANGE event is deleting an existing index. We can do this by passing the name of the index we want to delete.

    store.deleteIndex("IndexName");