Keine SQL-Datenbank, aber objektorientiert
Indexed DB ist objektorientiert, ist aber keine relationale Datenbank, hat keine SQL-Unterstützung und nutzt innerhalb seiner Objekte Schlüssel-/Wertpaare. Neben den einfachen Datenbank-Operationen search, get und put unterstützt IndexedDB auch Transaktionen.
Ohne Hilfsprogramm lässt sich die IndexDB-Datenbank nicht mit einer serverbasierten Datenbank synchronisieren, ist also ganz auf die Existenz im Browser angewiesen.
Der Speicher der IndexedDB liegt zwischen 50MB bis zum GB-Bereich (je nach Gerät und Speichervolumen), kann nur vom Benutzer oder der App selbst gelöscht werden, aber im Privaten Modus gibt es keinen Zugriff auf die Datenbank.
indexedDB-Datenbanken sind leistungsfähiger als localStorage und ermöglicht Anwendungen, die sowohl online als auch offline laufen, ist also bestens gerüstet für Anwendungen, die auch ohne Internet-Verbindung funktionieren (PWA, Spiele, technische Anwendungen).
Datenbank-Operationen
Wenn die Datenbank geöffnet wird, können Daten direkt eingegeben werden, meist wird aber erst mal ein ObjectStore erzeugt und die Daten dort eingegeben. Jede Datenbank kann mehrere ObjectStores haben – vergleichbar mit Tabellen in relationalen Datenbanken. Ein ObjectStore könnte z.B. Benutzerdaten mit einer eindeutigen Emailadresse der Index, mit der die Informationen zum Benutzer erreicht werden.
Die Einträge in einem ObjectStore sind allesamt von derselben Struktur, obwohl einige Einträge extra Eigenschaften aufweisen. Die Kerneigenschaften sind aber immer dieselben.
Ablauf
Das einfache Beispiel einer indizierten Datenbank auf dieser Seite sammelt Farbkombinationen aus einer Primärfarbe und einer Akzentfarbe. Jede Farbkombi bekommt einen Namen – Combo-Name (optional) – und zwei Farben (Vorgabe Schwarz). Bei einem Refresh der Seite oder wenn die Seite geschlossen und später irgendwann wieder geöffnet wird, sind die Farbkombinationen wieder verfügbar.
- open: Datenbank öffnen
- createObjectStore: Datenbank anlegen
- request: Anfrage an die Datenbank in einer Transaktion
- request.onsuccess: Abhorchen der Antwort mit einem DOM-Event
- Ergebnis des Request-Objekts behandeln
const request = window.indexedDB.open('colors', 1); request.onerror = function() { //console.log('Datenbank konnte nicht geöffnet werden.'); } request.onsuccess = function() { //console.log('Datenbankzugriff erfolgt'); db = request.result; displayData(); } request.onupgradeneeded = function(e) { const db = e.target.result; const objectStore = db.createObjectStore('colors', { keyPath: 'id', autoIncrement: true}); objectStore.createIndex('colorCombo', 'colorCombo', { unique: false}); objectStore.createIndex('favoriteColor', 'favoriteColor', { unique: false}); objectStore.createIndex('secondColor', 'secondColor', { unique: false}); //console.log('Datenbank eingerichtet'); }
Das indexedDb-API ist ein wenig antiquiert. Viele Beispiele und Tutorials nutzen darum einen Promises Wrapper.
Primary Keys – Primärschlüssel
Wie in jedem Datenbanksystem kann der objectStore einen Schlüssel haben. Der Primary Key identifiziert Daten und kann entweder mittels eines Key Path oder durch einen Schlüsselgenerator erzeugt werden. Der Schlüsselgenerator (Beispiel 2 und 3) erzeugt einen eindeutigen Wert für jedes neu angelegte Objekt.
1 upgradeDb.createObjectStore ("studies", {keyPath: "email"}) 2 upgradeDb.createObjectStore ("notes", {autoIncrement: true}) 3 upgradeDb.createObjectStore ("logs", {keyPath: "id", autoIncrement: true})
Transaktionen
Eine Transaktion fasst Operationen zusammen, um die Integrität der Datenbank abzusichern. Wenn eine Aktion innerhalb einer Transaktion scheitert, wird keine der Aktionen der Transaktion ausgeführt und die Datenbank wieder in den Zustand vor der Transaktion zurück versetzt. Alle Lese- und Schreiboperationen müssen Teil einer Transaktion sein.
objectStore.add(data, optionalKey);
Während sessionStorage und webStorage nur einfache Schlüssel-/Wertpaare aufnehmen, können die Daten einer indexedDB von beliebigem Typ sein: String, Number, Object, Array, JSON und Blobs.
transaction.complete prüft, ob die Operation durchgeführt wurde.
addName.onclick = addData; function addData (e) { const newItem = { colorCombo: colorComboInput.value, favoriteColor: favoriteColorInput.value, secondColor: secondColorInput.value }; const transaction = db.transaction(['colors'], 'readwrite'); const objectStore = transaction.objectStore('colors'); const request = objectStore.add(newItem); /** Eingabefelder leerräumen **/ request.onsuccess = () => { colorComboInput.value = ''; favoriteColorInput.value = ''; secondColorInput.value = ''; }; transaction.oncomplete = () => { //console.log('Datenbank-Transaktion durchgeführt'); displayData(); } transaction.onerror = () => { //console.log('Datenbank-Transaktion nicht durchgeführt, Fehler!'); } }
openCursor()
Einzelne Einträge, deren key bekannt ist, können mit get() gelesen werden. Ein Cursor hingegen läuft alle Werte des ObjectStore.
objectStore.openCursor().onsuccess = function (e) {
const cursor = e.target.result;
if (cursor) {
…
colorName.textContent = cursor.value.colorCombo;
cursor.continue();
} else {
…
}
console.log ("Farben eingespielt");
}
Requisiten: Live-Vorschau und Entwickler-Tools
Für die Entwicklung empfehle ich BBEDIT oder Visual Studio Code als Programm-Editor, denn VS Code zeigt eine Live-Vorschau ohne Speichern mit dem Plugin "Live Server". BBEdit hat eine Live-Vorschau ohne Installation eines Plugins (kostet allerdings rund 50€).
Außerdem unersetzlich: die Browser-Console der Entwickler-Tools.