Creating and opening IndexedDB objects

The Indexed Database (IndexedDB) specification defines an API for creating and managing database objects on a user's client device, whether that device is a PC running a web browser, a tablet running a Windows Store appusing JavaScript, or another device.

Here we show how to create and open the database objects used to track tags associated with images in a photo gallery.

The previous article, Determining database needs, identified the following requirements:

  • Store image details as objects containing attributes and values.
  • Store an array of keywords (tags) associated with an image.
  • Locate a given image in the database and return its details.
  • Locate and return a set of tags associated with an image.
  • Return a list of all tags in the database.
  • Return a list of all images associated with a given image.

In order to create the objects to serve these needs; it's important to understand how IndexedDB manages data.

In a traditional relational database management system (RDMS), data is grouped into sets of related values called records. Each record contains fields and each field contains a value. Fields share the same data type between records and all records contains the same set of fields. Records are collected into tables and tables can be organized by indexes, which sort data according to the values of fields defined by the index.

IndexedDB uses a similar, though slightly different approach. Records are JavaScript objects that define attributes that hold values. Records are collected into object stores and object stores can be sorted using indexes that sort records according to attributes shared by records in the object store.

While that sounds pretty similar, it's important to note that IndexedDB records don't have to share the same set of attributes. One record can have five attributes while another might have 30. However, all records in an object store must share at least one attribute in common, the key path. The key path is an attribute whose value uniquely identifies a record. This value is called the key value (or key for short).

The following IndexedDB objects meet the above needs:

  • The ImageDetails object store, which contains the individual details for each image (title, description, URL, and so on).
  • The IxImagesByURL index, which sorts images by their URLs. In theory, URLs uniquely identify an image, in part because two different images cannot share the same URL. In practice, however, different URLs can refer to the same image. As a result, the ImageDetails object store use a different key. More on that in a bit.
  • The IxImagesByName index, which sorts images by their filename.
  • The ImageTags object store, which contains the individual tags associated with a given image, along with the key value of the details for the image that the tag is associated with.
  • The IxTagsByWord index, which sorts the ImageTags object store alphabetically by the tag.
  • The IxTagsByImage index, which sorts the ImageTags object store according to the ImageID saved with the tag.

To create these objects, it's necessary to create a database, which is a container for a related set of objects, generally those used in an application. To do that, it's necessary to access IndexedDB features.

Accessing IndexedDB features

Use the window.indexedDB property to access IndexedDB features. For best results, use feature detection, as shown in the following example:

  var oIndexDB = null;
  if ( window.indexedDB ) { 
     oIndexDB = window.indexedDB
  } 
  if ( oIndexDB == null ) {
     handleError();
  }

This example includes additional error-checking because the property might not be available, even when run in a browser that supports IndexedDB.

For security and performance reasons, IndexedDB is supported only for Windows Store apps using JavaScript and for webpages displayed in Internet Explorer 10 using the "http" or "https" protocols.

As a result, there's no guarantee that the IndexedDB property is available to the window object; plan accordingly and use effective error handling techniques to manage the consequences.

Opening a database

IndexedDB uses database objects to serve as a container for object stores and indexes. To open a database, use the open method of the IndexedDB property, as shown here:

