📜 ⬆️ ⬇️

Cooking IndexedDB



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 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(){ //          f(request.result); } request.onupgradeneeded = function(e){ //     ,    . e.currentTarget.result.createObjectStore("myObjectStore", { keyPath: "key" }); connectDB(f); } } 

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:

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:


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 .

Source: https://habr.com/ru/post/213515/


All Articles