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: }

Thursday, January 12, 2012

Offline Application Caching: Make your web application offline available

In my previous post I have been talking about IndexedDB, an in-browser database. This means, once we can persist data on the client, it’s possible to make our web application offline available. For this we have the offline application caching API.

When we want to take our application offline, the first thing we need to do is creating a manifest file. The main function of this file is describing which files have to be offline available. These files will be downloaded when the users visits the web application for the first time. Also when the manifest file has been changed since the last visit, these files will be downloaded again.

Manifest file

The beginning of the manifest file is always the same:

CACHE MANIFEST

Below this it’s recommended to place a comment with some kind of version number/date. This is necessary when you want to force the browser to download the files again because you changed one or more files. This is because browsers will only update their application cache when the manifest file is changed and not if one of the resources described in the file change. This means whenever you change a file described in the manifest file, you should update the version number in a comment.

# v3 2012-01-11

Now we have three parts in the manifest file. The first one is the explicit section. This means all files that are described in this section will be downloaded. This is also the default section, so it isn’t necessary to provide the ‘CACHE:’ title explicitly. Note that the colon after the title of the section is required. In the explicit section all kinds of files are allowed, for example: html files, images, js files, … Every resource you want to describe, takes one line.

CACHE:
/Detault.htm
/Scripts/jquery-1.7.1.min.js
/Css/ui-lightness/jquery-ui-1.8.16.custom.css
/Css/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png

The next part of the manifest file will describe which files should never be cached. This is the case for pages who relay on code that is executed on the server. For example a logon page. In this section you can use the “*” character as wildcard.That’s a fancy way of saying that anything that isn’t in the appcache can still be downloaded from the original web address, as long as you have an internet connection. This also means all resources on the webpage will be cached, even if they are hosted on an other domain.

The wildcard is important if we want to provide an “open-ended” offline web application. This is common for large web applications, whom we want to make offline available such as Wikipedia.

NETWORK:
/Logon.apsx
/Secure
*

The last part of the file will describe the fallback mechanisms. If you are working offline, and you request a resources that isn’t offline available, the configured resources will be shown.

FALLBACK:
/ /HTML/Offline.htm

Offline available webpages

All webpages we want to use offline need the manifest attribute with a reference to the manifest file. This way the browser knows where he can locate the file describing the offline resources.

<html manifest="/cache.manifest">

It doesn’t matter where the manifest file is located on the server. The only thing you need is the correct path to it. There are 2 extensions that are commonly used for the manifest file. These are “.manifest” and “.appcache”. It doesn’t matter which one you use, as long as the extensions are recognized by your webserver. In IIS 7.0 or higher the a MIME-type for .manifest exists.

If u are using an Apache webserver, you can use the AddType directive in your side-wide httpd.conf by adding the following lines:

AddType text/cache-manifest .appcache "access plus 0 seconds"
AddType text/cache-manifest .manifest "access plus 0 seconds"

To avoid the risk of caching the manifest files, it is a good idea to set expires headers on your web server for manifest files so they expire immediately. In Apache you can configure this as follows:

ExpiresByType text/cache-manifest "access plus 0 seconds"

For IIS 6.0 you can find the explanation here.

Application cache events

The offline application cache API is also provided with some events:

Event Description
Checking Fires when the browsers notices a manifest attribute in the html tag, even in case you already visited the page
Downloading Fires if the browser starts downloading the files described in the manifest file
Progress

Fires periodically while downloading.Contains information about the number of files that have been downloaded and the number of files that are still queued

Cached

Fires when all the files in the manifest file are downloaded. The web app is now fully cached and ready to use offline. This is only the case if it’s the first time data from the manifest file is downloaded.

Noupdate

Fires if you visit an offline-enabled page and the manifest file hasn’t changed.

Updateready

Fires when downloading of the files described in the manifest file was successful. The new version web app is now fully cache. This is the case if the data from the manifest file has ever been downloaded in the past.

Error Fires when ever something goes wrong. Possible causes:
- HTTP error 404 (Page not found)
- HTTP error 410 (Permanently Gone)
- Page failed to download properly
- Manifest file changed while updating
- Browser failed to download one of the resources listed in the manifest file
Obsolete The manifest was not found. Possible causes:
- HTTP error 404 (Page not found)
- HTTP error 410 (Permanently Gone)
This means the application cache has been deleted.

It’s a best practice to call the window.applicationCache.SwapCache() method when the Updateready event is fired. This forces the browser to switch to the most recent application cache. If you forget to do this, the user needs to reload the webpage in order to take advantage of the new version.

Browser State

In the window object we have an interface “NavigatorOnline” which provides us information about the state of the browser. If the attribute “onLine” is false, we can be sure the browser is definitely offline. In case the attribute is true, the browser might be online, but that isn’t for sure.

window.navigator.onLine

There are also 2 events. The online and offline event that are fired when the browser either goes online or offline. You can attach the events on the following ways:

  • using the addEventListener on the window, document or document.body
  • Setting the .ononline or .onoffline properties on document or document.body to a JS function. (For some reason the window.ononline and window.onoffline will not work)
  • By specifying the ononline or onoffline attributes on the <body> element in the HTML.

Note when using the first 2 solutions, you can only attach the events after the page load event.

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.