try {

  // hDB is a global variable.
  hDb = null; 
  if (window.indexedDB) {

    var req = window.indexedDB.open( "IxImageGallery", 1.0 );

    req.onsuccess = function(evt) { 
       hDB = evt.target.result;
    }      

    req.onerror = failureHandler();
    req.onblocked = blockedHandler();

    req.onupgradeneeded = function(evt) {
       createDatabaseObjects( evt.target.result );
    } 
  } catch( ex ) {
    handleException( ex );
  }

You might notice that this example looks a bit different than the traditional JavaScript example, what with its event handlers and anonymous function.

Traditional JavaScript programming uses a linear (or synchronous) programming model; statements are executed in turn and control does not pass from one statement to the next until the first statement has finished. If a statement returns a value, the return value represents the results of the operation.

As implemented by Internet Explorer 10, IndexedDB is an asynchronous API. Instead of receiving results from a statement, you receive a request object. You then define event handlers for the request object to respond to different types of results. If the request succeeds, a success event is fired. If a problem occurs, an error event fires.

In this example, the value returned by the open method is an IDBOpenDBRequest object. When the database is successfully opened, a handle to the open database is returned as the target.target.result property of the event object passed to the success event handler.

This example also defines additional handlers for different request results. If an error prevents the database from opening, the failureHandler function is called. Likewise, the blockedHandler function is called when the open request is blocked by another transaction against the database. (A transaction will be blocked if there is an open connection to the database and the other connection uses a lower version number.)

The upgradeneeded event allows you to create database objects, such as object stores and indexes; it's triggered when you open a database with a higher version number than the one previously used to open the database. If the database doesn't exist, it's created and then opened. As with the success handler, the event object passed to the upgradeneeded event also includes a handle to the database in the target.result property. The anonymous function in this example passes that handle to a function called createDatabaseObjects.

You can only create object stores and indexes in the context of an upgradeneeded event, which occurs in the context of a "version change" transaction. A transaction is a group of operations that must all succeed or they all fail. For more info, see Managing data with transactions.

As an aside, you'll notice that the success handler for this example assigns the database handle to a global variable, which is a common technique for asynchronous APIs that return handles.

You'll also notice that the upgradeneeded event doesn't refer to the global variable when calling the function. Because the upgradeneeded event fires before the success event, the global variable has not yet been assigned the database handle. Attempting to access the global variable would generate an exception and prevent the database from being created or opened.

Creating object stores and indexes

In the previous example, the upgradeneeded event calls a function designed to create the object stores and indexes associated with the tag cloud, shown here:

function createDatabaseObjects( dbHandle ) {

  try {

   if ( dbHandle == null ) {
      updateResults( "Can't create database objects; the database is not open." );
   } else {
   
      var oOptions = { keyPath : "RecordID", autoIncrement : true };
      var oStore = dbHandle.createObjectStore( "ImageDetails", oOptions );

      var oIxOptions = { unique: false, multientry: false };
      oStore.createIndex( "IxImagesByName", "FileName", oIxOptions );
      oStore.createIndex( "IxImagesByURL", "FileURL", oIxOptions );

      oStore = dbHandle.createObjectStore( "ImageTags", oOptions );
      oStore.createIndex( "IxTagsByWord", "TagWord", oIxOptions );
      oStore.createIndex( "IxTagsByImage", "ImageID", oIxOptions );

 } catch (ex) { 
     handleError(ex.message); 
 }
}

This function creates two object stores and four indexes designed to handle the database requirements. The keys for the object stores are auto-incrementing values that will be generated for a record when it's added to the object store. The key path for each object store is called RecordID. When a record is added to the ImageDetails object store, for example, a new value will be assigned to the RecordID attribute for that record. As you'll see, this value will be used to assign individual tags to specific images.

In general, the URLassociated with an image is considered unique; that is, only one image is allowed for each unique URL. Because of this, it might be tempting to associate the image's URL with each tag. While it is true that no two images share the same URL, multiple URLs might point to the same image.

Instead, a unique ID value is calculated for each image details record and that ID is associated with each tag assigned to the image. This allows multiple tags to be efficiently associated with an image and prevents maintenance problems if the image's URL changes.

The design of your object stores and indexes is critical for the success of your IndexedDB projects. If you're familiar with relational database concepts, you find that practices such as normalization can be used effectively in IndexedDB applications.

Summary

This article showed how to open an IndexedDB database and how to use that database to create database objects. It also introduced the idea of transactions, which are discussed in Managing data with transactions.

Contoso Images photo gallery

Managing data with transactions

How to create a tag cloud using IndexedDB

Internet Explorer 10 Samples and Tutorials