JavaScript en IndexedDB

JavaScript en IndexedDB

In dit artikel leggen we JavaScript en IndexedDB uit.

Deze tutorial biedt een stapsgewijze uitleg van JavaScript en IndexedDB, inclusief praktische voorbeeldcode bij elke stap om uw begrip te verdiepen.

YouTube Video

javascript-indexed-db.html
  1<!DOCTYPE html>
  2<html lang="en">
  3<head>
  4  <meta charset="UTF-8">
  5  <title>JavaScript &amp; HTML</title>
  6  <style>
  7    * {
  8        box-sizing: border-box;
  9    }
 10
 11    body {
 12        margin: 0;
 13        padding: 1em;
 14        padding-bottom: 10em;
 15        font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
 16        background-color: #f7f9fc;
 17        color: #333;
 18        line-height: 1.6;
 19    }
 20
 21    .container {
 22        max-width: 800px;
 23        margin: 0 auto;
 24        padding: 1em;
 25        background-color: #ffffff;
 26        border: 1px solid #ccc;
 27        border-radius: 10px;
 28        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
 29    }
 30
 31    .container-flex {
 32        display: flex;
 33        flex-wrap: wrap;
 34        gap: 2em;
 35        max-width: 1000px;
 36        margin: 0 auto;
 37        padding: 1em;
 38        background-color: #ffffff;
 39        border: 1px solid #ccc;
 40        border-radius: 10px;
 41        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
 42    }
 43
 44    .left-column, .right-column {
 45        flex: 1 1 200px;
 46        min-width: 200px;
 47    }
 48
 49    h1, h2 {
 50        font-size: 1.2rem;
 51        color: #007bff;
 52        margin-top: 0.5em;
 53        margin-bottom: 0.5em;
 54        border-left: 5px solid #007bff;
 55        padding-left: 0.6em;
 56        background-color: #e9f2ff;
 57    }
 58
 59    button {
 60        display: block;
 61        margin: 1em auto;
 62        padding: 0.75em 1.5em;
 63        font-size: 1rem;
 64        background-color: #007bff;
 65        color: white;
 66        border: none;
 67        border-radius: 6px;
 68        cursor: pointer;
 69        transition: background-color 0.3s ease;
 70    }
 71
 72    button:hover {
 73        background-color: #0056b3;
 74    }
 75
 76    #output {
 77        margin-top: 1em;
 78        background-color: #1e1e1e;
 79        color: #0f0;
 80        padding: 1em;
 81        border-radius: 8px;
 82        min-height: 200px;
 83        font-family: Consolas, monospace;
 84        font-size: 0.95rem;
 85        overflow-y: auto;
 86        white-space: pre-wrap;
 87    }
 88
 89    .highlight {
 90        outline: 3px solid #ffc107; /* yellow border */
 91        background-color: #fff8e1;  /* soft yellow background */
 92        transition: background-color 0.3s ease, outline 0.3s ease;
 93    }
 94
 95    .active {
 96        background-color: #28a745; /* green background */
 97        color: #fff;
 98        box-shadow: 0 0 10px rgba(40, 167, 69, 0.5);
 99        transition: background-color 0.3s ease, box-shadow 0.3s ease;
100    }
101  </style>
102</head>
103<body>
104    <div class="container">
105        <h1>JavaScript Console</h1>
106        <button id="executeBtn">Execute</button>
107        <div id="output"></div>
108    </div>
109
110    <script>
111        // Override console.log to display messages in the #output element
112        (function () {
113            // Override console.log
114            const originalLog = console.log;
115            console.log = function (...args) {
116                originalLog.apply(console, args);
117                const message = document.createElement('div');
118                message.textContent = args
119                    .map(arg => (typeof arg === "object" && arg !== null ? JSON.stringify(arg) : String(arg)))
120                    .join(" ");
121                output.appendChild(message);
122            };
123
124            // Override console.error
125            const originalError = console.error;
126            console.error = function (...args) {
127                originalError.apply(console, args);
128                const message = document.createElement('div');
129                message.textContent = args
130                    .map(arg => (typeof arg === "object" && arg !== null ? JSON.stringify(arg) : String(arg)))
131                    .join(" ");
132                message.style.color = 'red'; // Color error messages red
133                output.appendChild(message);
134            };
135        })();
136
137        document.getElementById('executeBtn').addEventListener('click', () => {
138            // Prevent multiple loads
139            if (document.getElementById('externalScript')) return;
140
141            const script = document.createElement('script');
142            script.src = 'javascript-indexed-db.js';
143            script.id = 'externalScript';
144            //script.onload = () => console.log('javascript-indexed-db.js loaded and executed.');
145            //script.onerror = () => console.log('Failed to load javascript-indexed-db.js.');
146            document.body.appendChild(script);
147        });
148    </script>
149</body>
150</html>

