JavaScript og IndexedDB

JavaScript og IndexedDB

I denne artikkelen skal vi forklare JavaScript og IndexedDB.

Denne veiledningen gir en trinnvis forklaring på JavaScript og IndexedDB, inkludert praktiske eksempelkoder på hvert steg for å hjelpe deg å utdype forståelsen din.

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 og IndexedDB

IndexedDB er en asynkron nøkkel-verdidatabase som er innebygd i nettlesere. Den tilbyr funksjoner som ligner relasjonsdatabaser og lar deg lagre og søke i store mengder strukturert data på klientsiden. Den er spesielt nyttig for applikasjoner med offline-funksjonalitet og PWA-er (Progressive Web Apps).

Funksjoner til IndexedDB

  • Fungerer på en asynkron og hendelsesdrevet måte.
  • JavaScript-objekter kan lagres i objektlagre.
  • Søk via spørringer eller indekser er mulig.
  • Den har stor lagringskapasitet (hundrevis av MB eller mer), slik at du kan lagre mye mer data enn med cookies eller localStorage.

Åpne og opprette en database

For å bruke IndexedDB må du først åpne en database. Hvis den ikke eksisterer, blir den opprettet automatisk.

1const request = indexedDB.open('MyDatabase', 1); // Specify DB name and version
  • open-metoden åpner databasen asynkront og utløser de følgende tre hendelsene.

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};
  • onsuccess-hendelsen utløses når databasen åpnes vellykket. Påfølgende operasjoner bør utføres ved å bruke request.result, som blir tilgjengelig på dette punktet.

onerror

1// Fired when database fails to open
2request.onerror = (event) => {
3    console.error('Failed to open database:', event.target.error);
4};
  • onerror-hendelsen utløses når databasen ikke kan åpnes. Feillogging og feilbehandling bør utføres her.

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 utløses når databasen opprettes på nytt eller når den spesifiserte versjonen er høyere enn den nåværende. Opprettelse av tabeller (objektlagre) og definering av skjemaet bør gjøres på dette tidspunktet.

Opprette et objektlager

Først oppretter du et 'objektlager' (tilsvarer en tabell) inne i 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};

Her brukes følgende innstillinger:.

  • createObjectStore createObjectStore er en metode for å opprette et nytt objektlager i databasen. Du kan lagre poster i et objektlager og definere hvordan data håndteres ved å angi keyPath og andre alternativer. Denne prosessen må gjøres inne i onupgradeneeded-hendelsen.

  • keyPath: 'id' Angi keyPath til id-egenskapen, som entydig identifiserer hver post som en primærnøkkel. Dette gjør at id kan brukes automatisk når du legger til, søker eller oppdaterer data.

  • createIndex Bruk createIndex-metoden for å opprette en indeks basert på name-egenskapen for søk. Ved å sette unique: false tillates flere poster med samme name.

Håndtere vellykkede databasetilkoblinger

Tildel prosesser som skal utføres ved en vellykket databasetilkobling til onsuccess-hendelsen. I denne prosessen får du database-instansen og forbereder lesing og skriving av data.

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};
  • Denne prosessen kjøres når IndexedDB-databasetilkoblingen er fullført.

  • event.target.result inneholder den åpnede databaseinstansen (IDBDatabase-objektet), som brukes for å starte transaksjoner og få tilgang til objektlagre.

  • Faktiske lese- og skriveoperasjoner som å legge til, hente, oppdatere og slette data gjøres med db-objektet.

På dette punktet er databasen klar, så du kan trygt starte transaksjoner.

Legge til data

Slik legger du til nye data i 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};
  • Lag en transaksjon med db.transaction() og angi hvilket objektlager du skal operere på, samt modus (i dette tilfellet, readwrite).
  • Legg til nye data ved å bruke store.add()-metoden.
  • Transaksjoner forpliktes automatisk, men hvis du vil gruppere flere operasjoner, kan du håndtere det med transaksjonens slutt-hendelser.

Hente data (primærnøkkelsøk)

Slik henter du spesifikke data ved hjelp av primærnøkkelen.

 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};
  • Opprett en skrivebeskyttet transaksjon med db.transaction().
  • Bruk store.get(id) for å hente data som samsvarer med den angitte primærnøkkelen.
  • onsuccess kalles når henting lykkes, og resultatet vises hvis det finnes. Hvis det ikke finnes noe resultat, behandles det som 'ingen tilsvarende data.'.

Bruke indekssøk

Hvis du vil søke etter egenskaper andre enn primærnøkkelen, bruk indeksen du opprettet tidligere.

 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};
  • Få tilgang til den forhåndsopprettede name-indeksen ved å bruke store.index('name').
  • index.get(value) henter den første posten med tilsvarende verdi. Hvis flere poster har samme verdi, kan du hente alle med index.getAll(value).

Oppdatere data

Bruk put()-metoden for å oppdatere eksisterende data (overskrive).

Hvis en post med tilsvarende primærnøkkel finnes, blir den oppdatert; hvis ikke, legges en ny post til.

 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() er en praktisk metode som støtter både oppdatering og innsetting.
  • Hvis data med samme primærnøkkel allerede finnes, blir det overskrevet.
  • Hvis du vil kontrollere om data finnes før du oppdaterer, kan du bruke get() for å sjekke på forhånd.

Slette data

For å slette data som samsvarer med en angitt primærnøkkel, bruk delete()-metoden.

 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};
  • Bruk store.delete(id) for å slette data med tilsvarende primærnøkkel.
  • Merk at selv om dataene ikke finnes, vil det ikke oppstå noen feil, og operasjonen regnes som vellykket.
  • Implementering av feilbehandling vil gi mer robust kode.

Hente alle data

getAll()

For å hente alle poster i objektlageret, bruk getAll()-metoden.

 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() henter alle poster i det angitte objektlageret som en matrise (array).
  • Selv når store datamengder hentes på én gang, håndteres det effektivt.
  • Resultatene lagres som en matrise i request.result.
  • Legg alltid til feilbehandling slik at du kan håndtere feil.

openCursor()

openCursor() er en metode for sekvensielt å gå gjennom poster i et objektlager eller en indeks. Det er nyttig når du vil behandle data én post om gangen i stedet for å hente ut store mengder på en gang.

 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};
  • Ved å bruke openCursor() starter du en markør/cursor og henter alle poster i objektlageret én etter én.
  • Bruk cursor.value for å hente dataobjektet til den gjeldende posten.
  • Flytt markøren til neste post med cursor.continue().
  • Når cursor === null, er alle poster blitt gjennomgått.

Eksempel på en oppdateringsprosess ved bruk av openCursor()

For eksempel, prosessen med å endre navnet til en bruker med name lik Alice til Alicia vil se slik ut:.

 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) Ved å bruke IDBKeyRange.only, kan du kun målrette de postene hvor nøkkelen nøyaktig samsvarer med oldName. Dette er nyttig når du ønsker å få direkte tilgang til en bestemt verdi.

  • cursor.update() Etter å ha oppdatert cursor.value, vil et kall til update() overskrive den tilsvarende posten.

  • Håndtering av flere treff Ved å kalle cursor.continue(), kan du flytte markøren til neste samsvarende post. Dette gjør det mulig å behandle flere poster som samsvarer med samme nøkkel eller betingelse i rekkefølge.

  • Feilhåndtering Ved å loggføre i onerror når en prosess feiler, blir det enklere å undersøke årsaker og feilsøke under drift.

Eksempel på en sletteprosess ved bruk av openCursor()

For eksempel, prosessen med å slette alle brukere med name lik Bob vil se slik ut:.

 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() Med cursor.delete() slettes posten på den nåværende markørposisjonen. Siden resultatet returneres asynkront, kan du sjekke prosessen i onsuccess.

Notater om transaksjoner og asynkron behandling

IndexedDB er asynkron og hendelsesdrevet. Alle operasjoner må håndteres med onsuccess- eller onerror-hendelser. Når du ønsker å gruppere flere prosesser sammen, er det praktisk å kapsle dem i et Promise.

 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}
  • Ved å pakke inn funksjoner som openDatabase eller addUserAsync med Promise, blir det mulig å håndtere asynkrone prosesser intuitivt med async/await.
  • Dette unngår callback-helvete og gjør koden mer lesbar.

Sammendrag

IndexedDB er en svært kraftig funksjon når du ønsker å utføre avansert databehandling på klientsiden. I begynnelsen kan du bli forvirret av hendelsesbasert asynkron behandling, men når du først forstår strukturen, kan du utføre komplette dataoperasjoner på klientsiden.

Spesielt vil det å huske på følgende punkter hjelpe deg å bruke det mer smidig:.

  • Utfør innledende oppsett med onupgradeneeded.
  • Vær oppmerksom på lese/skrive-modus for transaksjoner.
  • Indekser gjør effektivt søk mulig.
  • Data kan lagres som objekter, noe som gjør det svært kompatibelt med JSON.

Ved å mestre IndexedDB blir databehandling for PWA-er og offline-apper mye enklere.

Du kan følge med på artikkelen ovenfor ved å bruke Visual Studio Code på vår YouTube-kanal. Vennligst sjekk ut YouTube-kanalen.

YouTube Video