Storing images and files in IndexedDB

The other day we wrote about how to Save images and files in localStorage, and it was about being pragmatic with what we have available today. There are, however, a number of performance implications with localStorage – something that we will cover on this blog later – and the desired future approach is utilizing IndexedDB. Here I’ll walk you through how to store images and files in IndexedDB and then present them through an ObjectURL.

The general approach

First, let’s talk about the steps we will go through to create an IndexedDB data base, save the file into it and then read it out and present in the page:

  1. Create or open a database.
  2. Create an objectStore (if it doesn’t already exist)
  3. Retrieve an image file and create a blob from that image file
  4. Initiate a database transaction
  5. Save that blob into the database
  6. Read out that saved file and create an ObjectURL from it and set it as the src of an image element in the page

Creating the code

Let’s break down all parts of the code that we need to do this:

Create or open a database.


// IndexedDB
var indexedDB = window.indexedDB || window.webkitIndexedDB ||
        window.mozIndexedDB || window.OIndexedDB || window.msIndexedDB,
    IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction ||
        window.OIDBTransaction || window.msIDBTransaction,
    dbVersion = 1.0;

// Create/open database
var request = indexedDB.open("elephantFiles", dbVersion);

request.onsuccess = function (event) {
console.log(“Success creating/accessing IndexedDB database”);
db = request.result;

db.onerror = function (event) {
console.log(“Error creating/accessing IndexedDB database”);
};

// Interim solution for Google Chrome to create an objectStore. Will be deprecated
if (db.setVersion) {
if (db.version != dbVersion) {
var setVersion = db.setVersion(dbVersion);
setVersion.onsuccess = function () {
createObjectStore(db);
getImageFile();
};
}
else {
getImageFile();
}
}
else {
getImageFile();
}
}

// For future use. Currently only in latest Firefox versions
request.onupgradeneeded = function (event) {
createObjectStore(event.target.result);
};

The intended way to use this is to have the onupgradeneeded event triggered when a database is created or gets a higher version number. This is currently only supported in Firefox, but will soon be in other web browsers. If the web browser doesn’t support this event, you can use the deprecated setVersion method and connect to its onsuccess event.

Create an objectStore (if it doesn’t already exist)


// Create an objectStore
console.log("Creating objectStore")
dataBase.createObjectStore("elephants");

Here you create an ObjectStore that you will store your data – or in our case, files – and once created you don’t need to recreate it, just update its contents.

Retrieve an image file and create a blob from that image file


// Create XHR and BlobBuilder
var xhr = new XMLHttpRequest(),
    blobBuilder = new (window.BlobBuilder || window.MozBlobBuilder || window.WebKitBlobBuilder || window.OBlobBuilder || window.msBlobBuilder),
    blob;

xhr.open("GET", "elephant.png", true);
// Set the responseType to arraybuffer. "blob" is an option too, rendering BlobBuilder unnecessary, but the support for "blob" is not widespread enough yet
xhr.responseType = "arraybuffer";

xhr.addEventListener("load", function () {
    if (xhr.status === 200) {
        console.log("Image retrieved");

        // File as response
        var response = xhr.response;

        if (blobBuilder) {
            // Append the response to the BlobBuilder
            blobBuilder.append(response);
            // Create a blob with the desired MIME type
            blob = blobBuilder.getBlob("image/png");
        }
        else {
            blob = new Blob([response]);
        }

        if (blob) {
            // Put the received blob into IndexedDB
            putElephantInDb(blob);
        }
    }
}, false);
// Send XHR
xhr.send();

This code gets the contents of a file as an arraybuffer. You could also use responseType = "blob"; to get a blob back directly. However, currently that’s only supported in Firefox.
Once you have received the entire file, you create a blob and then send it to the function to store it in the database.

Initiate a database transaction


// Open a transaction to the database
var transaction = db.transaction(["elephants"], IDBTransaction.READ_WRITE);

To start writing something to the database, you need to initiate a transaction with an objectStore name and the type of action you want to do – in this case read and write.

Save that blob into the database


// Put the blob into the dabase
transaction.objectStore("elephants").put(blob, "image");

Once the transaction is in place, you get a reference to the desired objectStore and then put your blob into it and give it a key.

Read out that saved file and create an ObjectURL from it and set it as the src of an image element in the page


// Retrieve the file that was just stored
transaction.objectStore("elephants").get("image").onsuccess = function (event) {
    var imgFile = event.target.result;
    console.log("Got elephant!" + imgFile);

    // Get window.URL object
    var URL = window.URL || window.webkitURL;

    // Create and revoke ObjectURL
    var imgURL = URL.createObjectURL(imgFile);

    // Set img src to ObjectURL
    var imgElephant = document.getElementById("elephant");
    imgElephant.setAttribute("src", imgURL);

    // Revoking ObjectURL
    URL.revokeObjectURL(imgURL);
};

Use the same transaction to get the image file you just stored, and then create an objectURL and set it to the src of an image in the page.
This could just as well, for instance, have been a JavaScript file that you attached to a script element, and then it would parse the JavaScript.

The complete code

So, here’s is the complete working code:


(function () {
    // IndexedDB
    var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.OIndexedDB || window.msIndexedDB,
        IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.OIDBTransaction || window.msIDBTransaction,
        dbVersion = 1.0;

    // Create/open database
    var request = indexedDB.open("elephantFiles", dbVersion),
        db,
        createObjectStore = function (dataBase) {
            // Create an objectStore
            console.log("Creating objectStore")
            dataBase.createObjectStore("elephants");
        },

        getImageFile = function () {
            // Create XHR and BlobBuilder
            var xhr = new XMLHttpRequest(),
                blobBuilder = new (window.BlobBuilder || window.MozBlobBuilder || window.WebKitBlobBuilder || window.OBlobBuilder || window.msBlobBuilder),
                blob;

            xhr.open("GET", "elephant.png", true);
            // Set the responseType to arraybuffer. "blob" is an option too, rendering BlobBuilder unnecessary, but the support for "blob" is not widespread enough yet
            xhr.responseType = "arraybuffer";

            xhr.addEventListener("load", function () {
                if (xhr.status === 200) {
                    console.log("Image retrieved");

                    // File as response
                    var response = xhr.response;

                    if (blobBuilder) {
                        // Append the response to the BlobBuilder
                        blobBuilder.append(response);
                        // Create a blob with the desired MIME type
                        blob = blobBuilder.getBlob("image/png");
                    }
                    else {
                        blob = new Blob([response]);
                    }

                    if (blob) {
                        // Put the received blob into IndexedDB
                        putElephantInDb(blob);
                    }
                }
            }, false);
            // Send XHR
            xhr.send();
        },

        putElephantInDb = function (blob) {
            console.log("Putting elephants in IndexedDB");

            // Open a transaction to the database
            var transaction = db.transaction(["elephants"], IDBTransaction.READ_WRITE);

            // Put the blob into the dabase
            transaction.objectStore("elephants").put(blob, "image");

            // Retrieve the file that was just stored
            transaction.objectStore("elephants").get("image").onsuccess = function (event) {
                var imgFile = event.target.result;
                console.log("Got elephant!" + imgFile);

                // Get window.URL object
                var URL = window.URL || window.webkitURL;

                // Create and revoke ObjectURL
                var imgURL = URL.createObjectURL(imgFile);

                // Set img src to ObjectURL
                var imgElephant = document.getElementById("elephant");
                imgElephant.setAttribute("src", imgURL);

                // Revoking ObjectURL
                URL.revokeObjectURL(imgURL);
            };
        };

    request.onerror = function (event) {
        console.log("Error creating/accessing IndexedDB database");
    };

    request.onsuccess = function (event) {
        console.log("Success creating/accessing IndexedDB database");
        db = request.result;

        db.onerror = function (event) {
            console.log("Error creating/accessing IndexedDB database");
        };

        // Interim solution for Google Chrome to create an objectStore. Will be deprecated
        if (db.setVersion) {
            if (db.version != dbVersion) {
                var setVersion = db.setVersion(dbVersion);
                setVersion.onsuccess = function () {
                    createObjectStore(db);
                    getImageFile();
                };
            }
            else {
                getImageFile();
            }
        }
        else {
            getImageFile();
        }
    }

    // For future use. Currently only in latest Firefox versions
    request.onupgradeneeded = function (event) {
        createObjectStore(event.target.result);
    };
})();

Web browser support

IndexedDB
Supported since long (a number of versions back) in Firefox and Google Chrome. Planned to be in IE10, unclear about Safari and Opera.
onupgradeneeded
Supported in latest Firefox. Planned to be in Google Chrome soon and hopefully IE10. Unclear about Safari and Opera.
Storing files in IndexedDB
Supported in Firefox 11 and later, and in latest Google Chrome. Hopefully IE10 will support it. Unclear about Safari and Opera.
XMLHttpRequest Level 2
Supported in Firefox and Google Chrome since long, Safari 5+ and planned to be in IE10 and Opera 12.
BlobBuilder
Supported in Firefox and Google Chrome since long, and is planned to be in IE10. Unclear about Safari and Opera.
responseType “blob”
Currently only supported in Firefox. Will soon be in Google Chrome and is planned to be in IE10. Unclear about Safari and Opera.

Demo and code

I’ve put together a demo with IndexedDB and saving images and files in it where you can see it all in action. Make sure to use any Developer Tool to Inspect Element on the image to see the value of its src attribute. Also make sure to check the console.log messages to follow the actions.

The code for storing files in IndexedDB is also available on GitHub, so go play now!

View full post on Mozilla Hacks – the Web developer blog

Tagged on: , , ,

2 thoughts on “Storing images and files in IndexedDB

  1. Robert Nyman

    Thanks for asking!
    Sorry, I should have been more clear about that: when it comes to storing files specifically, it works in Firefox 11 and later – please try Firefox Beta or Firefox Aurora and latest Google Chrome.

    I’ll update the blog post with that info.

    I hope that IE10 will support storing files before it is officially released.

  2. Florian

    Hello,

    Are you sure your demo is working on Firefox because on my side, it throw me an exception : “The object could not be cloned. ?

    This exception is also thrown in IE 10.

    This exception is thrown when you try to store a “complexe” object into the database.

    I was wondering that because I have the same issue on an application i am working on, and the only workaround I found is to store images in indexedDB as base64 string.

    Florian

Leave a Reply