JavaScript en IndexedDB

IndexedDB is een asynchrone key-value database die in browsers is ingebouwd. Het biedt functies die vergelijkbaar zijn met relationele databases en stelt u in staat om grote hoeveelheden gestructureerde gegevens aan de clientzijde op te slaan en te zoeken. Het is vooral nuttig voor toepassingen die offline werken en PWAs (Progressive Web Apps).

Kenmerken van IndexedDB

  • Werkt op een asynchrone en gebeurtenisgestuurde manier.
  • JavaScript-objecten kunnen opgeslagen worden in object stores.
  • Zoeken met queries of indexen is mogelijk.
  • Het heeft een grote opslagcapaciteit (honderden MB of meer), waardoor u veel meer data kunt opslaan dan met cookies of localStorage.

Een database openen en aanmaken

Om IndexedDB te gebruiken, moet u eerst een database openen. Als deze niet bestaat, wordt deze automatisch aangemaakt.

1const request = indexedDB.open('MyDatabase', 1); // Specify DB name and version
  • De open-methode opent de database asynchroon en activeert de volgende drie gebeurtenissen.

onsuccess

1// Fired when database is successfully opened
2request.onsuccess = (event) => {
3    const db = event.target.result; // Database instance
4    console.log('Database opened successfully:', db.name);
5};
  • De onsuccess-gebeurtenis wordt geactiveerd wanneer de database succesvol is geopend. Vervolgoperaties moeten worden uitgevoerd met request.result, die op dit moment beschikbaar is.

onerror

1// Fired when database fails to open
2request.onerror = (event) => {
3    console.error('Failed to open database:', event.target.error);
4};
  • De onerror-gebeurtenis wordt geactiveerd wanneer het openen van de database mislukt. Foutregistratie en foutafhandeling moeten hier worden uitgevoerd.

onupgradeneeded

 1// Fired when database is newly created or upgraded
 2request.onupgradeneeded = (event) => {
 3    const db = event.target.result;
 4    console.log('Database upgrade needed (or newly created):', db.name);
 5
 6    // Example: Create an object store (like a table) if it doesn’t exist
 7    if (!db.objectStoreNames.contains('users')) {
 8        db.createObjectStore('users', { keyPath: 'id' });
 9        console.log('Object store "users" created');
10    }
11};
  • onupgradeneeded wordt geactiveerd wanneer de database nieuw is aangemaakt of wanneer de opgegeven versie hoger is dan de huidige versie. Het aanmaken van tabellen (object stores) en het definiëren van het schema moet op dit moment gebeuren.

Een object store aanmaken

Maak eerst een 'object store' (vergelijkbaar met een tabel) aan binnen onupgradeneeded.

1request.onupgradeneeded = function (event) {
2    const db = event.target.result;
3    console.log("onupgradeneeded triggered. Database version:", db.version);
4    if (!db.objectStoreNames.contains('users')) {
5        console.log("Creating object store: users");
6        const store = db.createObjectStore('users', { keyPath: 'id' });
7        store.createIndex('name', 'name', { unique: false });
8    }
9};

