In the past few months, I’ve saw many developers that use local storage for ‘big data’ on the client side. Local storage is a powerful API that let developer save key-value data on the browser. However, it’s got some limitation like: synchronize operation that make it less efficient when it’s heavily used. Moreover, it should replace simple cases (e.g. saving the user state) and not in scenarios where you wish to save lots of data and then have the ability to ‘slide and dice’ it base on your needs with effiency. For that, we used to have WebSQL (which as you know is deprecated from 2010) and the new cool kid in town – IndexedDB. Here I will try to give you a short example that will run nicly both on IE10, Firefox (that match the spec) and Chrome (which need to tune a bit the setVersion update to call onupgrade). IE10 will support IndexedDB as well – so it’s great news to web developers in terms of ‘wild’ support for this important API in browsers. I wish we will see soon Safari (specially, on the mobile) match Chrome for android and give us the ability to leverage indexedDB both on Android and iOS.
Ok, as Linus said: “talking is cheap, show me some code”… Let’s go over the example code. In this example we will save todos to keep things simple. Classic, no? In the end of the post, I will also give two other examples of a ‘todo app’ the use indexedDB, WebSQL and jQueryMobile.
First let’s define our main variable and make sure we know how to handle all the browsers’ prefix.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var dbName = "todo"; | |
var dbVersion = 1.0; | |
var todoDB = {}; | |
var indexedDB = window.indexedDB || | |
window.webkitIndexedDB || | |
window.mozIndexedDB; | |
todoDB.indexedDB = {}; | |
todoDB.indexedDB.db = null; | |
if ('webkitIndexedDB' in window) { | |
window.IDBTransaction = window.webkitIDBTransaction; | |
window.IDBKeyRange = window.webkitIDBKeyRange; | |
} |
Then we can run on the initialization of our stuff
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
todoDB.indexedDB.open = function() { | |
var request = indexedDB.open(dbName, dbVersion); | |
request.onsuccess = function(e) { | |
console.log ("success to open DB: " + dbName); | |
todoDB.indexedDB.db = e.target.result; | |
var db = todoDB.indexedDB.db; | |
if (db.setVersion) { | |
console.log("in old setVersion: "+ db.setVersion); | |
if (db.version != dbVersion) { | |
var req = db.setVersion(dbVersion); | |
req.onsuccess = function () { | |
if(db.objectStoreNames.contains("todo")) { | |
db.deleteObjectStore("todo"); | |
} | |
var store = db.createObjectStore("todo", {keyPath: "timeStamp"}); | |
var trans = req.result; | |
trans.oncomplete = function(e) { | |
console.log("== trans oncomplete =="); | |
todoDB.indexedDB.getAllTodoItems(); | |
} | |
}; | |
} | |
else { | |
todoDB.indexedDB.getAllTodoItems(); | |
} | |
} | |
else { | |
todoDB.indexedDB.getAllTodoItems(); | |
} | |
} |
In the above code you can see the interim solution for Chrome to create an objectStore for our todos. We could deprecated this code once it’s fixed and use this code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
request.onupgradeneeded = function(e) { | |
console.log ("going to upgrade our DB!"); | |
todoDB.indexedDB.db = e.target.result; | |
var db = todoDB.indexedDB.db; | |
if(db.objectStoreNames.contains("todo")) { | |
db.deleteObjectStore("todo"); | |
} | |
var store = db.createObjectStore("todo", | |
{keyPath: "timeStamp"}); | |
todoDB.indexedDB.getAllTodoItems(); | |
} | |
Now we can hook up a listener to the button that will create a todo item for us. If you want to see the todos – just open Chrome DevTools and go to ‘Resources’ tab. There you can open the ‘todo’ DB under indexedDB item.
https://gist.github.com/3839587
The full code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<title>IndexedDB ToDo Example</title> | |
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.1.1/jquery.mobile-1.1.1.min.css" /> | |
<script src="http://code.jquery.com/jquery-1.7.1.min.js"></script> | |
<script src="http://code.jquery.com/mobile/1.1.1/jquery.mobile-1.1.1.min.js"></script> | |
<script> | |
var dbName = "todo"; | |
var dbVersion = 1.0; | |
var todoDB = {}; | |
var indexedDB = window.indexedDB || | |
window.webkitIndexedDB || | |
window.mozIndexedDB; | |
if ('webkitIndexedDB' in window) { | |
window.IDBTransaction = window.webkitIDBTransaction; | |
window.IDBKeyRange = window.webkitIDBKeyRange; | |
} | |
todoDB.indexedDB = {}; | |
todoDB.indexedDB.db = null; | |
$(document).bind('pageinit', function() { | |
console.log("– lets start the party –"); | |
todoDB.indexedDB.open(); | |
$("#addItem").click(function() { | |
addTodo(); | |
}); | |
}); | |
todoDB.indexedDB.onerror = function(e) { | |
console.log(e); | |
}; | |
todoDB.indexedDB.open = function() { | |
var request = indexedDB.open(dbName, dbVersion); | |
request.onsuccess = function(e) { | |
console.log ("success our DB: " + dbName); | |
todoDB.indexedDB.db = e.target.result; | |
var db = todoDB.indexedDB.db; | |
if (db.setVersion) { | |
console.log("in old hack checking for DB inside setVersion: " + db.setVersion); | |
if (db.version != dbVersion) { | |
var req = db.setVersion(dbVersion); | |
req.onsuccess = function () { | |
if(db.objectStoreNames.contains("todo")) { | |
db.deleteObjectStore("todo"); | |
} | |
var store = db.createObjectStore("todo", | |
{keyPath: "timeStamp"}); | |
var trans = req.result; | |
trans.oncomplete = function(e) { | |
console.log("== oncomplete transaction =="); | |
todoDB.indexedDB.getAllTodoItems(); | |
} | |
}; | |
} | |
else { | |
todoDB.indexedDB.getAllTodoItems(); | |
} | |
} | |
else { | |
todoDB.indexedDB.getAllTodoItems(); | |
} | |
} | |
request.onupgradeneeded = function(e) { | |
console.log ("Going to upgrade our DB"); | |
todoDB.indexedDB.db = e.target.result; | |
var db = todoDB.indexedDB.db; | |
if(db.objectStoreNames.contains("todo")) { | |
db.deleteObjectStore("todo"); | |
} | |
var store = db.createObjectStore("todo", | |
{keyPath: "timeStamp"}); | |
todoDB.indexedDB.getAllTodoItems(); | |
} | |
request.onfailure = todoDB.indexedDB.onerror; | |
request.onerror = function(e) { | |
console.error("Err:"+e); | |
} | |
}; | |
todoDB.indexedDB.addTodo = function(todoText) { | |
var db = todoDB.indexedDB.db; | |
var trans = db.transaction(['todo'], "readwrite"); | |
var store = trans.objectStore("todo"); | |
var data = { | |
"text": todoText, | |
"timeStamp": new Date().getTime() | |
}; | |
var request = store.put(data); | |
request.onsuccess = function(e) { | |
todoDB.indexedDB.getAllTodoItems(); | |
}; | |
request.onerror = function(e) { | |
console.error("Error Adding an item: ", e); | |
}; | |
}; | |
todoDB.indexedDB.deleteTodo = function(id) { | |
var db = todoDB.indexedDB.db; | |
var trans = db.transaction(["todo"], "readwrite"); | |
var store = trans.objectStore("todo"); | |
var request = store.delete(id); | |
request.onsuccess = function(e) { | |
todoDB.indexedDB.getAllTodoItems(); | |
}; | |
request.onerror = function(e) { | |
console.error("Error deleteing: ", e); | |
}; | |
}; | |
todoDB.indexedDB.getAllTodoItems = function() { | |
var todos = document.getElementById("todoItems"); | |
todos.innerHTML = ""; | |
var db = todoDB.indexedDB.db; | |
var trans = db.transaction(["todo"], "readwrite"); | |
var store = trans.objectStore("todo"); | |
// Get everything in the store; | |
var keyRange = IDBKeyRange.lowerBound(0); | |
var cursorRequest = store.openCursor(keyRange); | |
cursorRequest.onsuccess = function(e) { | |
var result = e.target.result; | |
if(!!result == false) | |
return; | |
renderTodo(result.value); | |
result.continue(); | |
}; | |
cursorRequest.onerror = todoDB.indexedDB.onerror; | |
}; | |
function renderTodo(row) { | |
var todos = document.getElementById("todoItems"); | |
var li = document.createElement("li"); | |
var a = document.createElement("a"); | |
var t = document.createTextNode(row.text); | |
a.addEventListener("click", function() { | |
todoDB.indexedDB.deleteTodo(row.timeStamp); | |
}, false); | |
// some fun with jquery mobile data attributes | |
a.setAttribute("href", "#"); | |
a.setAttribute("data-iconpos", "notext"); | |
a.setAttribute("data-role", "button"); | |
a.setAttribute("data-icon", "delete"); | |
a.setAttribute("data-inline", "true"); | |
li.appendChild(a); | |
li.appendChild(t); | |
todos.appendChild(li) | |
// And lets create the new il item with its markup | |
$("#todoItems").trigger('create'); | |
} | |
// Add an item only if we have more then zero letters | |
function addTodo() { | |
var todo = document.getElementById("todo"); | |
if (todo.value.length > 0) { | |
todoDB.indexedDB.addTodo(todo.value); | |
todo.value = ""; | |
} | |
} | |
</script> | |
</head> | |
<body> | |
<div data-role="page" data-content-theme="e"> | |
<div data-role="header"> | |
<h1>IndexedDB with JQM</h1> | |
</div> | |
<div data-role="content"> | |
<p> | |
<input type="text" id="todo" name="todo" | |
placeholder="What do you need to do?" /> | |
<input type="submit" value="Add Todo Item" | |
id="addItem" /> | |
</p> | |
<ul id="todoItems" data-role="listview" | |
data-inset="true" data-filter="true"></ul> | |
</div> | |
</div> | |
</body> | |
</html> |
Lastly, if you want to see a cool example of a mobile web app that use indexedDB and jQueryMobile and its code. The same example with WebSQL will work on iOS and its code. You can think on a case where you have both JavaScript files and base on modernizr.com you fetch the code (with something like: yepnopejs) that will match your browser. So in iOS it will be (for now) the WebSQL version and on Chrome for Android it will be indexedDB. Another good solution is to use this IndexedDB shim that will make your code work in Opera and Safari as well. The cool thing is that it open us to mobile Safari… You can run this “Todo List IndexedDB” on your iPhone/iPad.
Enjoy…
Added the IndexedDB polyfill script in the head, and got the IndexedDB examples working on Safari and Opera too ..
More IndexedDB examples – http://nparashuram.com/IndexedDB/trialtool/index.html
@Parashuram – Thank you.
It’s great stuff.
Pingback: How To Use IndexedDB – Simple(st) Example | Ido’s Blog | Itsaat
I wish you have seen REAL IndexedDB API wrapper library https://bitbucket.org/ytkyaw/ydn-db
Did you happen to take some of your code samples from – http://www.raymondcamden.com/index.cfm/2012/4/25/How-to-handle-setup-logic-with-indexedDB ?
A lot of the code you posted is identical to what Mr. Camden posted in his entry.
Sorry – I thought I’ve made a lot more changes… Anyway, I’ve replaced all the samples with my todo app.
The example Mr. Camden wrote it great but miss features like: query the DB with cursor and the full CRUD actions. I’ve put all of them in this example.
I’m visiting this blog for the first time and its amazing. I loved it!! Keep going …
Enjoyed examining this, very good stuff, thanks . “Be not careless in deeds, nor confused in words, nor rambling in thought.” by Marcus Aurelius Antoninus.