IndexedDB

From Coders.Bay Wiki
Jump to navigation Jump to search

IndexedDB

Introduction[edit]

Index Database API also known as IndexedDB is a database that is built into the most widely used web-browsers. The common use case of IndexedDB is to store data offline for web applications. To work with the IndexedDB API you need to know JavaScript and async programming with JavaScript.

What is IndexedDB?[edit]

IndexedDB is an NoSQL storage system for the client-side that support also transactions to the usual operations, it is also using indexes for high performance searches (regardless of network availability). Unlike Web storage that supports up to 10MB (5MB by Safari and Opera) IndexedDB data storage limits are large (about 50%) but individually handled by different browsers. A database is private to a domain and subdomain, so any other site can’t access another website IndexedDB stores same-origin-policy.

Features[edit]

  • Like other NoSQL databases IndexedDB store key-value pairs meaning that values can be complex structured objects and keys can be properties of those objects. For quick searching and sorted enumeration indexes can be created by any property the objects.
  • IndexedDB is mostly asynchrone meaning that every operation is represented by a request. So it doesn’t block the application during an operation. IndexedDB uses a lot of requests. After each request you get a notified by an DOM event whether the operation was successful or not after that you can access the result or error code by an event listener.
  • IndexedDB is a transactional database. So you can’t execute operations outside of a transaction in IndexedDB. Transactions have a well-defined lifetime. When a transaction finishes, it is committed automatically. If an operation fails, all the operations in the transaction are rolled back and the state goes back to a known state.
  • IndexedDB is a object-oriented database, instead of tables, rows and columns IndexedDB use object stores which is similar to tables in a traditional relational database. In this object stores you store key - value pairs as earlier mentioned.The values can be any JavaScript type including boolean, number, string, undefined, null, date, object, array, regex, blob, and files.

Downsides[edit]

  • The browser can wipe out the data by user request, private browsing and when disk limit is reached.
  • There is no equivalent to the ‘LIKE’ operator in SQL for full text search.
  • For synchronising the server-side database with the client-side IndexedDB you have to write the code by your own because IndexedDB doesn’t take care of it.

Create a db[edit]

We will create a contact book that stores your contact name and email into the IndexedDB. For this example i will use:

HTML[edit]

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
    </style>
    <title>IndexedDB - Example</title>
</head>
<body>
    <h1>Contacts </h1>
    <form action="" id="addForm">
        <label for="name">Name:</label>
        <input type="text" id="name" required name="name" placeholder="Max Mustermann">
        <br>
        <label for="email">Email:</label>
        <input type="email" id="email" required name="email" placeholder="max@example.com">
        <br>
        <button type="submit" id="add">Add</button>
        <button type="submit" id="update">Update</button>
        <button type="submit" id="delete">Delete</button>
    </form>
    <h1>Contact list</h1>
    <p>You can focus the contact by clicking for updating!</p>
    <ul class="contactlist">
    </ul>
<script src="index.js"></script>
</body>
</html>

JavaScript[edit]

// we check if IndexedDB is supported by the browser
if (!window.indexedDB) {
    console.log(`Your browser doesn't support IndexedDB`);
}


// after that now let us create a db 
// later we will save our DB into this variable
let db = null

let contactsStore = null

// With the open method we will create a DB if it not exists already or open an DB that exists,
// we will pass the name of the DB and verison number(if the DB does not exist, it automatically starts at 1) into the open method.
const openDBReq = indexedDB.open('ContactBookDB',4)

// The following three events onerror,onsuccess and onupgradeneeded. 
// We are always going to have within our code.

// This event will raise if a error occurred while trying to open the DB
openDBReq.onerror = (err) => {
    console.log('A problem occurred',err)
}

// This event will raise if the DB has been opened and the version has not changed
openDBReq.onsuccess = (event) =>{
    db = event.target.result
    buildContactList('contacts','readonly')
    console.log('success',db)
}

// This event will raise when you open the DB the first time
// or a new version was passed into the open method.
openDBReq.onupgradeneeded = (event) => {
    db = event.target.result
    console.log('created or upograded',db)

    // we can access the old and new version number
    let oldVersNo = event.oldVersion
    let newVersNo = event.newVersion
    console.log(`Updated DB from version ${oldVersNo} to  verison ${newVersNo}`);

    // before you create a object store we need to check if it exists already in the db
    // without checking we will raise an error
    if(!db.objectStoreNames.contains('contacts')) {
        // with createObjectStore you create an object store where the key-value pairs are stored
        // in this case the email is an unique value for the index
        contactsStore = db.createObjectStore('contacts',{ keyPath: "email" })

    }
}

let nameInputElement = document.querySelector('#name')
let emailInputElement = document.querySelector('#email')
let addBtn = document.querySelector('#add')
let contact = {
    name,
    email,
}
 // add contact