Hier worden de volgende instellingen toegepast:.

  • createObjectStore createObjectStore is een methode om een nieuwe object store in de database aan te maken. U kunt records opslaan in een object store en bepalen hoe gegevens worden beheerd door keyPath en andere opties te specificeren. Dit proces moet worden uitgevoerd binnen de onupgradeneeded-gebeurtenis.

  • keyPath: 'id' Stel de keyPath in op de id-eigenschap, die elk record uniek identificeert als primaire sleutel. Hierdoor kan id automatisch worden gebruikt bij het toevoegen, zoeken of bijwerken van gegevens.

  • createIndex Gebruik de createIndex-methode om een index op basis van de name-eigenschap aan te maken voor zoekdoeleinden. Door unique: false in te stellen zijn meerdere records met dezelfde name toegestaan.

Succesvolle databaseverbindingen verwerken

Wijs processen toe aan de onsuccess-gebeurtenis die moeten worden uitgevoerd wanneer de databaseverbinding succesvol is. Binnen dit proces verkrijgt u de database-instantie en bereidt u het lezen en schrijven van gegevens voor.

1request.onsuccess = function (event) {
2    const db = event.target.result;
3    console.log('Database opened successfully');
4    // Use db for reading and writing in subsequent operations
5};
  • Dit proces wordt uitgevoerd wanneer de verbinding met de IndexedDB-database succesvol is.

  • event.target.result bevat de geopende database-instantie (IDBDatabase-object), die wordt gebruikt om transacties te starten en object stores te benaderen.

  • Feitelijke lees- en schrijfoperaties zoals toevoegen, ophalen, bijwerken en verwijderen van gegevens worden uitgevoerd met het db-object.

Op dit moment is de database gereed en kunt u veilig transacties starten.

Gegevens toevoegen

Zo voegt u nieuwe gegevens toe aan IndexedDB.

 1function addUser(db, user) {
 2    const transaction = db.transaction('users', 'readwrite');
 3    const store = transaction.objectStore('users');
 4    const request = store.add(user);
 5
 6    request.onsuccess = () => {
 7        console.log('User added:', user);
 8    };
 9
10    request.onerror = () => {
11        console.error('Add failed:', request.error);
12    };
13}
14
15// Example: Add a user
16request.onsuccess = function (event) {
17    const db = event.target.result;
18    addUser(db, { id: 1, name: 'Alice' });
19    addUser(db, { id: 2, name: 'Bob' });
20    addUser(db, { id: 3, name: 'John' });
21};
  • Maak een transactie aan met db.transaction() en geef de object store en de modus aan (in dit geval readwrite).
  • Voeg nieuwe gegevens toe met de methode store.add().
  • Transacties worden automatisch bevestigd, maar als u meerdere bewerkingen wilt groeperen, kunt u deze beheren met transactiebereik-eindgebeurtenissen.

Gegevens ophalen (zoeken op primaire sleutel)

Zo haalt u specifieke gegevens op met behulp van de primaire sleutel.

 1function getUserById(db, id) {
 2    const transaction = db.transaction('users', 'readonly');
 3    const store = transaction.objectStore('users');
 4    const request = store.get(id);
 5
 6    request.onsuccess = () => {
 7        if (request.result) {
 8            console.log('User found:', request.result);
 9        } else {
10            console.log('User not found');
11        }
12    };
13
14    request.onerror = () => {
15        console.error('Error retrieving user:', request.error);
16    };
17}
18
19// Example: Get a user by id
20request.onsuccess = function (event) {
21    const db = event.target.result;
22    getUserById(db, 1);
23};
  • Maak een alleen-lezen transactie met db.transaction().
  • Gebruik store.get(id) om de gegevens op te halen die overeenkomen met de opgegeven primaire sleutel.
  • onsuccess wordt aangeroepen als het ophalen succesvol is, en het resultaat wordt weergegeven als het beschikbaar is. Als er geen resultaat is, wordt dit behandeld als 'geen overeenkomstige gegevens.'.

