[Switch] Use HTML5 IndexedDB API, html5indexeddb
Local data persistence improves Web application accessibility and mobile application response capabilities
The index database (IndexedDB) API (as part of HTML5) is useful for creating a data-intensive offline HTML5 Web application with rich local storage data. It also helps to cache data locally and enable 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 local data persistence, which enables users to access Web applications online and offline. In addition, local data persistence makes mobile applications more sensitive, uses less bandwidth, and can work more efficiently in low-bandwidth scenarios. HTML5 provides some local data persistence options. The first option islocalstorage
It allows you to use a simple key-value pair to store data. IndexedDB (a more powerful option) allows you to store a large number of objects locally and use a robust data access mechanism to retrieve data.
The IndexedDB API replaces the Web Storage API, which is not recommended in the HTML5 specification. (However, some leading browsers still support Web Storage, including Apple's Safari and Opera Web browsers). Compared with Web Storage, IndexedDB has multiple advantages, this includes indexing, transaction processing, and robust query functions. This article uses a series of examples to demonstrate how to manage the IndexedDB database. (See the download section to obtain the complete source code of the example .)
Important Concepts
A website may have one or more IndexedDB databases. Each database must have a unique name.
A database can contain one or moreObject Storage Service. An object storage service (uniquely identified by a name) is a set of records. Each record hasKeyAnd oneValue. This value is an object that can have one or more attributes. A key may be derived from a key path or explicitly set based on a key generator. A key generator automatically generates a unique continuous positive integer. The Key Path defines the key value path. It can be a single JavaScript identifier or multiple identifiers separated by periods.
The specification contains an asynchronous API and a synchronous API. Synchronous API is used in Web browsers. Asynchronous APIs use requests and callbacks.
In the following example, the output is appended toresult
Ofdiv
Mark. To updateresult
Element, which can be cleared and set during each data operationinnerHTML
Attribute. Each sample JavaScript function is composed ofonclick
Event call.
Handling errors or exceptions and debugging
All asynchronous requests have oneonsuccess
Callback andonerror
Callback. The former is called when the database operation is successful, and the latter is called when the operation is not successful. Listing 1 isonerror
Callback example.
List 1. asynchronous error handling functions
request.onerror = function(e) { // handle error ... console.log("Database error: " + e.target.errorCode);};
Use JavaScript when using the IndexedDB APItry/catch
Block is a good idea. This function is useful for handling errors and exceptions that may occur before database operations, such as attempting to read or operate data when the database is not opened, or try to write data when another read/write transaction is opened.
IndexedDB is difficult to debug and troubleshoot, because in many cases, error messages are extensive and lack information value. You can useconsole.log
And JavaScript debugging Tools, such as Firebug for Mozilla Firefox or Developer Tools built in Chrome. The value of these tools is immeasurable for any JavaScript-intensive application, especially for HTML5 applications that use the IndexedDB API.
Back to Top
Use Database
A database can only have one version at a time. When you create a database for the first time, its initial version number is 0. After a database is created, the database (and its object storage) can only be calledversionchange
To change. To change a database after it is created, you must open a database with a later version. This operation will triggerupgradeneeded
Event. The code for modifying the database or object storage service must be locatedupgradeneeded
In the event processing function.
The code segment in Listing 2 shows how to create a database: Callopen
Method and pass the database name. If a database with the specified name does not exist, the database is created.
List 2. 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 calldeleteDatabase
Method, and pass the name of the database to be deleted, as shown in listing 3.
Listing 3. Deleting 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 segment in Listing 4 shows how to connect to an existing database.
Listing 4. Open the latest database version
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; };}
It is so easy to create, delete, and open databases. Now it is time to use OSS.
Back to Top
Use OSS
OSS is a collection of data records. To create a new object storage service in an existing database, you must perform version control on the existing database. To do this, open the database for version control. Besides the Database Name,open
The method also accepts the version number as the second parameter. To create a new version of the database (that is, to create or modify an Object Storage Service), you only need to open a database with a higher version of the existing database. This will callonupgradeneeded
Event processing functions.
To create an object storage service, you can call it on a database object.createObjectStore
Method, as shown in listing 5.
Listing 5. Create an Object Storage Service
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 have learned how OSS works. Next, let's take a lookIndexHow to reference object storage that contains data.
Back to Top
Use Index
In addition to using keys to retrieve records in Oss, you can also use indexed fields to retrieve records. OSS can have one or more indexes. An index is a special object storage service that references object storage that contains data. It is automatically updated when you modify the referenced Object Storage Service (that is, when you add, modify, or delete a record.
To create an index, you must use the method shown in listing 5 to control the database version. Indexes can be unique or not unique. The unique index requires that all values in the index be unique, for example, using an email address field. When a value can be repeated, you need to use a non-unique index, such as a city, state, or country. The code segment in Listing 6 shows how to create a non-unique index on the state field of the employee object and create a non-unique index on the ZIP code field, create a unique index in the email address field:
Listing 6. Creating 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 OSS.
Back to Top
Use transactions
You need to use transactions to perform all read and write operations on OSS. Similar to the working principle of transactions in relational databases, IndexedDB transactions provide an atomic set of database write operations. This set is either completely committed or completely not committed. IndexedDB transactions also have a tool for stopping and committing database operations.
Table 1 lists and describes the transaction modes provided by IndexedDB.
Table 1. IndexedDB transaction mode
Mode |
Description |
readonly |
Provides read-only access to an object storage service, which is used when querying object storage service. |
readwrite |
Provides read and write access to an object storage service. |
versionchange |
Provides read and write access to modify the definition of OSS, or creates a new OSS. |
The default transaction mode isreadonly
. You can enable multiple concurrentreadonly
Transaction, but only onereadwrite
Transaction. For this reason, you can only use this function when updating data.readwrite
Transaction. Independent (indicating that no other concurrent transactions can be opened)versionchange
Transactions operate on a database or object storage service. You canonupgradeneeded
Used in event processing functionsversionchange
Create, modify, or delete an Object Storage Service (OSS), or add an index to OSS.
Toreadwrite
Inemployees
To create a transaction in Oss, you can use the following statement:var transaction = db.transaction("employees", "readwrite");
.
The JavaScript function in listing 7 shows how to use a transaction to retrieve data using keys.employees
A specific employee record in Oss.
Listing 7. Using keys to obtain 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 useemailIndex
Index Instead of object storage key for retrievalemployees
Specific employee records in Oss.
Listing 8. Using indexes to retrieve specific records
function fetchEmployeeByEmail() {try { var result = document.getElementById("result"); result.innerHTML = ""; if (localDatabase != null && localDatabase.db != null) { var range = IDBKeyRange.only("john.adams@somedomain.com"); 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 usesreadwrite
An example of creating a new employee record for a transaction.
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" : "jane.doh@somedomain.com", "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 was not added."; }; } } catch(e){ console.log(e); }}
In listing 10readwrite
An example of updating existing employee records for transactions. In this example, the record ID is changedE3
The email address of the employee.
Listing 10. update existing employee records
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 = "john.adams@anotherdomain.com"; 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 shows how to create or delete all records in Oss.readwrite
Example of a transaction. Like other asynchronous transactions,clear
Transactions are called based on whether the object storage service has been cleared.onsuccess
Oronerror
Callback.
Listing 11. Clearing OSS 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 demonstrate some common uses of transactions. Next, you will see how the IndexedDB dashboard works.
Back to Top
Use cursor
Similar to the method in which the midstream mark of a relational database works, the cursor in IndexedDB enables you to iterate records in an object storage service. You can also use OSS indexes to iterate records. The cursor in IndexedDB is bidirectional, so you can iterate 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 isemployees
OSS opens a cursor and iterates 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 example uses the cursor for the index. Table 2 lists and describes the range types or filters provided by the IndexedDB API when a cursor is opened for an index.
Table 2. Range types or filters provided by the IndexedDB API when a cursor is opened for an index
Range type or filter |
Description |
IDBKeyRange.bound |
Returns all records within the specified range. This range has a lower boundary and an upper boundary. It also has two optional parameters:lowerOpen AndupperOpen The two parameters indicate whether the records at the bottom or upper boundary should be included in the range. |
IDBKeyRange.lowerBound |
All records that exceed the specified boundary value range. This range has an optional parameterlowerOpen Indicates whether the records in the following world should be included in the range. |
IDBKeyRange.upperBound |
Returns all records before the specified boundary value. It also has an optionalupperOpen Parameters. |
IDBKeyRange.only |
Only records that match the specified value are returned. |
Listing 13 is a basic example of iterating all employee records in a particular country. This query is the most common. It enables you to retrieve all records that match a specific condition. This example usesstateIndex
AndIDBKeyRange.only
Range, returns and specifies the value (in this example"New York"
) All matched records.
Listing 13. iterate over 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 a usageIDBKeyRange.lowerBound
Range example. It iterates over 92000 of all employees.
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 a usageIDBKeyRange.upperBound
Range example. It iterates the zip code based on 93000 of all employees.
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 a usageIDBKeyRange.bound
Range example. It retrieves all employees whose zip code value is between 92000 and 92999.
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 function in IndexedDB is similar to that in relational databases. You can use IndexedDB cursor to iterate records in an object storage service and an index record in an object storage service. The cursor in IndexedDB is bidirectional, which provides additional flexibility.
Back to Top
Conclusion
The IndexedDB API is very powerful. You can use it to create data-intensive applications (especially offline HTML5 Web applications) with rich local data storage ). 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, this eliminates the need to retrieve data from the Web server each time. For example, you can cache the selected data in the IndexedDB database.
This document describes how to manage an IndexedDB database, including creating a database, deleting a database, and establishing a connection to the database. This article also shows many more advanced functions of the IndexedDB API, including transaction processing, index, and cursor. You can use these concepts to build offline applications or mobile Web applications that utilize IndexedDB APIs.