indexedDB
IndexedDB is a low-level API for storing large amounts of structured data on the client side that can be created and manipulated by web scripts. IndexedDB allows you to store large amounts of data, provide a lookup interface, and create indexes that LocalStorage does not have. In terms of database type, IndexedDB is not a relational database (does not support SQL queries) and is closer to a NoSQL database. Other introduction does not carry, you can baidu, there are reference materials behind.
demand
I wanted to better implement the document-driven idea and realized that I needed front-end storage, so I decided to use IndexedDB to do that. But look at its operation is cumbersome, so I plan to encapsulate it.
The official website gave several third party encapsulation library, I also point in the past to see, the result did not understand. Thought about it or do their own food and clothing.
The idea of making wheels over and over again:
- The first is the ability to make wheels.
- You build your own wheels. You handle them better.
Functional design
According to the function introduction on the official website, the functions are sorted out: as shown in the figure:
It is to build a database, add, delete, change and check that set. It’s great to see some third-party encapsulated libraries that support SQL statements. There’s no need for that right now, well, the capacity is limited. In short, meet their own needs first, later in the slow improvement.
Code implementation
Or simple and rough, directly on the code, the introduction of basic knowledge, there are a lot of online, you can see the back of the reference. The official website is also more detailed, and the Chinese version.
The configuration file
nf-indexedDB.config
const config = {
dbName: 'dbTest'.ver: 1.debug: true.objectStores: [ // Base for database construction
{
objectStoreName: 'blog'.index: [ // Index, whether unique can be repeated
{ name: 'groupId'.unique: false}}]].objects: { // Initialize the data
blog: [{id: 1.groupId: 1.title: 'This is a blog'.addTime: '2020-10-15'.introduction: 'This is the blog introduction'.concent: 'Here's the blog in detail
Line 2'.viewCount: 1.agreeCount: 1
},
{
id: 2.groupId: 2.title: 'These are two blogs'.addTime: '2020-10-15'.introduction: 'This is the blog introduction'.concent: 'Here's the blog in detail
Line 2'.viewCount: 10.agreeCount: 10}}}]export default config
Copy the code
- dbName
Specify the database name
- ver
Specifying the database version
- debug
Specifies whether to print the state
- objectStores
Description of object repository, library name, index, etc.
- objects
Initialize data. If you need to add default data after the database is created, you can set it here.
The setup here is not perfect, and there are some small problems that I haven’t figured out how to solve yet. You can change it later when you think about it.
The internal members
/** * IndexedDB database object * determines whether the browser supports ** /
const myIndexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.msIndexedDB
if(! myIndexedDB) {console.log('Your browser does not support IndexedDB')}let _db // Holds an instance of indexed database internally
Var var var var var var var var var var var var var var var var var var var var var
const _vueToObject = (vueObject) = > {
let _object = vueObject
// The type judgment made for Vue3
if (Vue.isRef(_object)) {
// If vue is ref type, replace with ref. Value
_object = _object.value
}
if (Vue.isReactive(_object)) {
Reactive type of vue; reactive type of vue
_object = Vue.toRaw(_object)
}
return _object
}
Copy the code
- myIndexedDB
Compatible browser writing, adapt to different browsers.
-
The IDBOpenDBRequest inside _db is used to check whether the database is open, and for database-related operations.
-
_vueToObject
This is a Vue compatible object conversion function. If Vue’s reactive is directly stored, an error will be reported, and the prototype needs to be obtained before it can be stored. I don’t want to make extra steps every time I save it, so I wrote this transformation function. If the environment is not VUE3, the parameter can be returned directly without affecting other functions.
Create the object library and open the database
// ======== Database operation ================
/** * Open indexedDB. * dbName: database name; * version: indicates the database version. * No value can be passed. * /
const dbOpen = (dbName, version) = > {
// Create a database and open it
const name = config.dbName || dbName
const ver = config.ver || version
const dbRequest = myIndexedDB.open(name, ver)
// Record whether the database version has changed
let isChange = false
/* The database myIndex */ in this domain
if (config.debug) {
console.log('dbRequest - Open indexedDb database: ', dbRequest)
}
// Open the database promise
const dbPromise = new Promise((resolve, reject) = > {
// The database opened successfully callback
dbRequest.onsuccess = (event) = > {
// _db = event.target.result
// After the database is successfully opened, record the database object
_db = dbRequest.result
if (isChange) { // If changed, the initial data is set
setup().then(() = > {
resolve(_db)
})
} else {
resolve(_db)
}
}
dbRequest.onerror = (event) = > {
reject(event) // Return parameters}})/ / create a table
// The following events are automatically executed after the first successful opening or the version changes, generally used to initialize the database.
dbRequest.onupgradeneeded = (event) = > {
isChange = true
_db = event.target.result /* Database object */
// Create the object table
for (let i = 0; i < config.objectStores.length; i++) {
const object = config.objectStores[i]
// Create a table of objects
if(! _db.objectStoreNames.contains(object.objectStoreName)) {const objectStore = _db.createObjectStore(object.objectStoreName, { keyPath: 'id' }) /* Create the person repository primary key */
// objectStore = _db.createObjectStore('person',{autoIncrement:true}); /* Automatically create primary keys */
// Create index
for (let i = 0; i < object.index.length; i++) {
const index = object.index[i]
objectStore.createIndex(index.name, index.name, { unique: index.unique })
}
if (config.debug) {
console.log('Onupgradenneeded - which has built a new object repository:', objectStore)
}
}
}
}
// Return the Promise instance -- open the Indexed library
return dbPromise
}
Copy the code
This code is a bit long because it has two functions, one is to open the database, the other is to create the database.
IndexedDB logic is needed to determine if there is a local database when it is opened and if there is no database it triggers onUpgradenneeded events, creates a database and then opens the database. If there is a database, determine the version number which is higher than the local database which also triggers onUpgradenneeded events. So open needed and onupgradenneeded are connected.
Initialize an object
/** * Set initial data */
const setup = () = > {
// Define an instance of a Promise
const objectPromise = new Promise((resolve, reject) = > {
const arrStore = []
// Iterates to get a set of table names for opening transactions
for (const key in config.objects) {
arrStore.push(key)
}
const tranRequest = _db.transaction(arrStore, 'readwrite')
// Iterate, add data (object)
for (const key in config.objects) {
const objectArror = config.objects[key]
const store = tranRequest.objectStore(key)
// Clear the data
store.clear().onsuccess = (event) = > {
// Iterate to add data
for (let i = 0; i < objectArror.length; i++) {
store
.add(objectArror[i])
.onsuccess = (event) = > {
if (config.debug) {
console.log('Added successfully! key:${key}-i:${i}`)}}}}}// Return after traversal
tranRequest.oncomplete = (event) = > {
// tranRequest.commit()
if (config.debug) {
console.log('setup - oncomplete')
}
resolve()
}
tranRequest.onerror = (event) = > {
reject(event)
}
})
return objectPromise
}
Copy the code
Sometimes you need to set up some initialization data after building a library, so this function is designed. Setup will add default objects to the database based on the configuration in NF-Indexeddb.config.
Add the object
The basic series of additions, additions, modifications, and queries, whether databases or object libraries, are unavoidable.
/ / = = = = = = = = add deletion operation = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
/** * Add the object. * storeName: Name of the object repository; * object: Object to be added */
const addObject = (storeName, object) = > {
const _object = _vueToObject(object)
// Define an instance of a Promise
const objectPromise = new Promise((resolve, reject) = > {
// Define a function that is easy to call
const _addObject = () = > {
const tranRequest = _db.transaction(storeName, 'readwrite')
tranRequest
.objectStore(storeName) / / for the store
.add(_object) // Add the object
.onsuccess = (event) = > { // Callback after success
resolve(event.target.result) // Return the ID of the object
}
tranRequest.onerror = (event) = > {
reject(event)
}
}
// Check whether the database is open
if (typeof _db === 'undefined') {
dbOpen().then(() = > {
_addObject()
})
} else {
_addObject()
}
})
return objectPromise
}
Copy the code
Such a long code, only the implementation of an object to fill in the database operation, visible the original operation is how tedious.
All right, joking aside, the original idea was to add an object like this:
dbOpen().then(() = >{
addObject('blog', {id: 3.groupId: 1.title: 'These are three blogs'.addTime: '2020-10-15'.introduction: 'This is the blog introduction'.concent: 'Here's the blog in detail
Line 2'.viewCount: 1.agreeCount: 1})})Copy the code
That is to say, every time the operation first open the library, and then the operation, but think about this is a little troublesome? Can we not open the library, directly kailu? So the internal implementation code gets a little more complicated.
Modify the object
/** * Modify object. * storeName: Name of the object repository; * object: Object to be modified */
const updateObject = (storeName, object) = > {
const _object = _vueToObject(object)
// Define an instance of a Promise
const objectPromise = new Promise((resolve, reject) = > {
// Define a function that is easy to call
const _updateObject = () = > {
const tranRequest = _db.transaction(storeName, 'readwrite')
// Obtain the object by id
tranRequest
.objectStore(storeName) / / for the store
.get(_object.id) // Get the object
.onsuccess = (event) = > { // Callback after success
// Retrieve the object from the repository and merge the modified values into the object.
constnewObject = { ... event.target.result, ... _object }// Modify the data
tranRequest
.objectStore(storeName) / / for the store
.put(newObject) // Modify the object
.onsuccess = (event) = > { // Callback after success
if (config.debug) {
console.log('updateObject -- onsuccess- event:', event)
}
resolve(event.target.result)
}
}
tranRequest.onerror = (event) = > {
reject(event)
}
}
// Check whether the database is open
if (typeof _db === 'undefined') {
dbOpen().then(() = > {
_updateObject()
})
} else {
_updateObject()
}
})
return objectPromise
}
Copy the code
When you modify an object, it means that the new object overwrites the original object. At the beginning, you want to put it directly, but later in practice, you find that you may only modify part of the attributes, but not all the attributes. If you directly overwrite an object, won’t it cause incomplete parameters?
So you have to take the object out, merge it with the new object, and then put it back, and the code gets this long again.
Delete the object
/** * Delete object based on id. * storeName: Name of the object repository; * id: specifies the key value of the object to be deleted. * /
const deleteObject = (storeName, id) = > {
// Define an instance of a Promise
const objectPromise = new Promise((resolve, reject) = > {
// Define a function that is easy to call
const _deleteObject = () = > {
const tranRequest = _db.transaction(storeName, 'readwrite')
tranRequest
.objectStore(storeName) / / for the store
.delete(id) // Delete an object
.onsuccess = (event) = > { // Callback after success
resolve(event.target.result)
}
tranRequest.onerror = (event) = > {
reject(event)
}
}
// Check whether the database is open
if (typeof _db === 'undefined') {
dbOpen().then(() = > {
_deleteObject()
})
} else {
_deleteObject()
}
})
return objectPromise
}
Copy the code
In fact, delete the object, a delete can be, but still to determine whether to open the database first, so the code is still short.
Empty the warehouse of objects
/** * Empty all objects in the store. * storeName: Name of the object repository; * /
const clearStore = (storeName) = > {
// Define an instance of a Promise
const objectPromise = new Promise((resolve, reject) = > {
// Define a function that is easy to call
const _clearStore = () = > {
const tranRequest = _db.transaction(storeName, 'readwrite')
tranRequest
.objectStore(storeName) / / for the store
.clear() // Empty the object repository
.onsuccess = (event) = > { // Callback after success
resolve(event)
}
tranRequest.onerror = (event) = > {
reject(event)
}
}
// Check whether the database is open
if (typeof _db === 'undefined') {
dbOpen().then(() = > {
_clearStore()
})
} else {
_clearStore()
}
})
return objectPromise
}
Copy the code
- clear()
Clear all objects in the specified object repository. Exercise caution when performing this operation.
Deleting an object repository
/** * Delete the entire store. * storeName: Name of the object repository; * /
const deleteStore = (storeName) = > {
// Define an instance of a Promise
const objectPromise = new Promise((resolve, reject) = > {
// Define a function that is easy to call
const _deleteStore = () = > {
const tranRequest = _db.transaction(storeName, 'readwrite')
tranRequest
.objectStore(storeName) / / for the store
.delete() // Empty the object repository
.onsuccess = (event) = > { // Callback after success
resolve(event)
}
tranRequest.onerror = (event) = > {
reject(event) // Callback after failure}}// Check whether the database is open
if (typeof _db === 'undefined') {
dbOpen().then(() = > {
_deleteStore()
})
} else {
_deleteStore()
}
})
return objectPromise
}
Copy the code
This is even better, because you can delete the object store. Be more cautious.
Deleting a database
/** * Delete database. * dbName: database name; * /
const deleteDB = (dbName) = > {
// Define an instance of a Promise
const objectPromise = new Promise((resolve, reject) = > {
// Delete the entire database
myIndexedDB.deleteDatabase(dbName).onsuccess = (event) = > {
resolve(event)
}
})
return objectPromise
}
Copy the code
If you can create a database, you should be able to delete a database, which is what this is. This is very simple, do not judge whether open database, directly delete good. However, the front-end database should have such a function: the entire library deleted, can automatically restore the state of the line.
Press the primary key to get the object, or all of it
/** * Get the object. * storeName: Name of the object repository; * id: specifies the key value of the object to be obtained. * If no id is set, all objects in the store are returned */
const getObject = (storeName, id) = > {
const objectPromise = new Promise((resolve, reject) = > {
const _getObject = () = > {
const tranRequest = _db.transaction(storeName, 'readonly')
const store = tranRequest.objectStore(storeName) / / for the store
let dbRequest
// Decide whether to get one or all
if (typeof id === 'undefined') {
dbRequest = store.getAll()
} else {
dbRequest = store.get(id)
}
dbRequest.onsuccess = (event) = > { // Callback after success
if (config.debug) {
console.log('getObject -- onsuccess- event:', id, event)
}
resolve(event.target.result) // Return the object
}
tranRequest.onerror = (event) = > {
reject(event)
}
}
// Check whether the database is open
if (typeof _db === 'undefined') {
dbOpen().then(() = > {
_getObject()
})
} else {
_getObject()
}
})
return objectPromise
}
Copy the code
There are two functions here
- Obtain the corresponding object based on the ID
- Gets all objects in the object repository
We don’t want to have two function names, so we’re going to separate them by argument, get an object with an ID if we pass it, and return all of it if we don’t pass an ID.
Querying the Object Repository
/** * Obtain multiple objects based on the index + cursor. * storeName: Specifies the name of the object repository. * page: {* start: start, * count: count, * description:'next' * // next * // prev * // nextUnique * // PrevUnique * // PrevUnique * // prevUnique * // prevUnique * // prevUnique * // prevUnique * / Take only one *} * findInfo = {* indexName: 'groupId, * indexKind:' = '/ /' > ', '> =', '<', '< =', 'between', * indexValue: 1, * betweenInfo: {* v1:1, * v2:2, * v1isClose:true, * v2isClose:true, *}, * WHERE (object) => {* reutrn true/false *} *} */
const findObject = (storeName, findInfo = {}, page = {}) = > {
const _start = page.start || 0
const _count = page.count || 0
const _end = _start + _count
const _description = page.description || 'prev' // Default reverse order
// The primary key or index is used for the query
let keyRange = null
if (typeoffindInfo.indexName ! = ="undefined") {
if (typeoffindInfo.indexKind ! = ="undefined") {
const id = findInfo.indexValue
const dicRange = {
"=":IDBKeyRange.only(id),
">":IDBKeyRange.lowerBound(id, true),
"> =":IDBKeyRange.lowerBound(id),
"<":IDBKeyRange.upperBound(id, true),
"< =":IDBKeyRange.upperBound(id)
}
switch (findInfo.indexKind) {
case '=':
case '>':
case '> =':
case '<':
case '< =':
keyRange = dicRange[findInfo.indexKind]
break
case 'between':
const betweenInfo = findInfo.betweenInfo
keyRange = IDBKeyRange.bound(betweenInfo.v1,betweenInfo.v2,betweenInfo.v1isClose,betweenInfo.v2isClose)
break}}}console.log('findObject - keyRange', keyRange)
const objectPromise = new Promise((resolve, reject) = > {
// Define a function that is easy to call
const _findObjectByIndex = () = > {
const dataList = []
let cursorIndex = 0
const tranRequest = _db.transaction(storeName, 'readonly')
const store = tranRequest.objectStore(storeName)
let cursorRequest
// Determine whether to index the query
if (typeof findInfo.indexName === "undefined") {
cursorRequest = store.openCursor(keyRange, _description)
} else {
cursorRequest = store
.index(findInfo.indexName)
.openCursor(keyRange, _description)
}
cursorRequest.onsuccess = (event) = > {
const cursor = event.target.result
if (cursor) {
if (_end === 0 || (cursorIndex >= _start && cursorIndex < _end)) {
// Judge the hook function
if (typeof findInfo.where === 'function') {
if (findInfo.where(cursor.value, cursorIndex)) {
dataList.push(cursor.value)
cursorIndex++
}
} else { // No search criteria are set
dataList.push(cursor.value)
cursorIndex++
}
}
cursor.continue()
}
// tranRequest.commit()
}
tranRequest.oncomplete = (event) = > {
if (config.debug) {
console.log('findObjectByIndex - dataList', dataList)
}
resolve(dataList)
}
tranRequest.onerror = (event) = > {
console.log('findObjectByIndex - onerror', event)
reject(event)
}
}
// Check whether the database is open
if (typeof _db === 'undefined') {
dbOpen().then(() = > {
_findObjectByIndex()
})
} else {
_findObjectByIndex()
}
})
return objectPromise
}
Copy the code
Open the specified object repository and check whether index query is set. If not, open the repository cursor. If yes, open the index cursor. You can use hooks to query other properties. You can get data in pages, similar to mySQL limit.
A functional test
After encapsulation, write a test code to run, otherwise how to know whether good or not. So I wrote a relatively simple test code.
Building an object library
dbOpen().then(() = >{
// After creating a table, get all objects
getAll()
})
Copy the code
- dbOpen
Open the database and determine whether to create a database. If yes, the database is automatically created based on the configuration information
Then press F12, open the Application TAB, and you can find the database we created, as shown in the figure below:
We can take a look at the index, as shown here:
Add the object
addObject('blog', {id: new Date().valueOf(),
groupId: 1.title: 'These are three blogs'.addTime: '2020-10-15'.introduction: 'This is the blog introduction'.concent: 'Here's the blog in detail
Line 2'.viewCount: 1.agreeCount: 1
}).then((data) = > {
re.value = data
getAll()
})
Copy the code
- Warehouse,
The first argument is the name of the object repository, which is a string for now.
- object
The second parameter is the object to be added, whose properties must have a primary key and index, and so on.
- The return value
The object ID is returned upon success
Click the right button to refresh data, as shown in the figure:
The updated data is shown as follows:
Modify the object
updateObject('blog',blog).then((data) = > {
re.value = data
getAll()
})
Copy the code
- Warehouse,
The first argument is the name of the object repository, which is a string for now.
- object
The second parameter is the object to be modified, and the property can be incomplete.
- The return value
The object ID is returned upon success
Delete the object
deleteObject('blog',id).then((data) = > {
re.value = data
getAll()
})
Copy the code
- Warehouse,
The first argument is the name of the object repository, which is a string for now.
- object
The second argument is the ID of the object to delete.
- The return value
The object ID is returned upon success
Empty the warehouse of objects
clearStore('blog').then((data) = > {
re.value = data
getAll()
})
Copy the code
- Warehouse,
The first argument is the name of the object repository, which is a string for now.
- The return value
The object ID is returned upon success
Deleting an object repository
deleteStore('blog').then((data) = > {
re.value = data
getAll()
})
Copy the code
- Warehouse,
The first argument is the name of the object repository, which is a string for now.
- The return value
The object ID is returned upon success
Deleting a database
deleteDB('dbTest').then((data) = > {
re.value = data
getAll()
})
Copy the code
- Database name
The first parameter is the name of the database
Query function
// Query conditions
const findInfo = {
indexName: 'groupId'.indexKind: '='.// '>','>=','<','<=','between',
indexValue: 1.betweenInfo: {
v1:1.v2:2.v1isClose:true.v2isClose:true,},where: (object) = > {
if (findKey.value == ' ') return true
let re = false
if (object.title.indexOf(findKey.value) >= 0) {
re = true
}
if (object.introduction.indexOf(findKey.value) >= 0) {
re = true
}
if (object.concent.indexOf(findKey.value) >= 0) {
re = true
}
return re
}
}
const find = () = > {
findObject('blog', findInfo).then((data) = > {
findRe.value = data
})
}
Copy the code
- findInfo
Query information object, put all the information you need to query here
- indexName
Index name. This parameter is optional.
- indexKind
Query mode of the index attribute. If indexName is set, it must be set.
- indexValue
The query value of the index field
- betweenInfo
If indexKind = ‘between’, it needs to be set.
- v1
Begin to value
- v2
The end value
- v1isClose
Closed interval
- v2isClose
Closed interval
- where
Hook function, which may not be set. When we open the cursor inside, we return the object, and then we can make all kinds of criteria here.
I will not post the entire code, if you are interested, you can go to GitHub to see. Paste a picture of the folded effect:
It’s just a matter of putting the related functions together, writing an action class, then applying that class in setup, and then writing some code to associate the classes together.
The code is much easier to maintain.
The source code
Github.com/naturefwvue…
The online demo
Naturefwvue. Making. IO/nf – vue – CND /…
The resources
Liverpoolfc.tv: developer.mozilla.org/zh-CN/docs/…
Nguyen other blog: www.ruanyifeng.com/blog/2018/0…
Ken: www.cnblogs.com/dolphinX/p/…