Zoeken met een index

Als u wilt zoeken op andere eigenschappen dan de primaire sleutel, gebruik dan de eerder gemaakte index.

 1function getUserByName(db, name) {
 2    const transaction = db.transaction('users', 'readonly');
 3    const store = transaction.objectStore('users');
 4    const index = store.index('name');
 5    const request = index.get(name);
 6
 7    request.onsuccess = () => {
 8        if (request.result) {
 9            console.log('User by name:', request.result);
10        } else {
11            console.log('No user found with that name');
12        }
13    };
14
15    request.onerror = () => {
16        console.error('Error retrieving user by name:', request.error);
17    };
18}
19
20// Example: Get a user by id
21request.onsuccess = function (event) {
22    const db = event.target.result;
23    getUserByName(db, 'Alice');
24};
  • Benader de vooraf aangemaakte name-index via store.index('name').
  • index.get(value) haalt het eerste record op met de overeenkomende waarde. Als er meerdere records zijn met dezelfde waarde, kunt u ze allemaal ophalen met index.getAll(value).

Gegevens bijwerken

Om bestaande gegevens bij te werken (overschrijven), gebruik de methode put().

Als er een record met dezelfde primaire sleutel bestaat, wordt deze bijgewerkt; zo niet, dan wordt een nieuw record toegevoegd.

 1function updateUser(db, updatedUser) {
 2    const transaction = db.transaction('users', 'readwrite');
 3    const store = transaction.objectStore('users');
 4    const request = store.put(updatedUser);
 5
 6    request.onsuccess = () => {
 7        console.log('User updated:', updatedUser);
 8    };
 9
10    request.onerror = () => {
11        console.error('Update failed:', request.error);
12    };
13}
14
15// Example: Update user
16request.onsuccess = async (event) => {
17    const db = event.target.result;
18
19    // Test : update existing user
20    updateUser(db, { id: 3, name: 'John Updated' });
21
22    // Test : insert new user
23    updateUser(db, { id: 4, name: 'Charlie' });
24};
  • put() is een handige methode die zowel bijwerken als toevoegen ondersteunt.
  • Als er al gegevens zijn met dezelfde primaire sleutel, worden deze overschreven.
  • Als u wilt controleren of het record bestaat voor het bijwerken, kunt u vooraf get() gebruiken.

Gegevens verwijderen

Gebruik de methode delete() om gegevens te verwijderen die overeenkomen met een opgegeven primaire sleutel.

 1function deleteUser(db, id) {
 2    const transaction = db.transaction('users', 'readwrite');
 3    const store = transaction.objectStore('users');
 4    const request = store.delete(id);
 5
 6    request.onsuccess = () => {
 7        console.log(`User with id=${id} deleted successfully`);
 8    };
 9
10    request.onerror = () => {
11        console.error(`Failed to delete user with id=${id}:`, request.error);
12    };
13}
14
15// Example: Delete a user by id
16request.onsuccess = function (event) {
17    const db = event.target.result;
18    deleteUser(db, 4);
19};
  • Gebruik store.delete(id) om gegevens met een overeenkomende primaire sleutel te verwijderen.
  • Let op: zelfs als de gegevens niet bestaan, zal er geen fout optreden en wordt het toch als succesvol beschouwd.
  • Door foutafhandeling te implementeren wordt uw code robuuster.

Alle gegevens ophalen

getAll()

Gebruik de methode getAll() om alle records in de object store op te halen.

 1function getAllUsers(db) {
 2    const transaction = db.transaction('users', 'readonly');
 3    const store = transaction.objectStore('users');
 4    const request = store.getAll();
 5
 6    request.onsuccess = () => {
 7        console.log('All users:', request.result);
 8    };
 9
10    request.onerror = () => {
11        console.error('Failed to retrieve users:', request.error);
12    };
13}
14
15// Example: Get all users
16request.onsuccess = function (event) {
17    const db = event.target.result;
18    getAllUsers(db);
19};
  • getAll() haalt alle records in de opgegeven object store op als een array.
  • Zelfs bij het ophalen van grote hoeveelheden gegevens tegelijk, wordt dit efficiënt verwerkt.
  • De resultaten worden als een array opgeslagen in request.result.
  • Voeg altijd foutafhandeling toe om met mislukkingen om te gaan.

