
On Habré
already told about IndexedDB - the standard of storage of the big structured data on the client. But that was a long time ago and the API has changed a lot. Despite this, the search for the article emerges as one of the first and misleads many who are trying to work with this technology. Therefore, I decided to write a new article with information about the current API.
What is IndexedDB
IndexedDB is an object database that is much more powerful, more efficient, and more reliable than a web storage of key / value pairs accessible through the Web Storage application interface. As in the case of web storage and file system application interfaces, the availability of a database is determined by the origin of the document that created it.
An arbitrary number of IndexedDB databases can be created for each origin. Each database has a name that must be unique for a given origin. From the point of view of the application interface, the IndexedDB database is a simple collection of named object stores. Each object must have a key under which it is stored and retrieved from storage. Keys must be unique and must have a natural order in order to be sorted. IndexedDB can automatically generate unique keys for each object added to the database. However, often the objects stored in the object storage will already have a property that can be used as a key. In this case, when creating an object repository, it is sufficient to simply define the
key property .
')
In addition to the ability to retrieve objects from storage by the value of the primary key, there is also the ability to search by the values of other properties of the object. To provide this capability, you can define any number of indexes in the object storage. Each
index defines a secondary key of the stored objects. These indices as a whole can be non-unique, and many objects can correspond to the same key. Therefore, in the operations of accessing the object storage using an index, a
cursor is usually used that defines an application interface to retrieve objects from the result stream one by one.
IndexedDB guarantees atomic operations: read and write operations to the database are combined in a transaction, so that all of them will be successfully executed, or none of them will be executed, and the database will never remain in an indefinite, partially changed state.
Let's get started
IE> 9, Firefox> 15 and Chrome> 23 support working without prefixes, but still it’s better to check all the options:
var indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB; var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
Database connection
Work with the database begins with a request to open:
var request = indexedDB.open("myBase", 1);
The
open method returns an IDBOpenDBRequest object, in which we are interested in three event handlers:
- onerror ;
- onsuccess ;
- onupgradeneeded .
Onerror will be called in case of an error and will receive an error object in the parameters.
Onsuccess will be called if everything is successful, but the method will not receive an instance of the open database as a parameter. An open database is available from the request object:
request.result .
So far everything has been as before, but now the differences begin. The second argument to the
open method is the database version. The version can only be a natural number. If you pass a fractional, it will be rounded to the whole. If there is no database with the specified version, then
onupgradeneeded will be called, in which you can modify the database if the old version exists, or create a database if it does not exist at all.
Thus, the universal database connection function might look like this:
function connectDB(f){ var request = indexedDB.open("myBase", 1); request.onerror = function(err){ console.log(err); }; request.onsuccess = function(){
where f is the function to which the open database will be transferred.
Database structure
IndexedDB does not operate on tables, but on object stores:
ObjectStore . When creating an ObjectStore, you can specify its name and parameters: the name of the key field (the string property of the settings object: keyPath) and the autogeneration of the key (the Boolean property of the settings object: autoIncrement).
Regarding the key field, there are 4 strategies of behavior:
- The key field is not specified, and key generation is not enabled — then you must manually specify the key each time a new entry is added;
- The key field is indicated, autogeneration is off - the key field is the key;
- The key field is not specified, autogeneration is enabled - IndexedDB itself generates the key value, but you can specify your key value when adding a new record;
- The key field is indicated, autogeneration is enabled - if the new element does not have a key property, then IndexedDB will generate a new value.
Different options are suitable for different types of records. For example, if you want to store primitives, then for them the key field is better to generate. I usually store objects and use my key field.
You can create an ObjectStore using the
createObjectStore method. When creating an ObjectStore, you can specify its name and parameters, for example, a key field. The database index can be created using the
createIndex method. When creating an index, you can specify its name, the field by which it is necessary to build it, and parameters, for example, the key's uniqueness:
objectStore.createIndex("name", "name", { unique: false });
Work with records
As mentioned in the introduction, any operations with entries in the IndexedDB occur within a transaction. A transaction is opened using the
transaction method. In the method you need to specify which ObjectStore you need and the access mode: read, read and write, version change. The version change mode is essentially the same as the onupgradeneeded method.
I didn’t measure specific numbers, but from a performance point of view, I think it’s better to carefully consider setting transaction parameters: open only the ObjectStore you need and don’t ask for a record when you only need to read.
db.transaction(["myObjectStore"], "readonly");
Now that we have an open transaction, we can get our ObjectStore which has the following methods for working with records:
- add - adds a strictly new entry; if you try to add an entry with an already existing key, you will get an error;
- put - overwrites or creates a new entry at the specified key;
- get - returns record by key;
- delete - deletes the entry at the specified key.
Cursor
The get method is convenient to use if you know the key for which you want to receive data. If you want to go through all the records in the ObjectStore, you can use the cursor:
var customers = []; objectStore.openCursor().onsuccess = function(event) { var cursor = event.target.result; if (cursor) { customers.push(cursor.value); cursor.continue(); } else { alert("Got all customers: " + customers); } };
But the guys from Mozilla, like me, this way of getting all the entries seemed inconvenient and they made a method that immediately returns the entire contents of ObjectStore:
mozGetAll . I hope in the future, and other browsers implement it.
Index
If you want to get a value using an index, then everything is also quite simple:
var index = objectStore.index("name"); index.get("Donna").onsuccess = function(event) { alert("Donna's SSN is " + event.target.result.ssn); };
Restrictions
The size
There are almost no restrictions on the size. Firefox limits only the size of the hard drive, but provided that for every additional 50 megabytes you will need to confirm the user. Chrome can take a database of all web pages that created them, half the hard drive, while limiting each database to 20% of this half.
Browser Support
It is supported by all new browsers except safari and mobile opera, which, in my opinion, does not matter.
Example
Usually, in all articles, the address book is considered as an example, but for the sake of diversity I decided to show storage of files, although there is essentially no difference and the code as a whole is universal.
var indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB, IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction, baseName = "filesBase", storeName = "filesStore"; function logerr(err){ console.log(err); } function connectDB(f){ var request = indexedDB.open(baseName, 1); request.onerror = logerr; request.onsuccess = function(){ f(request.result); } request.onupgradeneeded = function(e){ e.currentTarget.result.createObjectStore(storeName, { keyPath: "path" }); connectDB(f); } } function getFile(file, f){ connectDB(function(db){ var request = db.transaction([storeName], "readonly").objectStore(storeName).get(file); request.onerror = logerr; request.onsuccess = function(){ f(request.result ? request.result : -1); } }); } function getStorage(f){ connectDB(function(db){ var rows = [], store = db.transaction([storeName], "readonly").objectStore(storeName); if(store.mozGetAll) store.mozGetAll().onsuccess = function(e){ f(e.target.result); }; else store.openCursor().onsuccess = function(e) { var cursor = e.target.result; if(cursor){ rows.push(cursor.value); cursor.continue(); } else { f(rows); } }; }); } function setFile(file){ connectDB(function(db){ var request = db.transaction([storeName], "readwrite").objectStore(storeName).put(file); request.onerror = logerr; request.onsuccess = function(){ return request.result; } }); } function delFile(file){ connectDB(function(db){ var request = db.transaction([storeName], "readwrite").objectStore(storeName).delete(file); request.onerror = logerr; request.onsuccess = function(){ console.log("File delete from DB:", file); } }); }
Conclusion
IndexedDB is already fully supported by browsers and is ready for use. This is a great tool for creating stand-alone web applications, but you still need to use it wisely. Where to get by WebStorage - it's better to get by WebStorage. Where you can not store anything on the client, it is better not to store anything on the client.
Now there are more and more libraries that encapsulate in themselves work with WebStorage, FileSystem API, IndexedDB and WebSQL, but, in my opinion, it’s better to write your code at least once so that you don’t need to drag a bunch of someone else’s code without understanding it work.
More information at
MDN .