Using IndexedDB to Persist Client-Side Data in JavaScript

The code below demonstrates a few features of IndexedDB, allowing the user to create, retrieve, update, and delete instances of the “Widget” object from the browser’s local persistent object store.

To see it in action, copy the code into an .html file, deploy that file to a file server (for example, if using Microsoft Internet Information Services, that folder might be “C:\inetpub\wwwroot\”), and navigate to the corresponding address in a web browser (for example, “http://localhost/IndexedDBTest.html”).

You can create, retrieve, update, and delete Widgets using the simple user interface provided.  Any changes you make will stay in place until the next time you clear your browsing history.

As of this writing, the code has only been tested in Google Chrome, and clicking the “Delete All” button will display a “not yet implemented” message.

 

IndexedDBWidgets

<html>
<body>

<!-- user interface -->

	<table style="border:1px solid">
		<tr>
			<td>Save a Widget:</td>
		</tr>
		<tr>
			<td>ID</td>
			<td>Value</td>
		</tr>
		<tr>
			<td><input id="inputWidgetToSaveID" /></td>
			<td><input id="inputWidgetToSaveValue" /></td>
			<td><button id="buttonWidgetSave" onclick="buttonWidgetSave_Click();">Save</button></td>
		</tr>
	</table>

	<table style="border:1px solid">
		<tr>
			<td>Get a Widget:</td>
		</tr>
		<tr>
			<td>ID</td>
		</tr>
		<tr>
			<td><input id="inputWidgetToGetID" /></td>
			<td><button id="buttonWidgetGetByID" onclick="buttonWidgetGetByID_Click()">Get</button></td>
		</tr>
	</table>

	<table style="border:1px solid">
		<tr>
			<td>Delete a Widget:</td>
		</tr>
		<tr>
			<td>ID</td>
		</tr>
		<tr>
			<td><input id="inputWidgetToDeleteID" /></td>
			<td><button id="buttonWidgetDeleteByID" onclick="buttonWidgetDeleteByID_Click()">Delete</button></td>
		</tr>
	</table>

	<table style="border:1px solid">
		<tr>
			<td><button id="buttonWidgetGetAll" onclick="buttonWidgetGetAll_Click()">Get All Widgets</button></td>
			<td><button id="buttonWidgetDeleteAll" onclick="buttonWidgetDeleteAll_Click()">Delete All Widgets</button></td>
		</tr>
	</table>

<script type='text/javascript'>

// main

function main()
{
	Globals.Instance.initialize();
}

function buttonWidgetDeleteAll_Click(event)
{
	alert("Not yet implemented!");
}

function buttonWidgetDeleteAll_Click(event)
{
	Globals.Instance.database.documentDeleteFromCollectionWithNameAll
	(
		Widget.prototype.constructor.name, 
		displayDatabaseDocuments
	);
}

function buttonWidgetDeleteByID_Click(event)
{
	var widgetIDAsString = document.getElementById("inputWidgetToDeleteID").value;
	var widgetID = parseInt(widgetIDAsString);
	if (isNaN(widgetID) == true)
	{
		alert("Error: ID must be an integer");
	}
	else
	{
		Globals.Instance.database.documentDeleteFromCollectionWithNameByID
		(
			Widget.prototype.constructor.name, 
			widgetID,
			displayDatabaseDocuments
		);
	}
}

function buttonWidgetGetAll_Click(event)
{
	Globals.Instance.database.documentGetFromCollectionWithNameAll
	(
		Widget.prototype.constructor.name, 
		displayDatabaseDocuments
	);
}

function buttonWidgetGetByID_Click(event)
{
	var widgetIDAsString = document.getElementById("inputWidgetToGetID").value;
	var widgetID = parseInt(widgetIDAsString);
	if (isNaN(widgetID) == true)
	{
		alert("Error: ID must be an integer");
	}
	else
	{
		Globals.Instance.database.documentGetFromCollectionWithNameByID
		(
			Widget.prototype.constructor.name, 
			widgetID,
			displayDatabaseDocuments
		);
	}	
}

function displayDatabaseDocuments(documentSet)
{
	var pTitle = document.createElement("p");
	pTitle.innerHTML = "Results:";
	document.body.appendChild(pTitle);

	DisplayHelper.displayDatabaseDocumentsAsHTMLTable
	(
		documentSet.documents
	);
}

function buttonWidgetSave_Click()
{
	var widgetIDAsString = document.getElementById("inputWidgetToSaveID").value;
	var widgetID = parseInt(widgetIDAsString);
	if (isNaN(widgetID) == true)
	{
		alert("Error: ID must be an integer");
	}
	else
	{
		var widgetValue = document.getElementById("inputWidgetToSaveValue").value;

		var widgetToSave = new Widget(widgetID, widgetValue);

		Globals.Instance.database.documentSaveMany
		(
			[	
				widgetToSave
			],
			null // callback
		);
	}
}

// classes

function Database(name)
{
	this.name = name;
}
{
	// instance methods

	Database.prototype.collectionCreate = function(collectionName, callback)
	{
		if (this.systemDatabase.objectStoreNames.contains(collectionName) == false)
		{
			var indexedDBOpenRequest = indexedDB.open
			(
				this.name,
				this.systemDatabase.version + 1
			);

			indexedDBOpenRequest.callback = callback;
			indexedDBOpenRequest.nameOfCollectionToCreate = collectionName;
			indexedDBOpenRequest.onsuccess = this.handleEventOpenSuccess.bind(this);
			indexedDBOpenRequest.onblocked = this.handleEventError.bind(this);
			indexedDBOpenRequest.onerror = this.handleEventError.bind(this);
			indexedDBOpenRequest.onupgradeneeded = this.handleEventUpgradeNeeded.bind(this);		
		}
		else
		{
			callback(this);
		}
	}

	Database.prototype.delete = function()
	{
		indexedDB.deleteDatabase(this.name);
	}

	Database.prototype.documentDeleteFromCollectionWithNameAll = function(collectionName, callback)
	{
		alert("Not yet implemented!");
	}

	Database.prototype.documentDeleteFromCollectionWithNameByID = function(collectionName, id, callback)
	{
		if (this.systemDatabase.objectStoreNames.contains(collectionName) == true)
		{
			var transaction = this.systemDatabase.transaction
			(
				[ collectionName ],
				"readwrite"
			)	
			var store = transaction.objectStore(collectionName);
			var deleteRequest = store.delete(id);
			deleteRequest.callback = callback;
		}
		else
		{
			callback(this);
		}
	}

	Database.prototype.documentGetFromCollectionWithNameAll = function(collectionName, callback)
	{
		var documentSet = new DatabaseDocumentSet(this);

		if (this.systemDatabase.objectStoreNames.contains(collectionName) == true)
		{
			var transaction = this.systemDatabase.transaction
			(
				[ collectionName ],
				"readwrite"
			)	
			var store = transaction.objectStore(collectionName);
			var cursorRequest = store.openCursor();
			cursorRequest.callback = callback;
			cursorRequest.onsuccess = documentSet.handleEventCursorRequestSuccess.bind(documentSet);
		}
		else
		{
			callback(this, documentSet);
		}
	}

	Database.prototype.documentGetFromCollectionWithNameByID = function(collectionName, id, callback)
	{
		var documentSet = new DatabaseDocumentSet(this);

		if (this.systemDatabase.objectStoreNames.contains(collectionName) == true)
		{
			var transaction = this.systemDatabase.transaction
			(
				[ collectionName ],
				"readwrite"
			)	
			var store = transaction.objectStore(collectionName);
			var keyRange = IDBKeyRange.only(id);
			var cursorRequest = store.openCursor(keyRange);
			cursorRequest.callback = callback;
			cursorRequest.onsuccess = documentSet.handleEventCursorRequestSuccess.bind(documentSet);
		}
		else
		{
			callback(this, documentSet);
		}
	}

	Database.prototype.documentSaveMany = function(documentsToSave, callback)
	{
		if (documentsToSave.length > 0)
		{
			this.documentSaveManyToCollectionWithName
			(
				documentsToSave,
				documentsToSave[0].constructor.name,
				callback
			);
		}
	}

	Database.prototype.documentSaveManyToCollectionWithName = function(documentsToSave, collectionName, callback)
	{
		this.documentsToSave = documentsToSave;
		this.collectionName = collectionName;
		this.callback = callback;

		if (this.systemDatabase.objectStoreNames.contains(collectionName) == false)
		{
			this.collectionCreate
			(
				collectionName,
				this.documentSaveManyToCollectionWithName2.bind(this)
			);
		}
		else
		{
			this.documentSaveManyToCollectionWithName2();
		}
	}

	Database.prototype.documentSaveManyToCollectionWithName2 = function()
	{
		var documentsToSave = this.documentsToSave;
		var collectionName = this.collectionName;
		var callback = this.callback;

		this.documentsToSave = null;
		this.collectionName = null;
		this.callback = null;

		var transaction = this.systemDatabase.transaction
		(
			[ collectionName ],
			"readwrite"
		)	
		var store = transaction.objectStore(collectionName);
		for (var i = 0; i < documentsToSave.length; i++)
		{
			var documentToSave = documentsToSave[i];
			store.put(documentToSave);
		}

		if (callback != null)
		{
			callback(this);
		}
	}

	Database.prototype.load = function(callback)
	{
		var indexedDBOpenRequest = indexedDB.open
		(
			this.name
		);

		indexedDBOpenRequest.callback = callback;
		indexedDBOpenRequest.onsuccess = this.handleEventOpenSuccess.bind(this);
		indexedDBOpenRequest.onerror = this.handleEventError.bind(this);
		indexedDBOpenRequest.onupgradeneeded = this.handleEventUpgradeNeeded.bind(this);
	}

	// events

	Database.prototype.handleEventError = function(event)
	{
		var one = 1;
	}

	Database.prototype.handleEventOpenSuccess = function(event)
	{
		this.systemDatabase = event.target.result;
		this.systemDatabase.onversionchange = function(event)
		{
			event.target.close();
		}

		var callback = event.target.callback;
		if (callback != null)
		{
			callback(this);
		}
	}

	Database.prototype.handleEventUpgradeNeeded = function(event)
	{
		var systemDatabase = event.target.result;
		var nameOfCollectionToCreate = event.target.nameOfCollectionToCreate;

		if (nameOfCollectionToCreate != null)
		{
			var parameterObjectForCreateObjectStore = {};
			parameterObjectForCreateObjectStore.keyPath = "id";

			systemDatabase.createObjectStore
			(
				nameOfCollectionToCreate,
				parameterObjectForCreateObjectStore
			);
		}
	}
}

function DatabaseDocumentSet(database)
{
	this.database = database;
	this.documents = [];
}
{
	DatabaseDocumentSet.prototype.handleEventCursorRequestSuccess = function(event)
	{
		var result = event.target.result;
		if (result == null)
		{
			event.target.callback(this);
		}
		else
		{
			this.documents.push
			(
				result.value
			);
			result.continue();
		}
	}
}

function DisplayHelper()
{
	// do nothing
}
{
	DisplayHelper.displayDatabaseDocumentsAsHTMLTable = function(documentsToDisplay)
	{
		// Note the difference between the DOM "document"
		// and the database "documents". 

		var table = document.createElement("table");
		table.border = "1px solid";

		var tr = document.createElement("tr");

		for (var d = 0; d < documentsToDisplay.length; d++)
		{
			var documentToDisplay = documentsToDisplay[d];

			tr = document.createElement("tr");
			for (var propertyName in documentToDisplay)
			{
				var propertyValue = documentToDisplay[propertyName];

				var td = document.createElement("td");
				td.innerHTML = 
					propertyName
					+ " : "
					+ propertyValue;

				tr.appendChild(td);
			}
			table.appendChild(tr);
		}

		document.body.appendChild(table);
	}
}

function Globals()
{}
{
	// instance

	Globals.Instance = new Globals();

	// instance methods

	Globals.prototype.initialize = function()
	{
		this.database = new Database("Database1");

		// Uncomment this if you want to start fresh.
		// this.database.delete();

		this.database.load();	
	}
}

function Widget(id, value)
{
	this.id = id;
	this.value = value;
}

// run

main();

</script>
</body>
</html>
Advertisements
This entry was posted in Uncategorized and tagged , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s