openCursor()

openCursor() is een methode om records in een object store of index opeenvolgend te doorlopen. Het is nuttig als u gegevens één voor één wilt verwerken in plaats van allemaal tegelijk op te halen.

 1function getAllUsersWithCursor(db) {
 2    const transaction = db.transaction('users', 'readonly');
 3    const store = transaction.objectStore('users');
 4    const request = store.openCursor();
 5
 6    request.onsuccess = () => {
 7        const cursor = request.result;
 8        if (cursor) {
 9            console.log('User:', cursor.value); // Process the current record
10            cursor.continue(); // Move to the next record
11        } else {
12            console.log('All users have been processed.');
13        }
14    };
15
16    request.onerror = () => {
17        console.error('Failed to open cursor:', request.error);
18    };
19}
20
21// Example: Get all users
22request.onsuccess = function (event) {
23    const db = event.target.result;
24    getAllUsersWithCursor(db);
25};
  • Door openCursor() te gebruiken start u een cursor en haalt u één voor één alle records in de object store op.
  • Gebruik cursor.value om het gegevensobject van het huidige record te krijgen.
  • Verplaats de cursor naar het volgende record met cursor.continue().
  • Wanneer cursor === null is, zijn alle records doorlopen.

Voorbeeld van een updateproces met behulp van openCursor()

Bijvoorbeeld, het proces om de naam van een gebruiker met de naam Alice te wijzigen in Alicia ziet er als volgt uit:.

 1function updateUserName(db, oldName, newName) {
 2    const transaction = db.transaction('users', 'readwrite');
 3    const store = transaction.objectStore('users');
 4    const index = store.index('name'); // Use the 'name' index
 5    const request = index.openCursor(IDBKeyRange.only(oldName));
 6
 7    request.onsuccess = () => {
 8        const cursor = request.result;
 9        if (cursor) {
10            const user = cursor.value;
11            user.name = newName; // Update the name
12            const updateRequest = cursor.update(user);
13
14            updateRequest.onsuccess = () => {
15                console.log('Updated user:', user);
16            };
17
18            updateRequest.onerror = () => {
19                console.error('Failed to update user:', updateRequest.error);
20            };
21
22            cursor.continue();
23        } else {
24            console.log('All matching users have been updated.');
25        }
26    };
27
28    request.onerror = () => {
29        console.error('Cursor error:', request.error);
30    };
31}
32
33// Example: Update user name
34request.onsuccess = function (event) {
35    const db = event.target.result;
36    updateUserName(db, 'Alice', 'Alicia');
37};
  • IDBKeyRange.only(oldName) Door gebruik te maken van IDBKeyRange.only kun je alleen de records targeten waarvan de sleutel exact overeenkomt met oldName. Dit is handig wanneer je direct toegang wilt krijgen tot een specifieke waarde.

  • cursor.update() Na het aanpassen van cursor.value zal het aanroepen van update() het bijbehorende record overschrijven.

  • Omgaan met meerdere overeenkomsten Door cursor.continue() aan te roepen, kun je de cursor naar het volgende overeenkomende record verplaatsen. Hierdoor kun je meerdere records die aan dezelfde sleutel of voorwaarde voldoen achtereenvolgens verwerken.

  • Foutafhandeling Door logberichten weer te geven in onerror wanneer een proces faalt, wordt het eenvoudiger om oorzaken te achterhalen en problemen tijdens gebruik op te lossen.

Voorbeeld van een verwijderingsproces met behulp van openCursor()

