Welcome to Episode 7 of the Firefox OS App Development Tutorial. In the previous episode, we looked at how you could add persistence to your application via the localStorage API. As the objects that you want to persist become more complicated, you can use another HTML5 API called IndexedDB API, which lets you store complex objects. That’s what this episode is about.
Let us check out the application in action first. The application is going to be similar to the previous one and in terms of UI and functionality, there is no difference. But we will repeat it here for the sake of refreshing everything again.
What we shall write is a mobile application that will allow us to save some quick notes. Each note will have a title and some details. These notes will be saved in the Indexed DB of the device and then we can view all our notes too.
All right then, the first screen of the mobile app is shown below:
When we click on the Add a Note button, we get a screen as shown below where we can enter the title and details for the new note. Once we are done, we can save the note by clicking on the Save Note button.
If the note is successfully saved, it will display a message as shown below:
To view all the notes, you need to click on the View Notes button from the main screen. This will retrieve all the notes from the Local Storage and display them to you in a collapsible list form.
You can click on any of the + signs and it will expand to show you the note details as shown below:
If you wish to delete all the notes, there is also a Clear button. This will delete permanently all the notes from the Local Storage.
Let’s get going with the code. Note that the example screenshots are from the Firefox OS Simulator running locally. So you can use your Firefox OS Simulator to run all the examples.
I suggest that you begin with a full download of the project source code. Since the project depends on libraries like jQuery and jQuery Mobile, it will save you the hassle of downloading the dependent libraries.
Download the code from: https://github.com/anicholakos/SaveNotesIndexedDB
Extract all the code in some directory. You should see a directory structure inside of SaveNotes that looks something like this:
The HTML5 IndexedDB API is another JavaScript API that allows you to build persistence in your web applications. At the end of the day, it is similar at a high level with the localStorage API that you saw in the previous episode.
But it does have some important differences and these points will help you a bit in evaluating which way to go.
A few things to note about the IndexedDB API:
OK. Lets get going with understand the code and how the IndexedDB API has been used to persist (save) notes in our application.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | {
"version": "2.0",
"name": "Notes",
"description": "Uses the HTML5 Indexed API to demonstrate how to save data in your Firefox OS Apps.",
"launch_path": "/index.html",
"fullscreen": "false",
"icons": {
"128": "/images/notes_128.png",
"512": "/images/notes_512.png"
},
"developer": {
"name": "Romin Irani",
"url": "http://www.rominirani.com"
},
"default_locale": "en"
}
|
Next up is the index.html page and it is a simple jQuery Mobile page.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | <!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Notes</title>
<link rel="stylesheet" href="jquery.mobile-1.4.5.min.css">
<script src="jquery-2.1.4.min.js"></script>
<script src="jquery.mobile-1.4.5.min.js"></script>
<script src="app.js"></script>
</head>
<body>
<!-- Start of first page: #home -->
<div data-role="page" id="home">
<div data-role="header" data-position="fixed">
<h3>Notes</h3>
</div><!-- /header -->
<div data-role="content">
<a href="#" id="btnAddNote" data-role="button">Add a Note</a>
<a href="#" id="btnViewNotes" data-role="button">View Notes</a>
</div><!-- /content -->
</div><!-- /page home -->
<!-- Start of the second page : #add-notes -->
<div data-role="page" id="add-notes">
<div data-role="header" data-add-back-btn="true">
<h1>Add a Note</h1>
</div>
<div data-role="content">
<input type="text" id="noteTitle" value="" placeholder="Title" autofocus>
<textarea placeholder="Details" name="noteDetails" id="noteDetails">
</textarea>
<a href="#" id="btnSaveNote" data-role="button">Save Note</a>
<a href="#" id="btnClearNotes" data-role="button">Clear</a>
</div>
</div><!-- /page add-notes -->
<!-- Start of the third page : #view-notes -->
<div data-role="page" id="view-notes">
<div data-role="header" id="header" data-add-back-btn="true">
<h1>List of Notes</h1>
<a href="#" data-role="button" class="ui-btn-right"
id="clearAllNotesBtn">Clear</a>
</div>
<div id="note-list" data-role="content">
</div>
</div><!-- /page view-notes -->
</body>
</html>
|
Let us discuss the index.html page in detail now:
We have included the script in the app.js file on Line 11.
The #home page has two buttons for Add a Note and View Notes.
The #add-notes page has a form for entering the title (#noteTile), details (#noteDetails) and two buttons for saving the note (#btnSaveNote) and clearing the fields (#btnClearNotes).
The #view-notes page has a button in the header to clear all notes (#clearAllNotesBtn) and it has a div area (#note-list) to display all current notes, once we get them from the Local Storage.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | // variable which will hold the database connection
var db;
function initializeDB() {
if (window.indexedDB) {
console.log("Your environment supports IndexedDB");
}
else {
alert("Indexed DB is not supported. Where are you trying to run this?");
}
// open the database
// 1st parameter : Database name. We are using the name 'notesdb'
// 2nd parameter is the version of the database.
var request = indexedDB.open('notesdb', 1);
request.onsuccess = function(e) {
// e.target.result has the connection to the database
db = e.target.result;
}
request.onerror = function(e) {
console.log(e);
};
// this will fire when the version of the database changes
// We can only create Object stores in a versionchange transaction.
request.onupgradeneeded = function(e) {
// e.target.result holds the connection to database
db = e.target.result;
if (db.objectStoreNames.contains("notes")) {
db.deleteObjectStore("notes");
}
// create a store named 'notes'
// 1st parameter is the store name
// 2nd parameter is the key field that we can specify here. Here we have
// opted for autoIncrement but it could be your own provided value also.
var objectStore = db.createObjectStore('notes', {keyPath: 'id',
autoIncrement: true });
console.log("Object Store has been created");
};
}
$(document).ready(function() {
//Initialize the Database first
initializeDB();
$("#btnAddNote").click(function() {
//Change to the add-notes
$.mobile.changePage($("#add-notes"));
});
$("#btnViewNotes").click(function() {
//Change to the add-notes
$.mobile.changePage($("#view-notes"));
//Empty the list first
$("#note-list").html("");
//Read the notes
var transaction = db.transaction([ 'notes' ]);
var store = transaction.objectStore('notes');
// open a cursor to retrieve all items from the 'notes' store
store.openCursor().onsuccess = function(e) {
var cursor = e.target.result;
if (cursor) {
var value = cursor.value;
var noteElement = $("<div data-role='collapsible' data-mini='true'>");
var h3NoteTitle = $("<h3/>").text(value.title);
var pNoteDetails = $("<p/>").text(value.details);
noteElement.append(h3NoteTitle);
noteElement.append(pNoteDetails);
$("#note-list").append(noteElement);
// move to the next item in the cursor
cursor.continue();
}
$('div[data-role=collapsible]').collapsible({refresh:true});
};
});
//Click Handlers for Add Notes page
$("#btnSaveNote").click(function() {
var noteTitle = $("#noteTitle").val();
var noteDetails = $("#noteDetails").val();
// Create the transaction with 1st parameter is the list of stores and
// the second specifies a flag for the readwrite option
var transaction = db.transaction([ 'notes' ], 'readwrite');
// Create the Object to be saved i.e. our Note
var value = {
title: noteTitle,
details: noteDetails
};
// Add the note to the store
var store = transaction.objectStore('notes');
var request = store.add(value);
request.onsuccess = function(e) {
alert("Your note has been saved");
};
// Clear input areas for new note
$("#noteTitle").val("");
$("#noteDetails").val("");
request.onerror = function(e) {
alert("Error in saving the note. Reason: " + e.value);
}
});
$("#btnClearNotes").click(function() {
$("#noteTitle").val("");
$("#noteDetails").val("");
$("#noteTitle").focus();
});
//Click Handlers for View Notes page
$("#clearAllNotesBtn").click(function() {
var transaction = db.transaction([ 'notes' ], 'readwrite');
var store = transaction.objectStore('notes');
// Delete all the notes
// Alternately if you know the ID, you can use store.delete(ID) for
// individual item deletion
var request = store.clear();
request.onsuccess = function() {
$("#note-list").html("");
alert("All Notes have been cleared");
}
request.onerror = function(e) {
alert("Error while deleting notes: " + e.value);
};
});
});
|
Let us discuss the source code in detail now since it contains the HTML5 IndexedDB JavaScript API:
I encourage you to learn more about the IndexedDB API. Mozilla covers this in a lot more detail on a page on the MDN named Using Indexed DB. A good exercise to try out would be to come up with your own little database design. Come up with a DB and some data stores. Create some indices and play around with basic CRUD Operations.