addBtn.addEventListener('click',(event) => {
    event.preventDefault()
    // input value check
    if (nameInputElement.value === '' 
        && emailInputElement.value === '' 
        || emailInputElement.value.indexOf('@') === -1) {
        return
    }

    let contact = {
        name : nameInputElement.value.trim(),
        email : emailInputElement.value.trim()
    }

    console.log(contact.name, contact.email);

    // here we define an transaction with one request(operation)
    // to add a new contact
    const transaction = makeTransaction('contacts','readwrite')
    transaction.oncomplete = (event) => {
        nameInputElement.value = ''
        emailInputElement.value = ''
        buildContactList()
        console.log('transaction successed',event)
    }

    // now we fill our transaction with one request
    // if one request fails the transaction fails too
    // in this case we send a alert to the user that the email address 
    // exists already
    const store = transaction.objectStore('contacts')
    const addReq = store.add(contact)
    addReq.onsuccess = (event) => {
        nameInputElement.value = ''
        emailInputElement.value = ''
        contact.name = ''
        contact.email = ''
        console.log('request successed',event)
    }
    // every email address need to be unique
    addReq.onerror = (err) => {
        alert('Email exists alredy in your contacts!')
    }


})

// update contact
let updateBtn = document.querySelector('#update')
updateBtn.addEventListener('click',(event)=>{
    event.preventDefault()

    if (nameInputElement.value === '' 
    && emailInputElement.value === '' 
    || emailInputElement.value.indexOf('@') === -1) {
        return
    }
    let contact = {
        name : nameInputElement.value.trim(),
        email : emailInputElement.value.trim()
    }
    if (emailInputElement) {
        const transaction = makeTransaction('contacts','readwrite')
        transaction.oncomplete = (event) => {
            nameInputElement.value = ''
            emailInputElement.value = ''
            contact.name = ''
            contact.email = ''
            buildContactList()
        }
    
        const store = transaction.objectStore('contacts')
        const updateReq = store.put(contact)
        updateReq.onsuccess = (event) => {
            console.log('update successfull',event)
        }
        // every email address need to be unique
        updateReq.onerror = (err) => {
            console.log(err)
        }
    }
})

// select contact
document.querySelector('.contactlist').addEventListener('click',(event) =>{
    let listElement = event.target.closest('[id]')
    let email = listElement.getAttribute('id')
    console.log(email,listElement)

    const transaction = makeTransaction('contacts','readonly')
    transaction.oncomplete = (event) => {

    }

    let store = transaction.objectStore('contacts')
    let getReq = store.get(email)
    getReq.onsuccess = (event) => {
        let contact = event.target.result
        document.querySelector('#name').value = contact.name
        document.querySelector('#email').value = contact.email

    }
    getReq.onerror = (err) => {
        console.log(err)
    }
})

// delete contact
let deleteBtn = document.querySelector('#delete')
deleteBtn.addEventListener('click',(event) => {
    event.preventDefault()

    if (emailInputElement.value){
        const transaction = makeTransaction('contacts','readwrite')
        transaction.oncomplete = (event) => {
            nameInputElement.value = ''
            emailInputElement.value = ''
            contact.name = ''
            contact.email = ''
            buildContactList()
        }
        const store = transaction.objectStore('contacts')
        const deleteReq = store.delete(emailInputElement.value)
        deleteReq.onsuccess = (event) => {
            console.log('delete successfull',event)
        }
        // every email address need to be unique
        deleteReq.onerror = (err) => {
            console.log(err)
        }
    }
})

// we will wrap the transaction object and the error listener into a function
// because this will not change and we need it in every request
function makeTransaction(objectStore, mode) {
    const transaction = db.transaction(objectStore,mode)
    transaction.onerror = (err) => {
        console.log('Failure rollback',err)
    }
    return transaction
}


// here we will use the getAll method to get an array of objects
// and display the result
function buildContactList(){
    let contactListElement = document.querySelector('.contactlist')
    //contactListElement.innerHTML =`<li> Loading...</li>`
    const transaction = makeTransaction('contacts','readonly')
    transaction.oncomplete = (event) => {
        console.log('transaction successed', event)
    }
    let store = transaction.objectStore('contacts')
    let getAllReq = store.getAll()
    getAllReq.onsuccess = (event) => {
        contactListElement.innerHTML = event.target.result.map((contact) =>{
            return `<li id=${contact.email}><span>Name: ${contact.name}</span> Email: ${contact.email}</li>`
        }).join('\n')

        console.log(event.target.result)
    }
    getAllReq.onerror = (err) => {
        console.log('request failure',err)
    }
}

IndexedDB Promised Library vs IndexedDB API[edit]

IndexedDB can also be used with the concept of promises, thanks to Jake Archibald.

Instead of calling the request.onsucess, request.onerror events we now able to interact with the request by promise chains.

IndexedDB Promised IndexedDB API
Open database idb.open(name, version, upgradeCallback) indexedDB.open(name, version)
Upgrade database Inside upgradeCallback request.onupgradeneeded
Success .then request.onsuccess
Error .catch request.onerror

Weblinks[edit]