FileReader API and IndexedDB
http://www.irw.co.uk/media/171288/filereader-api-indexed-db.jpg
While the World Wide Web was in its infancy, websites would generally be made up of static content pages, providing simple information with very limited functionality but this is not your fathers’ internet.
Browsers, developers and as a result users (even if they don’t realise it) are embracing the changes brought about by HTML5, resulting in websites becoming more like applications than their static-content-based ancestors could ever have imagined.
Taking advantage of this, here at IRW Systems we have combined various features of HTML5 to create a website that would function fully with or without a connection to the internet.
Within this blog post, we will delve into two of the technologies that worked particularly well together: the FileReader API and the wonderful world of offline storage using IndexedDB. Alternatively, you could just skip to the end and download the code sample but, my fellow users, where is the fun in that?
To begin using the FileReader API we are going to need a way for the user to upload a file. We could go for a drag and drop interface but for simplicity, we will use a plain old input element, as we are focusing on what you can do with the files, rather than how to get access to them.
HTML
<input id="myBestPalIsFileReader" type="file" accept="image/*" multiple="multiple" />
The input element above allows us to upload a file.
Specifically for this example the files we are going to use are images and as I am feeling pretty adventurous we will allow multiple files to be uploaded at once.
Input elements are pretty standard but where this gets interesting is in the JavaScript, here we will intercept the file; read its contents; get to know it a little then store it away safely in our database.
JavaScript
<script type="text/javascript">
function ReadFile(currentFile) {
var reader = {
progress: function (e) {
//Useful for notifying the user
},
success: function (e) {
//File processed, e.target.result contains the array buffer
currentFile.arrayBuffer = e.target.result;
reader.complete();
},
error: function (e) {
var item = e;
//Handle the error properly
reader.complete();
},
complete: function () {
//Pull out some information about the file
var currentFileJson = {
name: currentFile.name,
type: currentFile.type,
size: currentFile.size,
lastModified: currentFile.lastModifiedDate,
arrayBuffer: currentFile.arrayBuffer
};
//Store the file using our storageDB variable (see snippet 2)
storageDB.set(currentFileJson);
}
};
//Create a new file reader and setup events
//use the readAsArrayBuffer to read in the contents of the file
var myfileReader = new FileReader();
myfileReader.onprogress = reader.progress;
myfileReader.onload = reader.success;
myfileReader.onerror = reader.error;
myfileReader.readAsArrayBuffer(currentFile);
}
function HandleFileUpload(event) {
var oFileArray = event.currentTarget.files;
if (oFileArray != null && oFileArray.length > 0) {
for (var i = 0; i < oFileArray.length; i++) {
ReadFile(oFileArray[i]);
}
}
}
//Add a handler for the file input on change event
myBestPalIsFileReader.onchange = HandleFileUpload;
</script>
<script type="text/javascript">
//Setup some useful parameters
var dbName = "Files",
storeName = "FileStore",
database,
dbReady = false;
var storageDB = {
init: function () {
// indexedDB is not part of HTML5 so need vendor specifics so that we can use across browsers
window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
if (!window.indexedDB) {
alert("What?! No IndexedDB?");
}
try {
// Create new database with name and version
var request = window.indexedDB.open(dbName, 1);
request.onerror = function (event) {
//Handle the error, notify the user, provide fallback using localStorage?
};
request.onsuccess = function (event) {
//Store our new database into a variable we can access later
database = request.result;
dbReady = true;
};
//onUpgradeNeeded is fired if the version is changed, so if you want to make
//schema changes then add them in here and increased your version number
request.onupgradeneeded = function (event) {
var db = event.target.result;
//We are using "id" as the identifier for the object and
//allowing the database to keep track of this for us by using autoIncrement: true
var objectStore = db.createObjectStore(storeName, { keyPath: "id", autoIncrement: true });
};
}
catch (Error) {
}
},
set: function (value) {
//Use our database parameter set earlier along with our store name
//Create transaction and use this to access the object store
var transaction = database.transaction([storeName], "readwrite");
var objectStore = transaction.objectStore(storeName);
var request = objectStore.put(value);
request.onsuccess = function (event) {
var item = event;
value.id = event.target.result;
alert("File Stored, ID: " + value.id);
};
request.onerror = function (event) {
// Handle the error
}
},
get: function (Id) {
//Retrieve an object by its Id, methods can be chained for shorter scripts
var request = database.transaction([storeName], "readwrite").objectStore(storeName).get(Id);
request.onsuccess = function (event) {
var item = event.target.result;
};
}
};
// Call to setup the database<
storageDB.init();
</script>
The code samples above are commented in the parts I felt needed further explanation however one key aspect not mentioned in the comments is the fact that IndexedDB uses asynchronous calls.
In most parts of the code above, when making a call using IndexedDB the result of the call is returned to a variable named request; this is not a regular variable but known affectionately as a ‘promise’. This could be a whole other blog post (and may well be) but you can see the basics of how they work in the above code sample, as it gives you a chance to define some event handlers to receive the asynchronous call-backs.
JavaScript can be a scary language to work with as there is no compiler, it doesn't have great IntelliSense support, it’s loosely-typed and browsers have their own versions of how it should work.
That being said, these same reasons are why I enjoy writing in JavaScript so much and with the increased power given to the browser through HTML5, it is going to become an integral part of my and any developer’s toolkit.
So hopefully you've read to the end and not just skipped down here to download the finished code. Either way, I’ve posted all you need onto http://jsfiddle.net/gbrown31/SKQs3/ so that you can try it out for yourself, enjoy!