WebComponents in einem Java Webframework einsetzen
- WebComponents
- JavaScript
- Java
- Wicket
Unter dem Begriff Web Components wird eine Reihe unterschiedlicher Webtechnologien zusammengefasst, die es ermöglichen, benutzerdefinierte und wiederverwendbare HTML-Elemente zu implementieren. Ihre Funktionalität und Styling sind in sich gekapselt und damit vollständig getrennt vom restlichen Code der Webanwendungen, in denen sie eingesetzt werden. Sie funktionieren in allen modernen Webbrowsern und lassen sich damit in allen Webframeworks, die HTML nutzen, einsetzen. Auch in einem Java-basierten Server-Side-Rendering-Webframework, wie Apache Wicket, ist dies möglich. In diesem Artikel wird beschrieben, wie eine einfache WebComponent unter Einsatz entsprechender Webtechnologien zunächst in einem JavaScript-Projekt entwickelt und später in eine vorhandene Webanwendung integriert werden kann. Dabei werden sowohl die notwendigen Anpassungen am bestehenden Build als auch die unterschiedlichen Ansätze zur Integration mit dem eingesetzten Webframework vorgestellt.
Die neue WebComponent
Die Reise beginnt mit der Entwicklung einer einfachen WebComponent. Ihre Aufgabe soll darin liegen, einen Zeitabstand vom aktuellen bis zum benutzerdefinierten Zeitstempel zu berechnen und ihn als formatierte Zeichenfolge in einer festgelegten Sprache auszugeben. Diese Darstellung soll sich in einem Intervall von einer Minute aktualisieren und damit immer den aktuellen Abstand anzeigen. Das Format dieser Zeichenfolge soll zum einen davon abhängig sein, ob der angegebene Zeitstempel sich in der Vergangenheit oder Zukunft befindet, und zum anderen, wie weit dieser vom aktuellen Zeitstempel entfernt ist. In Tabelle 1 werden die zeitlichen Abstände und die entsprechenden Formate gegenübergestellt.
Bezugsdatum | Format |
---|---|
Am selben Tag in der Vergangenheit | vor <X> <Zeiteinheiten> |
Am selben Tag in der Zukunft | in <X> <Zeiteinheiten> |
Am Tag zuvor | Gestern um <Uhrzeit> Uhr |
Am nächsten Tag | Morgen um <Uhrzeit> Uhr |
In der letzten Woche | Letzten <Wochentag> um <Uhrzeit> Uhr |
In der nächsten Woche | <Wochentag> um <Uhrzeit> Uhr |
Sonst | 10.03.2019 |
Zur Berechnung von Zeitabständen wird Moment.js verwendet – eine JavaScript-Bibliothek zum Parsen, Validieren, Manipulieren und Berechnen von Zeit- und Datumswerten.
Spezifikationen
WebComponents setzen auf die Standards Custom Elements, Shadom DOM, ES Module und HTML Template auf. Zusammengefasst ermöglichen sie die Erweiterung von HTML mit neuen Komponenten mit gekapseltem Styling und benutzerdefiniertem Verhalten. Bis auf das Feature HTML Template werden in der anknüpfenden Implementierung alle Standards verwendet, weshalb sie im Folgenden kurz erläutert werden:
- Custom Elements: Diese Spezifikation umfasst einen Satz von JavaScript APIs, die es ermöglichen, benutzerdefinierte Elemente und deren Verhalten zu definieren und sie überall im HTML zu verwenden.
- Shadow DOM: Diese Spezifikation definiert die Kapselung des Stylings und des Markups von Web Components.
- ES Module: Seit Version ES6 sind Module im JavaScript-Standard enthalten und bilden eine Basis für die strukturierte und gekapselte Entwicklung von JavaScript.
- HTML Template: Das Feature HTML Template ist ein Mechanismus, mit dem benutzerdefinierte Markup-Vorlagen im HTML-Dokument angegeben werden können, die zwar Client-seitig evaluiert aber nicht gerendert werden. Erst bei Bedarf können sie gezielt via JavaScript instanziiert werden.
Das Grundgerüst
Vorbereitend wird ein neues JavaScript-Projekt in einem neuen Verzeichnis mit einem beliebigen Namen, wie zum Beispiel WebComponents, angelegt. In diesem Projekt soll die neue WebComponent TimeGap entwickelt werden.
Um ein neues Custom Element zu implementieren, sind zwei Dinge notwendig: ein Klassenobjekt, welches die Klassensyntax
des ES6-Standards verwendet, und dessen Registrierung über den Aufruf der define-Methode auf dem globalen
customElements
-Objekt. Um die Implementierung von der Registrierung zu trennen werden im Unterordner src/
die
zwei JavaScript-Dateien time-gap.js
und index.js
erstellt. In der ersten wird das Klassenobjekt der WebComponent
implementiert, in der zweiten findet dessen Deklaration als Custom Element unter dem Tag-Namen time-gap
statt.
Die Datei index.js
stellt zugleich das zentrale Modul dar, das in das HTML-Dokument eingebunden wird, in dem die
Web Component verwendet werden soll. Listing 1 zeigt die Inhalte der beiden Dateien.
// src/time-gap.jsexport class TimeGap extends HTMLElement {constructor() {super();const shadow = this.attachShadow({ mode: 'open' });this.label = document.createElement('span');this.label.innerText = 'vor nicht allzu langer Zeit...';shadow.appendChild(this.label);}}// src/index.jsimport { TimeGap } from './time-gap.js';customElements.define('time-gap', TimeGap);
Die WebComponent beinhaltet zunächst noch keine Logik. Ihr Mark-up besteht aus einem span
-Element, das an die
Shadow-Root angehängt und initial mit einem Platzhaltertext versehen wurde. Um die Nutzung auf einer Website zu
demonstrieren, wird im Hauptverzeichnis eine index.html
erstellt und das Modul index.js
, wie im Listing 2
abgebildet, eingebunden. Der Inhalt des body
-Tags im HTML-Dokument besteht dabei lediglich aus der Verwendung
des neuen time-gap
-Tags.
<!DOCTYPE html><html lang="en"><head><!-- ... --><!-- index.js als ES-Modul einbinden --><script type="module" src="src/index.js"></script></head><body><!-- die neue Web Component verwenden --><time-gap></time-gap></body></html>
Damit die importierten ES-Module nachgeladen werden können, wird zusätzlich ein Webserver benötigt. Zu reinen
Entwicklungszwecken von WebComponents empfiehlt sich die Verwendung eines sogenannten
Live-Servers. Dieser überwacht den gesamten Quellcode und lädt automatisch
Inhalte nach, sobald sich diese geändert haben. Ein Live-Server kann, sofern Nodes.js auf
dem lokalen System verfügbar ist, über den Paketmanager NPM installiert und über die Kommandozeile im Hauptverzeichnis
des jeweiligen Projekts mit dem Befehl live-server
gestartet werden.
Wird die index.html
nun in einem modernen Browser aufgerufen, der die oben erwähnten Features implementiert, so
erscheint der Platzhaltertext, der im Konstruktor der WebComponent hinzugefügt wurde.
Die Schnittstelle
Im nächsten Schritt geht es darum, der Web Component etwas Leben einzuhauchen: Die statische Ausgabe soll durch eine
dynamische ersetzt werden. Als Vorbereitung dafür muss die Schnittstelle der neuen Komponente in Form von Tag-Attributen
definiert werden. Insgesamt werden zwei Attribute benötigt: from
und locale
. Das Attribut from
soll als
Bezugspunkt für die Berechnung des Zeitabstands dienen. Es soll in einem nach dem internationalen Standard ISO-8601
gültigen Datumsformat angegeben werden. Mit locale
sollen Anwender die Sprache für die Ausgabe festlegen können.
Die Belegung könnte zum Beispiel wie folgt aussehen:
<time-gap from="2019-01-03T14:45" locale="de"></time-gap>
Einsatz von moment.js
Für die Berechnung und die formatierten Ausgaben des Zeitabstands soll die JavaScript-Bibliothek
Moment.js verwendet werden. Damit diese Abhängigkeit (und später andere) nicht manuell
verwaltet werden muss, empfiehlt sich die Erstellung einer package.json
-Datei. Es wird also höchste Zeit, um die
Installation von Node.js auf der lokalen Entwicklungsumgebung vorzunehmen.
Ist Node.js auf dem System verfügbar, so kann der enthaltene Paketmanager NPM dazu benutzt werden, um eine
neue package.json
-Datei zu generieren. Für diesen Schritt muss über die Kommandozeile im Hauptverzeichnis des
Projekts der Befehl npm init -y
ausgeführt werden. Mit dem Befehl npm install moment --save
kann anschließend
die JavaScript-Bibliothek Moment.js aus der NPM-Registry heruntergeladen und als Abhängigkeit in die package.json
aufgenommen werden. Damit wurde Moment.js im Projekt installiert und kann über die import
-Anweisung in das Modul
time-gap.js
importiert und dort verwendet werden.
Die Programmierschnittstelle von Moment.js ist sehr umfangreich. Sie stellt Methoden zur Verfügung, die es erlauben,
auf unterschiedliche Art und Weise mit Zeit- und Datumswerten zu arbeiten. Für die Umsetzung von TimeGap
sind vor
allem die Methoden isSame
, fromNow
und calendar
von Bedeutung. Die Methode isSame
ermöglicht die Abfrage,
ob das als Argumente übergebene Datum sich im selben Jahr Monat oder Tag befindet, wie das Datum, mit dem das
moment
-Objekt initialisiert wurde. Die Methode fromNow
gibt den zeitlichen Abstand vom jetzigen Zeitpunkt bis
zu dem Zeitpunkt, mit welchem das moment
-Objekt initialisiert wurde. Das Ergebnis kommt der Antwort auf die
Frage „Wie lange ist es her?“ sehr nah und kann beispielsweise „vor einer Stunde und 30 Minuten“ lauten.
Mit calendar
verhält es sich ähnlich, jedoch mit dem Unterschied, dass hier die Frage „Wann“ beantwortet wird.
In der Tabelle 2 sind die möglichen Ergebnisse dieser Methode dargestellt.
Bezugsdatum | Beispielhafte Ausgabe |
---|---|
In der letzten Woche | Letzten Montag um 15:30 Uhr |
Am Tag zuvor | Gestern um 15:30 Uhr |
Am selben Tag | Heute um 15:30 Uhr |
Am nächsten Tag | Morgen um 15:30 Uhr |
In der nächsten Woche | Mittwoch 15:30 Uhr |
Sonst | 10.03.2019 |
Den Zeitabstand ermitteln
Für die Ermittlung des aktuellen Zeitabstands wird eine Klassenmethode mit dem Namen elapsedTime
definiert, deren
Implementierung in Listing 3 abgebildet ist.
import moment from 'moment';export class TimeGap extends HTMLElement {//...static elapsedTime(from, locale) {const fromMoment = moment(from).locale(locale);return fromMoment.isSame(new Date(), 'day') ?fromMoment.fromNow() :fromMoment.calendar();}}
Die Methode bekommt die zwei Argumente from
und locale
übergeben,
womit ein neues moment
-Objekt initialisiert und in der Variable fromMoment
gespeichert wird. Damit wurde der
Bezugspunkt der Zeitberechnung als auch die Sprache für die Ausgabe festgelegt.
In einer Bedingung wird anschließend festgestellt, ob es sich bei dem übergebenen Zeitstempel, um einen Zeitstempel von
heute handelt. Das gelingt, indem die Methode isSame
auf dem moment
-Objekt mit einer neuen Datum-Instanz new Date()
und der Zeichenfolge day
aufgerufen wird. Trifft diese Bedingung zu, dann wird der zeitliche Abstand mit der Methode
fromNow
berechnet. Andernfalls kommt die calendar
-Methode zum Tragen und liefert das Ergebnis.
In Listing 4 ist die nächste Methode implementiert, die für das Aktualisieren des Inhalts vom span
-Element
zuständig ist. Die vom Anwender angegebenen Attribute from
und locale
werden ausgelesen und als Argumente an den
Aufruf der Methode elapsedTime weitergereicht. Das Ergebnis wird auf der Benutzeroberfläche zur Anzeige gebracht, indem
es der Eigenschaft innerText
des span
-Elements zugewiesen wird.
//...updateTimeGap() {const from = this.getAttribute('from');const locale = this.getAttribute('locale');this.label.innerText = TimeGap.elapsedTime(from, locale);}//...
Nun fehlt noch der Aufruf der updateTimeGap
-Methode in regelmäßigen Abständen von einer Minute. Der initiale
Zeitpunkt dafür ist genau der, an dem die Website zum ersten Mal geladen wird. Für dieses Ereignis und noch ein
paar andere verwenden CustomElements die sogenannten Lifecycle-Callbacks – Methoden, die zu bestimmten Phasen in
ihrem Lebenszyklus automatisch aufgerufen werden. Die hier relevanten Methoden lauten connectedCallback
und
disconnectedCallback
. Die erste wird aufgerufen, wenn das CustomElement in das DOM eingehängt, die zweite, wenn es
aus dem DOM wieder entfernt wird.
Die Klasse TimeGap
wird um die beiden Methoden erweitert. In der
connectedCallback
wird die updateTimeGap
initial aufgerufen und anschließend als Argument an die auf dem
Window-Objekt definierte setInterval
-Methode mit 60.000 Millisekunden übergeben, um sich im Intervall von
einer Minute aufrufen zu lassen. Da sie dadurch als globale Funktion aufgerufen wird, muss die aktuelle Instanz der
Klasse als ihr Ausführungskontext mit bind(this)
explizit angegeben werden. Die als Ergebnis von setInterval
gelieferte ID wird in einer entsprechenden Instanzvariable gespeichert. Sie wird in der disconnectedCallback
benutzt,
um das Intervall mit der globalen clearInterval
-Methode wieder entfernen zu lassen.
Damit ist TimeGap
vollständig implementiert. Listing 5 zeigt den gesamten Quelltext der neuen WebComponent.
import moment from 'moment';export class TimeGap extends HTMLElement {static elapsedTime(from, locale) {const fromMoment = moment(from).locale(locale);return fromMoment.isSame(new Date(), 'day') ?fromMoment.fromNow() :fromMoment.calendar();}constructor() {super();const shadow = this.attachShadow({ mode: 'open' });this.label = document.createElement('span');this.label.innerText = 'vor nicht allzu langer Zeit...';shadow.appendChild(this.label);}connectedCallback() {this.updateTimeGap();this.intervalId = setInterval(this.updateTimeGap.bind(this),60000);}disconnectedCallback() {clearInterval(this.intervalId);}updateTimeGap() {const from = this.getAttribute('from');const locale = this.getAttribute('locale');this.label.innerText = TimeGap.elapsedTime(from, locale);}}
Module mit Webpack packen
Um Abhängigkeiten zwischen den Modulen aufzulösen und den benötigten Quelltext zusammengepackt in einer einzelnen JavaScript-Datei auszuliefern, wird Webpack eingesetzt. Damit wird sichergestellt, dass die Web Component keine unnötigen Server-Anfragen stellt, um Skripte, die sie importiert, nachzuladen. Ein nützlicher Nebeneffekt ist dabei, dass der Code in Webbrowsern ausgeführt werden kann, die noch keine Unterstützung für ES-Module bereitstellen.
Folgende Anweisung muss für die Installation von Webpack und des ebenso benötigten Webpack-CLI im Hauptverzeichnis ausgeführt werden:
npm install webpack webpack-cli -save-dev
Um Webpack zu konfigurieren, wird im Hauptverzeichnis eine webpack.config.js
mit dem Inhalt aus Listing 6 angelegt.
const path = require('path');module.exports = {entry: './src/index.js',mode: 'development',output: {filename: 'main.js',path: path.resolve(__dirname, 'dist')}};
Neben sämtlichen Konfigurationsmöglichkeiten kann damit vor allem der Einstiegspunkt, der Pfad und der Name der
zusammengepackten Datei definiert sowie der Modus für das Packen festgelegt werden. Es werden der Name der Zieldatei
und das Verzeichnis, in dem sie abgelegt werden soll, definiert. Diese Änderung bedingt auch die Anpassung des
entsprechenden script
-Tags in der index.html
. Dort muss der neue Pfad zu der gepackten
JavaScript-Datei dist/main.js
angegeben werden:
<head><!--...--><script src="dist/main.js"></script><head>
Der Modus production
sorgt dafür, dass die Ergebnisdatei möglichst klein ausfällt. Sie wird minifiziert, indem der
komplette Leerraum und Kommentare entfernt sowie Variablennamen in deutlich kürzere umbenannt werden. Für die
Entwicklung ist dieser Modus ungeeignet, weshalb hier der Parameter mode
auf development
eingestellt wird.
Die WebComponent kann nun getestet werden, indem die Befehle npx webpack
und live-server
über die Kommandozeile
im Hauptverzeichnis nacheinander aufgerufen werden. Mit Webpack wird der gesamte JavaScript-Quellcode zu einer
einzigen main.js
-Datei zusammengepackt und anschließend vom Live-Server bereitgestellt. Die im Webbrowser
automatisch aufgerufene index.html
lässt die neue WebComponent das Ergebnis berechnen und wie erwartet anzeigen.
Integration in eine Webanwendung
Im Folgenden wird schrittweise die Integration der entwickelten WebComponent in eine bestehende Webanwendung vorgestellt.
Die Webanwendung Tasks
Die Webanwendung Tasks wurde mit Apache Wicket in der Version 8.1 entwickelt. Das Projekt wird mit Gradle als Build-Management-Tool gebaut und setzt Spring Boot 2 als Entwicklungswerkzeug für Anwendungen mit Spring-Kontext ein. Die Applikation stellt eine einfache Aufgabenliste dar. Es können neue Aufgaben hinzugefügt, bestehende Aufgabe als erledigt markiert oder gelöscht werden. Abbildung 1 zeigt die Benutzeroberfläche der Webanwendung.
In jedem Listeneintrag, unterhalb der Aufgabenbeschreibung, wird das Erstellungsdatum des jeweiligen Eintrags ausgegeben. Dieses Datum soll durch die neue WebComponent etwas gesprächiger gemacht werden.
Die WebComponent einbinden
Die initiale Projektstruktur der Webanwendung kann Abbildung 2 entnommen werden.
Im Verzeichnis src/main/webapp
sind aktuell die benötigten Stylesheets und Webfonts der Webanwendung abgelegt. In diesem
Ordner wird ein neues Unterverzeichnis js
erzeugt, in welches die zuvor erstellten JavaScript-Dateien time-gap.js
und
index.js
hineinkopiert werden. Die CSS-Datei der Webanwendung wird bereits in der BasePage-Klasse, von der alle Seiten
der Applikation erben, in der Methode renderHead(response: HeaderResponse)
eingebunden. Mit ihr kann in Wicket der
HTML-Head der angefragten Seite mit unterschiedlichen Header-Tags dekoriert werden. Diese Methode wird, wie in Listing 7
abgebildet, erweitert. An die Response wird dabei ein JavaScriptHeaderItem
angehängt, mit dessen Hilfe die mit der
relativen URL dist/js/main.js
angegebene JavaScript-Datei geladen werden soll. Im weiteren Verlauf wird im Build der
Webanwendung sichergestellt, dass diese Datei dort auch tatsächlich landet.
public class BasePage extends WebPage {@Overridepublic void renderHead(IHeaderResponse res) {super.renderHead(res);res.render(CssHeaderItem.forUrl("dist/css/main.css"));res.render(JavaScriptHeaderItem.forUrl("dist/js/main.js"));}}
Node.js als Gradle-Plug-In
Wenn die einzelnen JavaScript-Module weiterhin zu einer einzelnen JavaScript-Datei gepackt und die externe Bibliothek Moment.js nicht in das Quellcode-Repository des Projekts aufgenommen werden soll, dann wird das Build-Management um die Verwendung von NPM und Webpack nicht herumkommen. Die JavaScript-Laufzeitumgebung Node.js wird also auch hier weiterhin benötigt.
Da das Build-Management nicht von der lokalen Node.js-Umgebung abhängig gemacht werden soll, empfiehlt sich stattdessen der Einsatz des Gradle-Plug-ins Node. Dieses Plug-in ermöglicht die lokale Installation und Verwendung einer Projekt-spezifischen Node.js-Version, ohne die JavaScript-Laufzeitumgebung global auf dem System des Entwicklers installieren zu müssen.
Das Node-Plug-in wird eingebunden, indem die build.gradle
-Datei um den plugins
-Block mit der vollqualifizierten
Plug-in-ID erweitert wird (Listing 8).
plugins {id "com.moowork.node" version "1.2.0"}
Diese Einbindung stattet den Gradle-Build zusätzlich mit ein paar hilfreichen Tasks aus, die für die Arbeit mit
Node-Projekten unabdingbar sind. Der wichtigste Task dabei ist npmInstall
, mit dem alle in der package.json
als
Abhängigkeit definierten NPM-Pakete heruntergeladen und im Verzeichnis node_modules
installiert werden.
Im Hauptverzeichnis des Projekts wird eine neue package.json
-Datei mit dem Inhalt aus Listing 9 angelegt.
{"name": "task-components","version": "1.0.0","dependencies": {"moment": "^2.23.0"},"devDependencies": {"webpack": "^4.28.4","webpack-cli": "^3.2.1"}}
Webpack als Gradle-Task
Im nächsten Schritt wird in der build.gradle
ein neuer Task mit dem Namen webpack definiert. Als Typ wird dabei
der NodeTask
aus dem Node-Plug-in benutzt. Dieser Typ ermöglicht die Ausführung von Node.js-Skripten als Teil des
Build-Prozesses. Da mit diesem Task der JavaScript-Quellcode zusammengepackt werden soll, muss webpack
als ausführbares
Skript angegeben werden. Dieses befindet sich in dem von NPM automatisch angelegten node_modules/.bin
-Verzeichnis.
In diesem Ordner legt Node.js alle ausführbaren Skripte nach ihrer Installation ab.
Damit webpack
die Einstiegsdatei finden und das Ergebnis am richtigen Ort wieder ablegen kann, müssen entsprechende
Argumente an das auszuführende Skript übergeben werden. Das erste Argument ist dabei der Pfad zu jener JavaScript-Datei,
die als Einstiegspunkt dient und alle anderen Dateien importiert. Im Falle dieser Entwicklung ist das die index.js
.
Da Webpack theoretisch auch mehrere Quellen verarbeiten kann, wird als zweites Argument der -o
-Parameter (-o für Output)
angegeben, um die Angabe der Zieldatei festzulegen. Als drittes Argument wird dementsprechend der Pfad zur Zieldatei angegeben.
Damit dieser Task vom Gradle-Cache profitieren kann und nur dann erneut ausgeführt wird, wenn sich am Code etwas geändert
hat oder die Zieldatei gelöscht wurde, müssen die entsprechenden Verzeichnisse unter Beobachtung gestellt werden.
Mit inputs.dir
und outputs.dir
können die entsprechenden Pfade festgelegt werden. Listing 10 zeigt die
vollständige Implementierung des Gradle-Tasks.
task webpack(type: NodeTask) {inputs.dir("src/main/webapp/js")outputs.dir("src/main/webapp/dist/js")script = file("$projectDir/node_modules/.bin/webpack")args = ["src/main/webapp/js/index.js", "-o","src/main/webapp/dist/js/main.js"]}
Nun wurde dafür gesorgt, dass das nötige JavaScript durch den Build-Prozess gepackt und über die Einbindung in der
BasePage
in der Webanwendung geladen werden kann. Auch wenn der neue HTML-Tag time-gap
jetzt schon benutzt
werden könnte, fehlt hier immer noch ein wichtiger Schritt der Integration – die Datenbindung.
Datenbindung
Die Datenbindung ist stark davon abhängig, welches Webframework eingesetzt wird. In Wicket kann beispielsweise die
Kombination aus einem AttributeAppender
und einem Model
verwendet werden, um einen aus der Datenbank geladenen
oder errechneten Wert an ein bestimmtes Attribut eines Tags im HTML zu binden. Listings 11 und 12 und zeigen, wie eine solche
Datenbindung für die TimeGap
in der Webanwendung Tasks aussehen könnte.
<time-gap wicket:id="created" locale="de"></time-gap>
//...private void addTimeGap(IModel<LocalDateTime> createdModel) {WebMarkupContainer timeGap =new WebMarkupContainer("created");timeGap.add(new AttributeAppender("from", createdModel));add(timeGap);}//...
Ein anderer Weg, der eine solche Komponente im Wicket-Kontext wiederverwendbarer macht, ist die Implementierung
einer eigenen Component-Klasse. In dieser speziellen Component-Klasse kann zum einen der richtige Tag-Name gesetzt,
zum anderen die Datenbindung an die entsprechenden Attribute gekapselt werden. Der Aufrufer dieser Klasse muss dann nur
noch ein passendes Model vom Typ LocalDateTime
an den Konstruktor übergeben, um den Rest kümmert sich die Klasse selbst.
In Listing 13 ist eine mögliche Implementierung und Listings 14 und 15 ihre Verwendung abgebildet.
public class TimeGap extendsGenericWebMarkupContainer<LocalDateTime> {public TimeGap(String id, IModel<LocalDateTime> model) {super(id, model);}@Overrideprotected void onComponentTag(ComponentTag tag) {super.onComponentTag(tag);tag.setName("time-gap");tag.put("from", getModelObject().toString());tag.put("locale", getLocale().getCountry());}}
//...add(new TimeGap("created", createdModel));//...
<!-- ... --><span wicket:id="created"></span><!-- ... -->
Abbildung 3 stellt die angepasste Benutzeroberfläche der Webanwendung dar. An Stelle von Datums- und Zeitwerten erscheinen nun entsprechende Zeitabstände, die durch die eingebundene WebComponent errechnet und in der gewünschten Sprache zur Anzeige gebracht werden.
Fazit
In diesem Artikel wurde gezeigt, wie einfach sich eine Web Component implementieren und in ein bestehendes Gradle-Projekt mit einem Java-basierten Server-Side-Rendering-Webframework integrieren lässt. Das Konzept der Kapselung von Mark-up und Logik bei WebComponents erhöht nicht nur ihren Wiederverwendungswert, auch die Wartbarkeit wird dadurch enorm erleichtert. Da sich solche Komponenten wie native HTML-Elemente verhalten, müssen ihre Anwender zum einen keinen Initialisierungscode schreiben und können zum anderen von der einfachen Datenbindung über Tag-Attribute profitieren. Die vorgestellte Node.js-Integration liefert den Vorteil, dass die eingesetzten JavaScript-Bibliotheken als NPM-Pakete verwaltet und damit nicht in das Quellcode-Repository eingecheckt werden müssen. Zusätzlich eröffnet sie die Möglichkeit, einen vollständigen JavaScript-Build mit einem dedizierten Task-Runner, wie beispielsweise Gulp, in den Build-Prozess der Webanwendung einbeziehen zu können.
Zur Blog-Post Übersicht