Bijvoorbeeld, het proces om alle gebruikers waarvan de naam Bob is te verwijderen ziet er als volgt uit:.

 1function deleteUsersByName(db, targetName) {
 2    const transaction = db.transaction('users', 'readwrite');
 3    const store = transaction.objectStore('users');
 4    const index = store.index('name');
 5    const request = index.openCursor(IDBKeyRange.only(targetName));
 6
 7    request.onsuccess = () => {
 8        const cursor = request.result;
 9        if (cursor) {
10            const deleteRequest = cursor.delete();
11
12            deleteRequest.onsuccess = () => {
13                console.log('Deleted user:', cursor.value);
14            };
15
16            deleteRequest.onerror = () => {
17                console.error('Failed to delete user:', deleteRequest.error);
18            };
19
20            cursor.continue();
21        } else {
22            console.log('All matching users have been deleted.');
23        }
24    };
25
26    request.onerror = () => {
27        console.error('Cursor error:', request.error);
28    };
29}
30
31// Example: Delete user by name
32request.onsuccess = function (event) {
33    const db = event.target.result;
34    deleteUsersByName(db, 'Bob');
35};
  • cursor.delete() Met cursor.delete() wordt het record op de huidige positie van de cursor verwijderd. Omdat het resultaat asynchroon wordt teruggegeven, kun je het proces in onsuccess controleren.

Notities over transacties en asynchrone verwerking

IndexedDB is asynchroon en gebeurtenisgestuurd. Alle bewerkingen moeten worden afgehandeld met onsuccess of onerror events. Als je meerdere processen wilt groeperen, is het handig om ze in een Promise te wikkelen.

 1function openDatabase() {
 2    return new Promise((resolve, reject) => {
 3        const request = indexedDB.open('MyDatabase', 1);
 4
 5        request.onupgradeneeded = function (event) {
 6            const db = event.target.result;
 7
 8            // Initialization process (creating object stores and indexes, etc.)
 9            const store = db.createObjectStore('users', { keyPath: 'id' });
10            store.createIndex('name', 'name', { unique: false });
11        };
12
13        request.onsuccess = function (event) {
14            const db = event.target.result;
15            resolve(db);
16        };
17
18        request.onerror = function () {
19            reject(request.error);
20        };
21    });
22}
23
24function addUserAsync(db, user) {
25    return new Promise((resolve, reject) => {
26        const transaction = db.transaction('users', 'readwrite');
27        const store = transaction.objectStore('users');
28        const request = store.add(user);
29
30        request.onsuccess = () => resolve();
31        request.onerror = () => reject(request.error);
32    });
33}
34
35async function main() {
36    try {
37        const db = await openDatabase();
38        await addUserAsync(db, { id: 1, name: 'Alice' });
39        console.log('User added successfully');
40    } catch (error) {
41        console.error('Error:', error);
42    }
43}
  • Door functies zoals openDatabase of addUserAsync met een Promise te omhullen, kun je asynchrone processen op een intuïtieve manier afhandelen met async/await.
  • Dit voorkomt callback-hell en maakt de code beter leesbaar.

Samenvatting

IndexedDB is een zeer krachtige functie als je geavanceerd databeheer aan de browserzijde wilt uitvoeren. In het begin kun je verward raken door op gebeurtenissen gebaseerde asynchrone verwerking, maar zodra je de structuur begrijpt, kun je grootschalige dataoperaties aan de clientzijde uitvoeren.

In het bijzonder zal het onthouden van de volgende punten helpen om het soepeler te gebruiken:.

  • Voer de initiële setup uit met onupgradeneeded.
  • Let op de lees/schrijfmodus van transacties.
  • Indexen maken efficiënte zoekopdrachten mogelijk.
  • Gegevens kunnen als objecten worden opgeslagen, waardoor het zeer compatibel is met JSON.

Door IndexedDB te beheersen, wordt het beheren van data voor PWA's en offline apps veel eenvoudiger.

Je kunt het bovenstaande artikel volgen met Visual Studio Code op ons YouTube-kanaal. Bekijk ook het YouTube-kanaal.

YouTube Video