Eine vernünftige Architektur auf der Clientseite wird oft sehr vernachlässigt. JavaScript wird in der Verwendung reduziert auf seine Skript-Funktionalitäten. Dabei lässt sich durch ein paar Handgriffe eine sehr gute Architektur einbauen, die bereits ein mini-MVC Framework implementiert.
Die Grundlage dabei bilden die Routen. Eine Route repräsentiert dabei eine statische Seite unter einer URL. Der Ablauf ist folgender:
- Definition der Routen
- Der Dispatcher wird gestartet
- Dieser sucht für die aktuelle URL eine konfigurierte Route
- Der Controller wird aus der Route geholt und initialisiert
- Der Controller-Code wird ausgeführt
Definition der Routen
1 2 3 4 5 6 7 8 9 10 11 12 | class Route { constructor(url, controllerClass) { this._url = url; this._controllerClass = controllerClass; } matches(currentUrl) { return currentUrl.match(this._url).length > 0; } getControllerClass() { return this._controllerClass; } } |
Diese Klasse repräsentiert eine einzelne Route. Es wird die URL als regulärer Ausdruck übergeben und die Referenz der Controller-Klasse.
Mit der Methode matches wird untersucht, ob die Route auf die URL passt.
Verwalten der Routen und Suchen der aktuellen Route:
1 2 3 4 5 6 7 8 9 | const Routing = { _routes: [], registerRoute(route) { this._routes.push(route); }, getCurrentRoute(currentUrl) { return this._routes.find((route) => route.matches(currentUrl)); } } |
Dieses Singleton Objekt verwaltet alle definierten Routen und sucht nach der ersten passenden Routendefinition zu einer URL.
Der Dispatcher
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class Dispatcher { constructor() { window.addEventListener('popstate', () => this.onRouteChanged()); this.onRouteChanged(); } onRouteChanged() { const url = `${window.location.pathname}${window.location.hash}`; const route = Routing.getCurrentRoute(url); let controllerInstance = null; if(route) { const controllerClass = route.getControllerClass(); controllerInstance = new controllerClass(); } const event = new Event('routeDispatched'); event.controller = controllerInstance; window.dispatchEvent(event); } } |
Der Dispatcher sucht für die aktuelle URL die passende Route. Wenn eine vorliegt, dann wird der Controller instanziiert und ein Event auf window getriggert. Das Event enthält die Controller-Instanz für den Fall, dass jemand den Controller benötigt. Aber vor allem lässt das Event eine asynchrone Steuerung zu.
Anwendungs-Beispiel
1 2 3 4 5 6 7 8 | class ControllerA { constructor() { alert('It works!'); } } Routing.registerRoute(new Route('', ControllerA)); const dispatcher = new Dispatcher(); |
Fazit
Mit ein paar Handgriffen ist ein einfaches Dispatching gebaut. Natürlich fehlen noch einige Dinge; Fehlerbehandlung, eine Model- und eine View-Schicht. Wenn man weiterhin mit Promises arbeitet, kann man auf einfachem Wege asynchrone Initialisierungen implementieren. So lässt sich beispielsweise eine “initialize” Methode im Controller einbauen, die Daten asynchron vom Server lädt. Model und View Klassen lassen sich bequem über die Route hinzufügen. Man kann Basisklassen für alle Ebenen verwenden, um gemeinsame Funktionalitäten zu kapseln.
Weitere Links:
- Beispiel auf jsfiddle: https://jsfiddle.net/p4h7638t/