JavaScript e IndexedDB
In questo articolo, spiegheremo JavaScript e IndexedDB.
Questo tutorial offre una spiegazione passo per passo di JavaScript e IndexedDB
, includendo codice di esempio pratico ad ogni fase per approfondire la vostra comprensione.
YouTube Video
javascript-indexed-db.html
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>JavaScript & 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 e IndexedDB
IndexedDB
è un database key-value asincrono integrato nei browser. Offre funzionalità simili ai database relazionali e permette di memorizzare e ricercare grandi quantità di dati strutturati lato client. È particolarmente utile per applicazioni utilizzabili offline e per le PWA (Progressive Web Apps).
Caratteristiche di IndexedDB
- Funziona in modo asincrono e basato sugli eventi.
- Gli oggetti JavaScript possono essere memorizzati negli object store.
- È possibile effettuare ricerche tramite query o indici.
- Dispone di una grande capacità di archiviazione (centinaia di MB o più), consentendo di memorizzare molti più dati rispetto ai cookie o al localStorage.
Apertura e creazione di un database
Per utilizzare IndexedDB
, è necessario prima aprire un database. Se non esiste, viene creato automaticamente.
1const request = indexedDB.open('MyDatabase', 1); // Specify DB name and version
- Il metodo
open
apre il database in modo asincrono e genera i seguenti tre eventi.
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};
- L'evento
onsuccess
viene attivato quando il database si apre correttamente. Le operazioni successive devono essere eseguite utilizzandorequest.result
, che diventa disponibile a questo punto.
onerror
1// Fired when database fails to open
2request.onerror = (event) => {
3 console.error('Failed to open database:', event.target.error);
4};
- L'evento
onerror
viene attivato quando l'apertura del database fallisce. La registrazione degli errori e la gestione degli errori dovrebbero essere eseguite qui.
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
viene attivato quando il database viene creato per la prima volta o quando la versione specificata è superiore a quella attuale. La creazione delle tabelle (object store) e la definizione dello schema devono essere effettuate in questo momento.
Creare un Object Store
Per prima cosa, creare un 'object store' (equivalente a una tabella) all'interno di 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};
Qui vengono applicate le seguenti impostazioni:.
-
createObjectStore
createObjectStore
è un metodo per creare un nuovo object store all'interno del database. Puoi memorizzare record in un object store e definire come i dati vengono gestiti specificandokeyPath
e altre opzioni. Questo processo deve essere eseguito all'interno dell'eventoonupgradeneeded
. -
keyPath: 'id'
ImpostakeyPath
sulla proprietàid
, che identifica in modo univoco ogni record come chiave primaria. Questo consente di usareid
automaticamente per aggiungere, cercare o aggiornare i dati. -
createIndex
Utilizza il metodocreateIndex
per creare un indice sulla proprietàname
per le ricerche. Impostandounique: false
, sono consentiti più record con lo stesso valore pername
.
Gestione delle connessioni al database avvenute con successo
Assegna i processi da eseguire al momento di una connessione riuscita del database all'evento onsuccess
. All'interno di questo processo si ottiene l'istanza del database e ci si prepara per la lettura e la scrittura dei dati.
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};
-
Questo processo viene eseguito quando la connessione al database
IndexedDB
viene completata con successo. -
event.target.result
contiene l'istanza del database aperta (oggettoIDBDatabase
), che viene utilizzato per avviare le transazioni e accedere agli object store. -
Le effettive operazioni di lettura e scrittura, come l'aggiunta, la ricerca, l'aggiornamento e la cancellazione dei dati, vengono effettuate utilizzando l'oggetto
db
.
A questo punto, il database è pronto e puoi iniziare le transazioni in modo sicuro.
Aggiunta di Dati
Ecco come aggiungere nuovi dati a 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};
- Crea una transazione con
db.transaction()
specificando l'object store su cui operare e la modalità (in questo caso,readwrite
). - Aggiungi nuovi dati utilizzando il metodo
store.add()
. - Le transazioni vengono confermate automaticamente, ma se vuoi raggruppare più operazioni puoi gestirle tramite gli eventi di fine transazione.
Recupero dei Dati (Ricerca tramite Chiave Primaria)
Ecco come recuperare dati specifici utilizzando la chiave primaria.
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};
- Crea una transazione di sola lettura con
db.transaction()
. - Utilizza
store.get(id)
per recuperare i dati corrispondenti alla chiave primaria specificata. onsuccess
viene chiamato quando il recupero ha avuto successo e il risultato viene mostrato se disponibile. Se non c'è nessun risultato, viene trattato come 'nessun dato corrispondente'.
Utilizzo della Ricerca tramite Indice
Se vuoi cercare tramite proprietà diverse dalla chiave primaria, utilizza l'indice creato in precedenza.
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};
- Accedi all'indice
name
creato in precedenza utilizzandostore.index('name')
. index.get(value)
recupera il primo record col valore corrispondente. Se ci sono più record con lo stesso valore, puoi recuperarli tutti conindex.getAll(value)
.
Aggiornamento dei Dati
Per aggiornare dati esistenti (sovrascrittura), usa il metodo put()
.
Se esiste un record con la chiave primaria corrispondente, viene aggiornato; altrimenti viene aggiunto un nuovo record.
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()
è un metodo conveniente che supporta sia l'aggiornamento che l'aggiunta.- Se esistono già dati con la stessa chiave primaria, verranno sovrascritti.
- Se vuoi verificare l'esistenza prima di aggiornare, puoi usare
get()
per controllare in anticipo.
Eliminazione dei Dati
Per eliminare dati corrispondenti a una determinata chiave primaria, utilizza il metodo delete()
.
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};
- Usa
store.delete(id)
per eliminare dati con la chiave primaria corrispondente. - Nota che anche se i dati non esistono, non si verificherà alcun errore e l'operazione verrà considerata come riuscita.
- Implementare la gestione degli errori renderà il codice più robusto.
Recupero di Tutti i Dati
getAll()
Per recuperare tutti i record nell'object store, usa il metodo getAll()
.
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()
recupera tutti i record nell'object store specificato come array.- Anche quando si recuperano grandi quantità di dati in una volta sola, vengono processati in modo efficiente.
- I risultati vengono memorizzati come array in
request.result
. - Aggiungi sempre la gestione degli errori in modo da poter gestire eventuali fallimenti.
openCursor()
openCursor()
è un metodo per scorrere sequenzialmente i record in un object store o indice. È utile quando vuoi processare i dati uno alla volta anziché recuperarli tutti insieme.
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};
- Utilizzando
openCursor()
, avvii un cursore e recuperi tutti i record dell'object store uno per uno. - Utilizza
cursor.value
per ottenere l'oggetto relativo al record corrente. - Sposta il cursore al record successivo con
cursor.continue()
. - Quando
cursor === null
, tutti i record sono stati scorsi.
Esempio di un processo di aggiornamento utilizzando openCursor()
Ad esempio, il processo per cambiare il nome di un utente con name
pari a Alice
in Alicia
sarebbe il seguente:.
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)
UtilizzandoIDBKeyRange.only
, puoi selezionare solo i record la cui chiave corrisponde esattamente aoldName
. Questo è utile quando si desidera accedere direttamente a un valore specifico. -
cursor.update()
Dopo aver aggiornatocursor.value
, chiamandoupdate()
si sovrascriverà il record corrispondente. -
Gestione di più corrispondenze Chiamando
cursor.continue()
, puoi spostare il cursore al prossimo record corrispondente. Questo permette di elaborare in sequenza più record che corrispondono alla stessa chiave o condizione. -
Gestione degli errori Visualizzando i log in
onerror
quando un processo fallisce, diventa più facile individuare le cause e risolvere i problemi durante il funzionamento.
Esempio di un processo di eliminazione utilizzando openCursor()
Ad esempio, il processo per eliminare tutti gli utenti con name
pari a Bob
sarebbe il seguente:.
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()
Concursor.delete()
, il record alla posizione attuale del cursore viene eliminato. Poiché il risultato viene restituito in modo asincrono, puoi verificare il processo inonsuccess
.
Note su transazioni e elaborazione asincrona
IndexedDB
è asincrono e basato su eventi. Tutte le operazioni devono essere gestite tramite gli eventi onsuccess
o onerror
. Quando vuoi raggruppare più processi insieme, è comodo racchiuderli in una 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}
- Racchiudere funzioni come
openDatabase
oaddUserAsync
in unaPromise
consente di gestire i processi asincroni in modo intuitivo conasync/await
. - Questo evita il cosiddetto callback hell e rende il codice più leggibile.
Riepilogo
IndexedDB
è una funzionalità molto potente quando si desidera eseguire una gestione avanzata dei dati lato browser. All'inizio, l'elaborazione asincrona basata su eventi può creare confusione, ma una volta compresa la struttura, puoi eseguire operazioni sui dati su larga scala lato client.
In particolare, tenere a mente i seguenti punti ti aiuterà a utilizzarlo in modo più fluido:.
- Effettua la configurazione iniziale con
onupgradeneeded
. - Fai attenzione alla modalità di lettura/scrittura delle transazioni.
- Gli indici consentono una ricerca efficiente.
- I dati possono essere memorizzati come oggetti, rendendoli altamente compatibili con JSON.
Padroneggiando IndexedDB
, la gestione dei dati per le PWA e le applicazioni offline diventa molto più semplice.
Puoi seguire l'articolo sopra utilizzando Visual Studio Code sul nostro canale YouTube. Controlla anche il nostro canale YouTube.