Die Web-Komponenen entfalten ihre ganze Stärke im Zusammenspiel mit einem Service-Worker, einem Web-Worker und der IndexedDB. Dadurch reduzieren wir die Server-Anfragen drastisch. Wenn ein Item einmal geladen wurde, ist es lokal verfügbar, egal wo und in welchem Kontext es erneut angezeigt wird.
Schauen wir uns das im Detail an.
Beispiel
Die folgende Liste zeigt die Verwendung von IndexedDB in einem Web-Worker. Die Daten werden in der IndexedDB gespeichert und von dort abgerufen.
Eine IndexedDB ist eine Datenbank, die im Browser gespeichert wird. Sie ist eine NoSQL-Datenbank, die auf Objekte basiert. Die Datenbank ist in Transaktionen organisiert. Die Datenbank ist asynchron und wird im Hintergrund ausgeführt. Die Datenbank ist in der Regel schneller als eine SQL-Datenbank. Das Lesen und Schreiben von Daten in die Datenbank erfolgt über einen Web-Worker. Ein Web-Worker ist ein eigenständiger Thread, der im Hintergrund ausgeführt wird. Der Web-Worker kommuniziert ebenfalls mit dem Server, falls die gewünschten Daten nicht in der IndexedDB enthalten sind.
Voraussetzungen
Das Konzept der Web-Komponenten sollte dir bekannt sein. Ebenso solltest du wissen, wie ein Service-Worker funktioniert. Letzter verwenden wir jedoch nur zur Synchonisation der Klick-Events zwischen mehreren Browser-Tabs/-Fenster.
Zur Kommunikation mit dem Web-Worker verwenden wir die Klasse WebWorker. Diese Klasse stellt Methoden zur Verfügung, um den Web-Worker zu starten und Daten an den Web-Worker zu senden respektive vom Web-Worker zu empfangen. Zum Laden der Liste verwenden wir die Klasse Items. Beide Klassen laden wir über die default.js, die im HTML eingebunden ist. Ich sammle die Klassen im Unterordner modules und nenne die Dateien wie die Klasse, aber mit Kleinbuchstaben beginnend.
Javascript: default.js// Load Module
async function loadModule(module) {
const file = './modules/' + module.charAt(0).toLowerCase() + module.slice(1) + '.js';
const mod = await import(file);
return new mod[module];
}
// Register a Web Worker
async function registerWorker() {
const WebWorker = await loadModule('WebWorker');
await WebWorker.init();
return true;
}
// Load Items
async function loadItems(element) {
if (document.querySelector(element)) {
await loadModule('Items');
}
}
// Do on DOM Ready
document.addEventListener('DOMContentLoaded', () => {
registerWorker().then(() => { loadItems('item-list'); });
});
Die Methode registerWorker() lädt den Web-Worker und die Methode loadItems() lädt die Items. Beide Methoden werden aufgerufen sobald der DOM geladen ist. Die Klasse WebWorker bindet das Klassenmodul webWorkerModule.js ein, welches den eigentlichen Worker aktiviert.
Die item-list ist ein benutzerdefiniertes Element. Die data-Parameter definieren das zu verwendende Template (itemCover) und die Daten (item1, item2 und item3). Diese Informationen werden vom Javascript gelesen und verarbeitet.
Gehen wir Schritt für Schritt vor. Die items.js liest die data-Paramenter aus und gibt die Daten an die webWorker.js weiter. Diese startet über die webWorkerModule.js den Worker (worker.js) und übergibt ihm dann die Anfrage, also die Werte aus den data-Paramentern. Der Worker holt die dazu passenden Daten aus der IndexedDB und gibt diese via webWorker.js an die items.js zurück. Diese generiert in einem Shadow-DOM schliesslich das HTML.
PHP
Doch wie kommen die Daten initial in die IndexedDB? Dazu benötigen wir eine Serverabfrage. Diese wird im Worker durchgeführt.
Auf dem Server wird ein PHP-Skript benötigt, welches die Daten aus der Datenbank - in unserem Fall sind dies JSON-Dateien - holt und zurückgibt. Die worker.js erwartet das PHP-Script unter /includes/data.php.
PHP: data.php<?php
// allow CORS
if(isset($_SERVER['HTTP_ORIGIN'])) {
header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Max-Age: 86400');
}
// set content type
header('Content-Type: application/json; charset=utf-8');
// verify the GET parameters
$data = isset($_GET['data']) ? $_GET['data'] : [];
if (!is_array($data)) {
$data = json_decode($data, true);
}
if (!is_array($data) || count($data) < 3) {
$response['error'] = 'Invalid GET parameters.';
echo json_encode($response);
exit;
}
$sessionId = $data[0];
$pool = $data[1];
$key = $data[2];
$value = isset($data[3]) ? $data[3] : null;
// check if session, pool and key are not empty
if ($sessionId == '' || $pool == '' || $key == '') {
$response['error'] = 'Session, Pool, and Key must not be empty.';
} else {
// check if the session exists
$sessionFile = 'users/'.$sessionId.'.json';
if (!file_exists($sessionFile)) {
$response['error'] = 'Session not found.';
} else {
// check if the pool exists
$poolFile = 'pools/'.$pool.'.json';
if (!file_exists($poolFile)) {
$response['error'] = 'Pool '.$pool.' not found.';
} else {
// check if the key exists
$poolData = json_decode(file_get_contents($poolFile), true);
if (!array_key_exists($key, $poolData)) {
$response['error'] = 'Key '.$key.' not found.';
} else {
// get data from file
if (!$value) {
$response['data'] = $poolData[$key];
} else {
// update data in file
$poolData[$key] = $value;
if (file_put_contents($poolFile, json_encode($poolData)) !== false) {
chmod($poolFile, 0666);
$response['success'] = $key.' updated.';
} else {
$response['error'] = $poolFile.' not saved.';
}
}
}
}
}
}
// send response
echo json_encode($response);
?>
JSON
Jetzt fehlen uns nur noch ein paar Musterdaten. Diese speichern wir in JSON-Dateien. Die Dateien liegen im Ordner /includes/pool/ und haben die Endung .json. Auch diese kannst du dir herunterladen.
Die Klick-Events werden über den Service-Worker synchronisiert. Dieser sendet die Events an alle geöffneten Tabs und Fenster, die dort über die items.js behandelt werden.
Javascript: service-worker.js// Process Message from Client
self.addEventListener('message', event => {
self.clients.matchAll().then(clients => {
clients.forEach(client => {
client.postMessage(event.data);
});
});
});
In unserem Beispiel wird das geklickte Item in allen Tabs/Fenstern grün markiert.
Fazit
Die Kommunikation mit der IndexedDB ist etwas komplex und leider auch viel zu langsam. Zudem treten immer mal wieder nicht reproduzierbare Fehler auf, die nur teilweise sauber abgefangen werden können. Für den prokuktiven Gebrauch eignet sich daher die hier vorgestellte Variante nur bedingt.
Dran bleiben
Du hast es geschafft. Abonniere meine Benachrichtigungen, um weitere News und Anleitungen von mir zu erhalten.