Local data persistence improves WEB application accessibility and mobile application responsiveness
The index Database (IndexedDB) API (as part of HTML5) is useful for creating data-intensive offline HTML5 WEB applications that have rich, locally stored data. It also helps to cache data locally, enabling traditional online Web applications, such as mobile Web applications, to run and respond faster. This article describes how to manage the IndexedDB database.
An important feature of HTML5 is the persistence of local data, which enables users to access WEB applications online and offline. In addition, local data persistence makes mobile applications more responsive, uses less bandwidth, and works more efficiently in low-bandwidth scenarios. HTML5 provides some local data persistence options. The first option is localstorage
that it allows you to store data using a simple key-value pair. IndexedDB (a more powerful option) allows you to store a large number of objects locally and retrieve data using robust data access mechanisms.
The IndexedDB API replaces the WEB Storage API, which is deprecated in the HTML5 specification. (but some leading browsers still support Web Storage, including Apple's Safari and Opera Web browsers), and the IndexedDB has several advantages over web Storage, including indexing, transactional processing, and robust query functionality. This article will demonstrate how to manage the IndexedDB database through a series of examples. (See the download section for the full source code for the sample.) )
Important Concepts
A Web site may have one or more IndexedDB databases, and each database must have a unique name.
A database can contain one or more object stores . An object store (uniquely identified by a name) is a collection of records. Each record has a key and a value . The value is an object that can have one or more properties. The key may be based on a key generator, derived from a key path, or explicitly set. A key generator automatically generates a unique continuous positive integer. The key path defines the path to the key value. It can be a single JavaScript identifier or multiple identifiers separated by periods.
The specification contains an asynchronous API and a synchronization API. The synchronization API is used in a Web browser. Asynchronous APIs use requests and callbacks.
In the following example, the output is appended to a tag with an ID result
div
. To update result
elements, you can clear and set properties during each data operation innerHTML
. Each example JavaScript function is called by an event of an HTML button onclick
.
Handling errors or exceptions and debugging
All asynchronous requests have a onsuccess
callback and a callback, which is called when the onerror
database operation succeeds, and is called when an operation is unsuccessful. Listing 1 is an onerror
example of a callback.
Listing 1. Asynchronous error-handling functions
Request.onerror = function (e) { //Handle error ... Console.log ("Database error:" + E.target.errorcode);};
Using JavaScript try/catch
blocks is a good idea when using the IndexedDB API. This feature is useful for handling errors and exceptions that can occur before a database operation, such as attempting to read or manipulate data when the database is not open, or attempting to write data when another read/write transaction is open.
IndexedDB is difficult to debug and troubleshoot, because in many cases the error message is generic and lacks information value. When developing an application, you can use console.log
and JavaScript debugging tools, such as Firebug for Mozilla Firefox, or Chrome's built-in Developer tools. The value of these tools is invaluable for any JavaScript-intensive application, especially for HTML5 applications that use the IndexedDB API.
Back to top of page
Working with databases
A database can have only one version at a time. When the database is first created, its initial version number is 0. After a database is created, the database (and its object storage) can only be changed by a transaction of a versionchange
particular type called. To change a database after it is created, you must open a database that has a higher version. This action triggers the upgradeneeded
event. The code that modifies the database or object store must be upgradeneeded
in the event handler.
The code snippet in Listing 2 shows how to create a database: Call the open
method and pass the database name. If a database with the specified name does not exist, the database is created.
Listing 2. To create a new database
function CreateDatabase () { var openrequest = LocalDatabase.indexedDB.open (dbName); Openrequest.onerror = function (e) { console.log ("Database error:" + E.target.errorcode); }; openrequest.onsuccess = function (event) { Console.log ("Database created"); localdatabase.db = Openrequest.result; }; openrequest.onupgradeneeded = function (evt) { ... };}
To delete an existing database, you can call the deleteDatabase
method and pass the name of the database you want to delete, as shown in Listing 3.
Listing 3. Delete an existing database
function DeleteDatabase () { var deletedbrequest = localDatabase.indexedDB.deleteDatabase (dbName); deletedbrequest.onsuccess = function (event) { //database deleted successfully }; Deletedbrequest.onerror = function (e) { console.log ("Database error:" + E.target.errorcode);} ;}
The code snippet in Listing 4 shows how to open a connection to an existing database.
Listing 4. Open the latest version of the database
function OpenDatabase () { var openrequest = LocalDatabase.indexedDB.open ("dbName"); Openrequest.onerror = function (e) { console.log ("Database error:" + E.target.errorcode); }; openrequest.onsuccess = function (event) { localdatabase.db = Openrequest.result; };}
creating, deleting, and opening a database is as simple as that. Now is the time to use object storage.
Back to top of page
Using Object storage
An object store is a collection of data records. To create a new object store in an existing database, you need to version control the existing database. To do this, open the database for which you want to version control. In addition to the database name, the open
method accepts a version number as the second parameter. If you want to create a new version of the database (that is, to create or modify an object store), simply open a database with a higher version of the existing database. This invokes the onupgradeneeded
event handler function.
To create an object store, you can invoke the method on the database object createObjectStore
, as shown in Listing 5.
Listing 5. Creating an Object Store
function Createobjectstore () { var openrequest = LocalDatabase.indexedDB.open (dbName, 2); Openrequest.onerror = function (e) { console.log ("Database error:" + E.target.errorcode); }; openrequest.onsuccess = function (event) { localdatabase.db = Openrequest.result; }; openrequest.onupgradeneeded = function (evt) { var employeestore = Evt.currentTarget.result.createObjectStore ("Employees", {keypath: "id"});} ;}
We've learned how object storage works. Next, let's look at how the index references the object store that contains the data.
Back to top of page
Working with Indexes
In addition to using keys to retrieve records from an object store, you can also use a field that is indexed to retrieve records. An object store can have one or more indexes. An index is a special object store that references an object store that contains data that is automatically updated when changes are made to the referenced object store (that is, when records are added, modified, or deleted).
To create an index, you must use the method shown in Listing 5 to make the database version-controlled. The index can be unique, or it can be inflexible. A unique index requires that all values in the index are unique, such as using an e-mail address field. When a value can recur, you need to use a non-unique index, such as city, state, or country. The code snippet in Listing 6 shows how to create a non-unique index on the state field of the Employee object, create a non-unique index on the ZIP Code field, and create a unique index on the email address field:
Listing 6. Create an index
function CreateIndex () { var openrequest = LocalDatabase.indexedDB.open (dbName, 2); Openrequest.onerror = function (e) { console.log ("Database error:" + E.target.errorcode); }; openrequest.onsuccess = function (event) { db = Openrequest.result; }; openrequest.onupgradeneeded = function (evt) { var employeestore = Evt.currentTarget.result.objectStore (" Employees "); Employeestore.createindex ("Stateindex", "state", {unique:false}); Employeestore.createindex ("Emailindex", "email", {unique:true}); Employeestore.createindex ("Zipcodeindex", "Zip_Code", {unique:false}) };
Next, you will use transactions to perform some operations on the object store.
Back to top of page
Using transactions
You need to use transactions to perform all read and write operations on the object store. Similar to how transactions in a relational database work, the IndexedDB transaction provides an atomic collection of database writes that are either fully committed or not committed at all. IndexedDB transactions also have a abort and commit tool for database operations.
Table 1 lists and describes the transaction modes provided by the IndexedDB.
Table 1. IndexedDB Transaction Mode
Mode |
Description |
readonly |
Provides read-only access to an object store and is used when querying object storage. |
readwrite |
Provides read and write access to an object store. |
versionchange |
Provides read and write access to modify the object storage definition, or create a new object store. |
The default transaction mode is readonly
. You can open multiple concurrent transactions at any given time readonly
, but only one transaction can be opened readwrite
. For this reason, the use of transactions is only considered when data is updated readwrite
. versionchange
a single database or object store that represents a transaction operation that cannot open any other concurrent transaction. You can onupgradeneeded
use transactions in an event handler to versionchange
Create, modify, or delete an object store, or to add an index to an object store.
To readwrite
create a transaction in the schema for the employees
object store, you can use the statement: var transaction = db.transaction("employees", "readwrite");
.
The JavaScript function in Listing 7 shows how to use a transaction to retrieve employees
a specific employee record in the object store using a key.
Listing 7. Use keys to get a specific record
function Fetchemployee () {try { var result = document.getElementById ("result"); result.innerhtml = ""; if (localdatabase = null && localdatabase.db! = null) { var store = localDatabase.db.transaction ("Employees" ). ObjectStore ("Employees"); Store.get ("E3"). onsuccess = function (event) { var employee = Event.target.result; if (employee = = null) { Result.value = "Employee not found"; } else { var jsonstr = json.stringify (employee); result.innerhtml = Jsonstr;}} ; } catch (e) { console.log (e);}}
The JavaScript function in Listing 8 shows how to use a transaction to retrieve a emailIndex
employees
specific employee record in an object store using an index instead of an object store key.
Listing 8. Using indexes to get specific records
function Fetchemployeebyemail () {try { var result = document.getElementById ("result"); result.innerhtml = ""; if (localdatabase! = NULL && localdatabase.db! = null) { var range = idbkeyrange.only ("[email protected]");
var store = localDatabase.db.transaction ("Employees"). ObjectStore ("Employees"); var index = store.index ("Emailindex"); Index.get (range). onsuccess = function (evt) { var employee = Evt.target.result; var jsonstr = json.stringify (employee); result.innerhtml = jsonstr;} ;} } catch (e) {
Listing 9 is readwrite
An example of using transactions to create a new employee record.
Listing 9. Create a new employee record
function AddEmployee () {try {var result = document.getElementById ("result"); result.innerhtml = ""; var transaction = localDatabase.db.transaction ("Employees", "ReadWrite"); var store = Transaction.objectstore ("Employees"); if (localdatabase! = NULL && localdatabase.db! = null) {var request = Store.add ({"id": "E5", "First_Name": "Jane", "last_name": "Doh", "email": "[email protected]", "Street": "123 Pennsylvania Avenue", "City": "Washington D.C.", "state": "DC", "Zip_ Code ":" 20500 ",}); request.onsuccess = function (e) {result.innerhtml = "Employee record was added successfully."; }; Request.onerror = function (e) {console.log (e.value); result.innerhtml = "Employee record is not added."; }; }} catch (e) {console.log (e); }}
Listing 10 is readwrite
An example of updating an existing employee record with a transaction. This example changes the E3
e-mail address of the employee who has the record ID.
Listing 10. Update an existing employee record
function UpdateEmployee () {try {var result = document.getElementById ("result"); result.innerhtml = ""; var transaction = localDatabase.db.transaction ("Employees", "ReadWrite"); var store = Transaction.objectstore ("Employees"); var jsonstr; var employee; if (localdatabase = null && localdatabase.db! = null) {Store.get ("E3"). onsuccess = function (event) { Employee = Event.target.result; Save old value Jsonstr = ' old: ' + json.stringify (employee); result.innerhtml = Jsonstr; Update record employee.email = "[email protected]"; var request = Store.put (employee); request.onsuccess = function (e) {console.log ("Added Employee"); }; Request.onerror = function (e) {console.log (e.value); }; Fetch record again Store.get ("E3"). onsuccess = function (event) {employee = Event.target.result; Jsonstr = "
NEW: "+ json.stringify (employee); result.innerhtml = result.innerhtml + jsonstr; }; Fetch employee AGAIN}; Fetch Employee first time}}catch (e) {console.log (e);}}
Listing 11 is an example of a transaction that creates or deletes all records in an object store readwrite
. Like other asynchronous transactions, a clear
transaction invokes or callbacks based on whether the object store has been purged onsuccess
onerror
.
Listing 11. Clear Object Store Transactions
function clearallemployees () {try { var result = document.getElementById ("result"); result.innerhtml = ""; if (localdatabase = null && localdatabase.db! = null) { var store = localDatabase.db.transaction ("Employees" , "ReadWrite"). ObjectStore ("Employees"); Store.clear (). onsuccess = function (event) { result.innerhtml = "' Employees ' object store cleared"; }; } catch (e) { console.log (e);}}
These examples illustrate some of the common uses of transactions. Next you'll see how the IndexedDB midstream works.
Back to top of page
Using cursors
Similar to how a relational database works, cursors in IndexedDB enable you to iterate over a record in an object store. You can also use an index of object storage to iterate over records. Cursors in IndexedDB are bidirectional, so you can iterate through records forward and backward, and you can skip duplicate records in non-unique indexes. openCursor
method to open a cursor. It accepts two optional parameters, including the range and direction.
Listing 12 employees
opens a cursor for the object store and iterates through all employee records.
Listing 12. Iterate all Employee records
function fetchallemployees () {try { var result = document.getElementById ("result"); result.innerhtml = ""; if (localdatabase = null && localdatabase.db! = null) { var store = localDatabase.db.transaction ("Employees" ). ObjectStore ("Employees"); var request = Store.opencursor (); request.onsuccess = function (evt) { var cursor = Evt.target.result; if (cursor) { var employee = Cursor.value; var jsonstr = json.stringify (employee); result.innerhtml = result.innerhtml + "
"+ jsonstr; Cursor.continue ();}} ;} } catch (e) { console.log (e);}}
The following examples use cursors for indexing. Table 2 lists and describes the scope types or filters that the IndexedDB API provides when you open a cursor for an index.
Table 2. The scope type or filter provided by the IndexedDB API when the cursor is opened for the index
|
|
idbkeyrange.bound |
returns all records in the specified range. This range has a lower and upper boundary. It also has two optional parameters: loweropen and Upperopen , which indicate whether records on the lower or upper bounds should be included in the range. |
idbkeyrange.lowerbound |
All records that exceed the specified boundary value range. This range has an optional parameter loweropen , which indicates whether the records on the lower boundary should be included in the scope. |
idbkeyrange.upperbound |
returns all records before the specified boundary value. It also has an optional upperopen parameters. |
idbkeyrange.only |
returns only records that match the specified value. |
Listing 13 is a basic example of all employee records for an iteration of a specific country. This query is most common. It enables you to retrieve all records that match a specific condition. This example uses stateIndex
and IDBKeyRange.only
ranges to return all records that match the specified value (in this case "New York"
).
Listing 13. Iterate all employee records in New York City.
function fetchnewyorkemployees () {try { var result = document.getElementById ("result"); result.innerhtml = ""; if (localdatabase! = NULL && localdatabase.db! = null) { var range = idbkeyrange.only ("New York"); var store = localDatabase.db.transaction ("Employees"). ObjectStore ("Employees"); var index = store.index ("Stateindex"); Index.opencursor (range). onsuccess = function (evt) { var cursor = Evt.target.result; if (cursor) { var employee = Cursor.value; var jsonstr = json.stringify (employee); result.innerhtml = result.innerhtml + "
"+ jsonstr; Cursor.continue ();}} ;} } catch (e) { console.log (e);}}
Listing 14 is an IDBKeyRange.lowerBound
example of using scopes. It iterates all employees with a zip code above 92000.
Listing 14. Use
IDBKeyRange.lowerBound
function FetchEmployeeByZipCode1 () {try { var result = document.getElementById ("result"); result.innerhtml = ""; if (localdatabase = null && localdatabase.db! = null) { var store = localDatabase.db.transaction ("Employees" ). ObjectStore ("Employees"); var index = store.index ("Zipindex"); var range = Idbkeyrange.lowerbound ("92000"); Index.opencursor (range). onsuccess = function (evt) { var cursor = Evt.target.result; if (cursor) { var employee = Cursor.value; var jsonstr = json.stringify (employee); result.innerhtml = result.innerhtml + "
"+ jsonstr; Cursor.continue ();}} ;} } catch (e) { console.log (e);}}
Listing 15 is an IDBKeyRange.upperBound
example of using scopes. It iterates over all employees whose ZIP code is based on 93000.
Listing 15. Use
IDBKeyRange.upperBound
function FetchEmployeeByZipCode2 () {try { var result = document.getElementById ("result"); result.innerhtml = ""; if (localdatabase = null && localdatabase.db! = null) { var store = localDatabase.db.transaction ("Employees" ). ObjectStore ("Employees"); var index = store.index ("Zipindex"); var range = Idbkeyrange.upperbound ("93000"); Index.opencursor (range). onsuccess = function (evt) { var cursor = Evt.target.result; if (cursor) { var employee = Cursor.value; var jsonstr = json.stringify (employee); result.innerhtml = result.innerhtml + "
"+ jsonstr; Cursor.continue ();}} ;} } catch (e) { console.log (e);}}
Listing 16 is an IDBKeyRange.bound
example of using scopes. It retrieves all employees whose postal code values are between 92000 and 92999 (inclusive).
Listing 16. Use
IDBKeyRange.bound
function fetchEmployeeByZipCode3 () {try { var result = document.getElementById ("result"); result.innerhtml = ""; if (localdatabase = null && localdatabase.db! = null) { var store = localDatabase.db.transaction ("Employees" ). ObjectStore ("Employees"); var index = store.index ("Zipindex"); var range = Idbkeyrange.bound ("92000", "92999", true, true); Index.opencursor (range). onsuccess = function (evt) { var cursor = Evt.target.result; if (cursor) { var employee = Cursor.value; var jsonstr = json.stringify (employee); result.innerhtml = result.innerhtml + "
"+ jsonstr; Cursor.continue ();}} ;} } catch (e) { console.log (e);}}
These examples show that the cursor functionality in IndexedDB is similar to the cursor functionality in a relational database. Using the IndexedDB cursor, you can iterate over the records in an object store and the records of an index stored by an object. Cursors in the IndexedDB are bidirectional, providing additional flexibility.
Back to top of page
Conclusion
The IndexedDB API is so powerful that you can use it to create data-intensive applications with rich local storage data (especially offline HTML5 WEB applications). You can also use the IndexedDB API to cache data locally so that traditional online Web applications, especially mobile Web applications, can run and respond faster, eliminating the need to retrieve data from the WEB server every time. For example, you can cache data from a picklist in a IndexedDB database.
This article shows how to manage a IndexedDB database, including creating a database, deleting a database, and establishing a connection to a database. This article also shows many of the more advanced features of the IndexedDB API, including transactional processing, indexing, and cursors. You can use these presentation concepts to build offline apps or mobile WEB applications that take advantage of the IndexedDB API.
[Go] using the HTML5 IndexedDB API