Recipe 5.4
Searching for String Values With Cursors
One of IndexedDB’s limitations is there isn’t a built-in way to match object properties against a string pattern using indexes.
It’s less efficient, but you can do this by using a cursor.
This employee database can be filtered by employee name. Type a partial name pattern to filter the list.
Demo
Name | Last name | Department |
---|---|---|
Loading...
Loading Users
|
Code
JavaScript
// Once the database is opened, it will be assigned to this variable.
let employeeDb;
/**
* Searches for employees by name.
*
* @param name A query string to match employee names
* @param onSuccess Success callback that will receive the matching employees.
*/
function searchEmployees(name, onSuccess) {
// An array to hold all contacts with a name containing the query text.
const results = [];
const query = name.toLowerCase();
const request = employeeDb
.transaction(['employees'], 'readonly')
.objectStore('employees')
.openCursor();
// The cursor request will emit a `success` event for each object it finds
request.addEventListener('success', () => {
const cursor = request.result;
if (cursor) {
const name = `${cursor.value.firstName} ${cursor.value.lastName}`.toLowerCase();
// Add the contact to the result array if it matches the query.
if (name.includes(query)) {
results.push(cursor.value);
}
// Continue to the next record.
cursor.continue();
} else {
onSuccess(results);
}
});
request.addEventListener('error', () => {
console.error('Error searching employees:', request.error);
});
}
/**
* Opens the database, creating the object store and index if needed.
* Once the database is ready, `onSuccess` will be called with the database object.
*
* @param onSuccess A callback function that is executed when the database is ready
*/
function openDatabase(onSuccess) {
const request = indexedDB.open('employees-cursor');
// Keep track of whether or not the database needs to be populated.
let shouldPopulate = false;
request.addEventListener('upgradeneeded', () => {
const db = request.result;
// New employee objects will be given an auto-generated
// `id` property which serves as its key.
const employeesStore = db.createObjectStore('employees', {
keyPath: 'id',
autoIncrement: true,
});
// If the upgradeneeded event was triggered, that means the database
// didn't exist yet, so the database needs to be populated.
// Data can't be inserted during this event, so the success handler
// will check its value and populate the database if needed.
shouldPopulate = true;
});
request.addEventListener('success', () => {
const db = request.result;
if (shouldPopulate) {
// If the flag is set, populate the database, passing along the
// onSuccess callback
populateDatabase(db, onSuccess);
} else {
// Otherwise, call the onSuccess callback
onSuccess(db);
}
});
request.addEventListener('error', () => {
console.error('Error opening database:', request.error);
});
}
/**
* Gets employees, either by loading all employees or querying by
* a name pattern.
*
* @param name (optional) A name query string
* @param onSuccess Success callback that will receive the employees.
*/
function getEmployees(name, onSuccess) {
if (name) {
searchEmployees(name, onSuccess);
} else {
getAllEmployees(onSuccess);
}
}
function getAllEmployees(onSuccess) {
const request = employeeDb
.transaction(['employees'], 'readonly')
.objectStore('employees')
.getAll();
request.addEventListener('success', () => {
console.log('Got employees:', request.result);
onSuccess(request.result);
});
request.addEventListener('error', () => {
console.log('Error loading employees:', request.error);
});
}
/**
* Reads the employees from the database, and renders them in the table.
* @param name The name to filter by (optional)
*/
function renderEmployees(name) {
getEmployees(name, employees => {
const tbody = document.querySelector('.table tbody');
// Remove the current employee rows
tbody.innerHTML = '';
employees.forEach(employee => {
const row = document.createElement('tr');
const firstName = document.createElement('td');
firstName.textContent = employee.firstName;
row.appendChild(firstName);
const lastName = document.createElement('td');
lastName.textContent = employee.lastName;
row.appendChild(lastName);
const department = document.createElement('td');
department.textContent = employee.department;
row.appendChild(department);
tbody.appendChild(row);
})
});
}
openDatabase(db => {
employeeDb = db;
renderEmployees();
});
const searchField = document.querySelector('#name-filter');
searchField.addEventListener('input', () => {
renderEmployees(searchField.value);
});
/**
* Populates the database with seed data.
* @param db the IndexedDB object
* @param onSuccess A callback to execute once the database is populated
*/
function populateDatabase(db, onSuccess) {
const employees = [
{ firstName: "John", lastName: "Doe", department: "Sales" },
{ firstName: "Jane", lastName: "Smith", department: "HR" },
{ firstName: "Michael", lastName: "Johnson", department: "Finance" },
{ firstName: "Emily", lastName: "Williams", department: "Marketing" },
{ firstName: "David", lastName: "Brown", department: "IT" },
{ firstName: "Sarah", lastName: "Miller", department: "Operations" },
{ firstName: "James", lastName: "Taylor", department: "Sales" },
{ firstName: "Linda", lastName: "Anderson", department: "HR" },
{ firstName: "Robert", lastName: "Clark", department: "Finance" },
{ firstName: "Karen", lastName: "White", department: "Marketing" }
];
const transaction = db.transaction(['employees'], 'readwrite');
const store = transaction.objectStore('employees');
employees.forEach(employee => {
store.add(employee);
});
transaction.addEventListener('complete', () => {
console.log('Database was populated');
onSuccess(db);
});
}
HTML
<div>
<input
id="name-filter"
class="form-control"
type="text"
placeholder="Search by employee name..."
>
</div>
<table id="users" class="table">
<thead>
<tr>
<th class="w-25">Name</th>
<th class="w-25">Last name</th>
<th class="w-25">Department</th>
</tr>
</thead>
<tbody>
<tr id="loader">
<td colspan="3" class="text-center p-4">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<div>Loading Users</div>
</td>
</tr>
</tbody>
</table>