JavaScript et IndexedDB
Dans cet article, nous allons expliquer JavaScript et IndexedDB.
Ce tutoriel propose une explication pas à pas de JavaScript et IndexedDB
, avec du code d’exemple pratique à chaque étape afin d’approfondir votre compréhension.
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 et IndexedDB
IndexedDB
est une base de données clé-valeur asynchrone intégrée aux navigateurs. Elle offre des fonctionnalités similaires à celles des bases de données relationnelles et vous permet de stocker et rechercher de grandes quantités de données structurées côté client. Elle est particulièrement utile pour les applications compatibles hors ligne et les PWAs (Progressive Web Apps).
Caractéristiques de IndexedDB
- Fonctionne de manière asynchrone et pilotée par les événements.
- Les objets JavaScript peuvent être stockés dans des object stores.
- La recherche par requêtes ou par index est possible.
- Elle dispose d'une grande capacité de stockage (plusieurs centaines de Mo ou plus), permettant de stocker beaucoup plus de données que les cookies ou localStorage.
Ouverture et création d'une base de données
Pour utiliser IndexedDB
, il faut d'abord ouvrir une base de données. Si elle n'existe pas, elle est créée automatiquement.
1const request = indexedDB.open('MyDatabase', 1); // Specify DB name and version
- La méthode
open
ouvre la base de données de façon asynchrone et déclenche les trois événements suivants.
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’événement
onsuccess
se déclenche lorsque la base de données s’ouvre avec succès. Les opérations suivantes doivent être exécutées en utilisantrequest.result
, qui devient disponible à ce moment-là.
onerror
1// Fired when database fails to open
2request.onerror = (event) => {
3 console.error('Failed to open database:', event.target.error);
4};
- L’événement
onerror
se déclenche lorsque l’ouverture de la base de données échoue. C'est ici que doivent être effectués la gestion et le journalisation des erreurs.
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
est déclenché lors de la création de la base de données ou si la version spécifiée est supérieure à la version actuelle. La création des tables (object stores) et la définition du schéma doivent être effectuées à ce moment-là.
Création d’un object store
Commencez par créer un 'object store' (équivalent d’une table) dans 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};
Ici, les paramètres suivants sont définis :.
-
createObjectStore
createObjectStore
est une méthode permettant de créer un nouvel object store dans la base de données. Vous pouvez stocker des enregistrements dans un object store et définir la gestion des données viakeyPath
et d’autres options. Ce processus doit être réalisé dans l’événementonupgradeneeded
. -
keyPath: 'id'
RéglezkeyPath
sur la propriétéid
, qui identifie chaque enregistrement de façon unique comme clé primaire. Cela permet d’utiliser automatiquementid
lors de l’ajout, de la recherche ou de la mise à jour des données. -
createIndex
Utilisez la méthodecreateIndex
pour créer un index basé sur la propriéténame
afin d’effectuer des recherches. En définissantunique: false
, plusieurs enregistrements avec le mêmename
sont autorisés.
Gestion des connexions réussies à la base de données
Attribuez les traitements à exécuter en cas de connexion réussie à l'événement onsuccess
. Dans ce processus, vous obtenez l’instance de la base de données et préparez la lecture et l’écriture des données.
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};
-
Ce processus s’exécute lorsque la connexion à la base de données
IndexedDB
aboutit. -
event.target.result
contient l’instance ouverte de la base de données (objetIDBDatabase
), utilisée pour démarrer des transactions et accéder aux object stores. -
Les opérations réelles de lecture et d’écriture (ajout, récupération, modification et suppression de données) sont effectuées à l’aide de l’objet
db
.
À ce stade, la base de données est prête, vous pouvez donc lancer des transactions en toute sécurité.
Ajout de données
Voici comment ajouter de nouvelles données à 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};
- Créez une transaction avec
db.transaction()
en spécifiant l’object store concerné et le mode (ici,readwrite
). - Ajoutez les nouvelles données avec la méthode
store.add()
. - Les transactions sont validées automatiquement, mais pour regrouper plusieurs opérations vous pouvez les gérer via les événements de fin de transaction.
Récupération de données (recherche par clé primaire)
Voici comment récupérer des données spécifiques avec la clé primaire.
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};
- Créez une transaction en lecture seule avec
db.transaction()
. - Utilisez
store.get(id)
pour récupérer les données correspondant à la clé primaire spécifiée. onsuccess
est appelé lorsque la récupération est réussie, et le résultat est affiché s’il existe. S’il n’y a pas de résultat, cela signifie qu’il n’existe pas de donnée correspondante.
Recherche via un index
Si vous souhaitez rechercher par d’autres propriétés que la clé primaire, utilisez l’index créé au préalable.
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};
- Accédez à l’index
name
créé précédemment avecstore.index('name')
. index.get(value)
permet de récupérer le premier enregistrement correspondant à la valeur. Si plusieurs enregistrements ont la même valeur, vous pouvez tous les récupérer avecindex.getAll(value)
.
Mise à jour de données
Pour mettre à jour des données existantes (écrasement), utilisez la méthode put()
.
Si un enregistrement avec la même clé primaire existe, il est mis à jour ; sinon, un nouvel enregistrement est ajouté.
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()
est une méthode pratique qui prend en charge à la fois l’ajout et la mise à jour.- Si des données ayant la même clé primaire existent déjà, elles seront écrasées.
- Si vous souhaitez vérifier l’existence avant de mettre à jour, vous pouvez utiliser
get()
pour confirmer au préalable.
Suppression de données
Pour supprimer des données correspondant à une clé primaire donnée, utilisez la méthode 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};
- Utilisez
store.delete(id)
pour supprimer des données avec la clé primaire correspondante. - Notez que même si la donnée n’existe pas, aucune erreur ne sera générée et l’opération sera considérée comme réussie.
- La gestion des erreurs rendra votre code plus robuste.
Récupération de toutes les données
getAll()
Pour récupérer tous les enregistrements de l’object store, utilisez la méthode 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()
récupère tous les enregistrements de l’object store spécifié sous forme de tableau.- Même lors de la récupération d’un grand volume de données d’un coup, le traitement reste efficace.
- Les résultats sont stockés sous forme de tableau dans
request.result
. - Ajoutez toujours une gestion des erreurs afin de pouvoir traiter les échecs.
openCursor()
openCursor()
est une méthode permettant de parcourir séquentiellement les enregistrements d’un object store ou d’un index. Elle est utile si vous souhaitez traiter les données une à une plutôt que d’en récupérer une grande quantité d’un coup.
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};
- En utilisant
openCursor()
, vous démarrez un curseur et récupérez les enregistrements de l’object store un par un. - Utilisez
cursor.value
pour obtenir l’objet de données de l’enregistrement courant. - Passez à l’enregistrement suivant en utilisant
cursor.continue()
. - Lorsque
cursor === null
, tous les enregistrements ont été parcourus.
Exemple d'un processus de mise à jour utilisant openCursor()
Par exemple, le processus de modification du nom d'un utilisateur dont le name
est Alice
en Alicia
serait le suivant :.
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)
En utilisantIDBKeyRange.only
, vous pouvez cibler uniquement les enregistrements dont la clé correspond exactement àoldName
. Ceci est utile lorsque vous souhaitez accéder directement à une valeur spécifique. -
cursor.update()
Après la mise à jour decursor.value
, l'appel àupdate()
remplacera l'enregistrement correspondant. -
Gestion de plusieurs correspondances En appelant
cursor.continue()
, vous pouvez déplacer le curseur vers le prochain enregistrement correspondant. Cela vous permet de traiter plusieurs enregistrements correspondant à la même clé ou condition, de manière séquentielle. -
Gestion des erreurs En affichant des journaux dans
onerror
lorsqu'un processus échoue, il devient plus facile d'enquêter sur les causes et de résoudre les problèmes pendant l'opération.
Exemple d'un processus de suppression utilisant openCursor()
Par exemple, le processus de suppression de tous les utilisateurs dont le name
est Bob
ressemblerait à ceci :.
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()
Aveccursor.delete()
, l'enregistrement à la position actuelle du curseur est supprimé. Comme le résultat est retourné de façon asynchrone, vous pouvez vérifier le processus dansonsuccess
.
Remarques sur les transactions et le traitement asynchrone
IndexedDB
est asynchrone et basé sur les événements. Toutes les opérations doivent être gérées avec les événements onsuccess
ou onerror
. Lorsque vous souhaitez regrouper plusieurs processus, il est pratique de les encapsuler dans une 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}
- Encapsuler des fonctions comme
openDatabase
ouaddUserAsync
dans unePromise
permet de gérer les processus asynchrones de façon intuitive avecasync/await
. - Cela permet d'éviter l'enfer des callbacks et rend le code plus lisible.
Résumé
IndexedDB
est une fonctionnalité très puissante lorsque vous souhaitez effectuer une gestion avancée des données côté navigateur. Au début, le traitement asynchrone basé sur les événements peut prêter à confusion, mais une fois que vous avez compris la structure, vous pouvez effectuer des opérations de données à grande échelle côté client.
En particulier, garder à l'esprit les points suivants vous aidera à l'utiliser plus facilement :.
- Effectuez la configuration initiale avec
onupgradeneeded
. - Faites attention au mode lecture/écriture des transactions.
- Les index permettent une recherche efficace.
- Les données peuvent être stockées sous forme d'objets, ce qui les rend hautement compatibles avec JSON.
En maîtrisant IndexedDB
, la gestion des données pour les PWA et les applications hors-ligne devient beaucoup plus facile.
Vous pouvez suivre l'article ci-dessus avec Visual Studio Code sur notre chaîne YouTube. Veuillez également consulter la chaîne YouTube.