Skip to content

Instantly share code, notes, and snippets.

@prof3ssorSt3v3
Created February 25, 2021 15:23
Show Gist options
  • Save prof3ssorSt3v3/4f98bf8fdf62a02d09d5ad822feddeed to your computer and use it in GitHub Desktop.
Save prof3ssorSt3v3/4f98bf8fdf62a02d09d5ad822feddeed to your computer and use it in GitHub Desktop.
IndexedDB Part 7 - Using Indexes and KeyRanges
import { uid } from './uid.js';
import { state } from './data.js';
//https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase
const IDB = (function init() {
let db = null;
let objectStore = null;
let DBOpenReq = indexedDB.open('WhiskeyDB', 4);
DBOpenReq.addEventListener('error', (err) => {
//Error occurred while trying to open DB
console.warn(err);
});
DBOpenReq.addEventListener('success', (ev) => {
//DB has been opened... after upgradeneeded
db = ev.target.result;
console.log('success opening DB');
if (typeof state !== 'undefined') {
let tx = makeTX('whiskeyStore', 'readwrite');
tx.oncomplete = (ev) => {
console.log('finished adding the data');
buildList();
};
let store = tx.objectStore('whiskeyStore');
let request = store.getAll();
// let request = store.delete .deleteIndex .clear()
request.onsuccess = (ev) => {
if (ev.target.result.length === 0) {
//OR ev.target.result.length !== state.length
state.forEach((obj) => {
let req = store.add(obj);
req.onsuccess = (ev) => {
console.log('added an object');
};
// tx.abort() - if you want to kill your transaction
req.onerror = (err) => {
console.warn(err);
};
});
}
};
} else {
buildList();
}
});
DBOpenReq.addEventListener('upgradeneeded', (ev) => {
//first time opening this DB
//OR a new version was passed into open()
db = ev.target.result;
let oldVersion = ev.oldVersion;
let newVersion = ev.newVersion || db.version;
console.log('DB updated from version', oldVersion, 'to', newVersion);
// console.log('upgrade', db);
if (db.objectStoreNames.contains('whiskeyStore')) {
db.deleteObjectStore('whiskeyStore');
}
//create the ObjectStore
objectStore = db.createObjectStore('whiskeyStore', {
keyPath: 'id',
});
//add the indexes
objectStore.createIndex('nameIDX', 'name', { unique: false });
objectStore.createIndex('countryIDX', 'country', { unique: false });
objectStore.createIndex('ageIDX', 'age', { unique: false });
objectStore.createIndex('editIDX', 'lastEdit', { unique: false });
});
document.getElementById('btnUpdate').addEventListener('click', (ev) => {
ev.preventDefault();
let name = document.getElementById('name').value.trim();
let country = document.getElementById('country').value.trim();
let age = parseInt(document.getElementById('age').value);
let owned = document.getElementById('isOwned').checked;
//id
let key = document.whiskeyForm.getAttribute('data-key');
if (key) {
let whiskey = {
id: key,
name,
country,
age,
owned,
lastEdit: Date.now(),
};
let tx = makeTX('whiskeyStore', 'readwrite');
tx.oncomplete = (ev) => {
console.log(ev);
buildList();
clearForm();
};
let store = tx.objectStore('whiskeyStore');
let request = store.put(whiskey); //request a put/update
request.onsuccess = (ev) => {
console.log('successfully updated an object');
//move on to the next request in the transaction or
//commit the transaction
};
request.onerror = (err) => {
console.log('error in request to update');
};
}
});
document.getElementById('btnDelete').addEventListener('click', (ev) => {
ev.preventDefault();
//id
let key = document.whiskeyForm.getAttribute('data-key');
if (key) {
let tx = makeTX('whiskeyStore', 'readwrite');
tx.oncomplete = (ev) => {
console.log(ev);
buildList();
clearForm();
};
let store = tx.objectStore('whiskeyStore');
let request = store.delete(key); //request a delete
request.onsuccess = (ev) => {
console.log('successfully deleted an object');
//move on to the next request in the transaction or
//commit the transaction
};
request.onerror = (err) => {
console.log('error in request to delete');
};
}
});
document.getElementById('btnAdd').addEventListener('click', (ev) => {
ev.preventDefault();
//one of the form buttons was clicked
let name = document.getElementById('name').value.trim();
let country = document.getElementById('country').value.trim();
let age = parseInt(document.getElementById('age').value);
let owned = document.getElementById('isOwned').checked;
let whiskey = {
id: uid(),
name,
country,
age,
owned,
lastEdit: Date.now(),
};
let tx = makeTX('whiskeyStore', 'readwrite');
tx.oncomplete = (ev) => {
//console.log(ev);
buildList();
clearForm();
};
let store = tx.objectStore('whiskeyStore');
let request = store.add(whiskey); //request an insert/add
request.onsuccess = (ev) => {
console.log('successfully added an object');
//move on to the next request in the transaction or
//commit the transaction
};
request.onerror = (err) => {
console.log('error in request to add');
};
});
function buildList() {
//use getAll to get an array of objects from our store
let list = document.querySelector('.wList');
list.innerHTML = `<li>Loading...</li>`;
let tx = makeTX('whiskeyStore', 'readonly');
tx.oncomplete = (ev) => {
//transaction for reading all objects is complete
};
let store = tx.objectStore('whiskeyStore');
//let getReq = store.getAll();
// let range = IDBKeyRange.lowerBound(14, true); //false 14 or higher... true 15 or higher
let range = IDBKeyRange.bound(1, 10, false, false);
let idx = store.index('ageIDX');
let getReq = idx.getAll(range);
//returns an array
//option can pass in a key or a keyRange
getReq.onsuccess = (ev) => {
//getAll was successful
let request = ev.target; //request === getReq === ev.target
//console.log({ request });
list.innerHTML = request.result
.map((whiskey) => {
return `<li data-key="${whiskey.id}"><span>${whiskey.name}</span> ${whiskey.age}</li>`;
})
.join('\n');
};
getReq.onerror = (err) => {
console.warn(err);
};
}
document.querySelector('.wList').addEventListener('click', (ev) => {
let li = ev.target.closest('[data-key]');
let id = li.getAttribute('data-key');
console.log(li, id);
let tx = makeTX('whiskeyStore', 'readonly');
tx.oncomplete = (ev) => {
//get transaction complete
};
let store = tx.objectStore('whiskeyStore');
let req = store.get(id);
req.onsuccess = (ev) => {
let request = ev.target;
let whiskey = request.result;
document.getElementById('name').value = whiskey.name;
document.getElementById('country').value = whiskey.country;
document.getElementById('age').value = whiskey.age;
document.getElementById('isOwned').checked = whiskey.owned;
//put the whiskey id into a form attribute
document.whiskeyForm.setAttribute('data-key', whiskey.id);
};
req.onerror = (err) => {
console.warn(err);
};
});
function makeTX(storeName, mode) {
let tx = db.transaction(storeName, mode);
tx.onerror = (err) => {
console.warn(err);
};
return tx;
}
document.getElementById('btnClear').addEventListener('click', clearForm);
function clearForm(ev) {
if (ev) ev.preventDefault();
document.whiskeyForm.reset();
document.whiskeyForm.removeAttribute('data-key');
}
})();
export const state = [
{
age: 8,
country: 'Scotland',
id: 'KLIE87ES-023IPDDPYG',
name: 'Lagavulin',
owned: true,
lastEdit: 1613990943287,
},
{
age: 3,
country: 'Canada',
id: 'KLIE1ST7-00IM8EM2Y6V7',
name: 'Crown Royal',
owned: true,
lastEdit: 1613897343287,
},
{
age: 16,
country: 'Scotland',
id: 'KLH80H0H-00TNHA9Z71KD',
name: 'Lagavulin',
owned: true,
lastEdit: 1614170943287,
},
{
age: 14,
country: 'Scotland',
id: 'KLH7ZQBT-026IOWX9I4N3',
name: 'Oban',
owned: true,
lastEdit: 1614062943287,
},
];
<!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" />
<title>Simple IndexedDB</title>
<link rel="stylesheet" href="main.css" />
</head>
<body>
<header>
<h1>IndexedDB</h1>
<h2>Using Vanilla JS</h2>
</header>
<main>
<p>This example is using plain vanilla JS and core indexedDB methods.</p>
<p>
If you are familiar with document databases like MongoDB, you can think
of an ObjectStore as a collection.
</p>
<p>The Core Object and Interfaces are:</p>
<ul>
<li>
<code>window.indexedDB</code> The database object holds Object Stores.
</li>
<li>
<code>IDBObjectStore</code> An Object Store inside a database. (A
Collection of objects)
</li>
<li>
<code>IDBRequest</code> A request object for add, put, delete, etc.
</li>
<li><code>IDBTransaction</code> A wrapper around requests.</li>
<li><code>IDBIndex</code> An index added to an Object Store</li>
<li>
<code>IDBCursor</code> To track current position inside the results of
a request.
</li>
<li><code>IDBKeyRange</code> A range of values for matching a key.</li>
</ul>
<form name="whiskeyForm">
<fieldset>
<p>
<label for="name">Whiskey: </label>
<input type="text" id="name" placeholder="Whiskey Name" required />
</p>
<p>
<label for="country">Country of Origin: </label>
<input type="text" id="country" value="" required />
</p>
<p>
<label for="age">Years Old: </label>
<input
type="text"
inputmode="numeric"
pattern="[\d]+"
id="age"
required
/>
</p>
<p>
<label for="isOwned">Owned: </label>
<input type="checkbox" id="isOwned" value="yes" />
</p>
<p>
<button id="btnAdd">Add Whiskey</button>
<button id="btnUpdate">Update Whiskey</button>
<button id="btnDelete">Delete Whiskey</button>
<button id="btnClear">Reset Form</button>
</p>
</fieldset>
</form>
<section>
<h3>List of Whiskeys</h3>
<ul class="wList">
<!-- build list here -->
</ul>
</section>
</main>
<footer>
<p>&copy; 2021 Chicken Stuff Inc.</p>
</footer>
<script type="module" src="app.js"></script>
</body>
</html>
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
html {
font-size: 16px;
font-weight: 300;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
line-height: 1.5;
color: #eee;
background-color: #333;
}
body {
background-color: #333;
min-height: 100vh;
padding-bottom: 4rem;
}
header,
main,
footer {
padding: 1rem 2rem;
}
h1 {
color: orange;
font-size: 3.6rem;
font-weight: 700;
}
h2 {
color: orangered;
font-size: 2.4rem;
font-weight: 700;
}
p {
font-size: 1.2rem;
font-weight: 300;
margin: 1rem 0;
}
li {
list-style-position: inside;
margin-left: 2rem;
font-size: 1.2rem;
font-weight: 500;
}
form {
padding: 1rem 1rem;
}
fieldset {
padding: 1rem;
}
form p {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
margin: 1rem 0;
}
label {
font-size: 1rem;
}
input[type='text'] {
padding: 0.2rem 1rem;
font-size: 1rem;
line-height: 1.5;
width: 30ch;
}
input[type='checkbox'] {
font-size: 1rem;
line-height: 1.5;
height: 1.5rem;
width: 1.5rem;
}
button {
padding: 0.2rem 2rem;
margin: 1rem 0;
font-size: 1.2rem;
border: none;
color: white;
background-color: cornflowerblue;
}
.highlighted {
color: #333;
background-color: gold;
}
export const uid = () => {
let timmy = Date.now().toString(36).toLocaleUpperCase();
let randy = parseInt(Math.random() * Number.MAX_SAFE_INTEGER);
randy = randy.toString(36).slice(0, 12).padStart(12, '0').toLocaleUpperCase();
return ''.concat(timmy, '-', randy);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment