Benutzungsoberflächen in Webanwendungen bestehen zwingend aus HTML und CSS. Durch diese werden die Struktur und deren Aussehen beschrieben. Dabei ist es erst einmal egal, ob wir das HTML bereits auf dem Server erzeugen und direkt an den Browser senden oder ob dieses innerhalb des Browsers per JavaScript erzeugt wird.

Um zu HTML und CSS zu kommen, wurde dabei in der Vergangenheit häufig Seite für Seite in Form von Bildern, in Photoshop oder ähnlichen Tools, erstellt. Diese wurden dann innerhalb der Anwendung, vielfach Pixel genau, in HTML und CSS übersetzt. Bereits in diesem Ansatz kamen, wenn auch sehr implizit und versteckt, Komponenten zum Einsatz. Tauchte ein Element, beispielsweise ein Button, auf mehreren Seiten auf, wurde dieser, in der Regel, nicht jedes Mal komplett neu und anders designt, sondern glich sich. Mit der Zeit haben wir deswegen gelernt, dass es sinnvoller und effektiver ist, diese Komponenten zu spezifizieren und die Seiten der Anwendung aus diesen zu kombinieren.

Hierzu werden Komponentenbibliotheken erstellt. Diese entstehen, idealerweise, in enger Zusammenarbeit von Menschen mit Erfahrung in User Experience, User Interface Design und Frontendentwicklung. Beispiele für solche Bibliotheken gibt es viele. Die wohl bekanntesten Implementierungen sind Bootstrap und Material UI. Aber auch Shopify Polaris, SAP UI5 oder INNOQ sind offen ansehbare Komponentenbibliotheken.

Eine solche Komponentenbibliothek hat dabei mehrere Aufgaben. Als Erstes gibt sie einen Überblick über alle vorhandenen, und damit nutzbaren, Komponenten. Jede Komponente selbst besteht dann aus der Dokumentation, ihrer Schnittstelle und dem notwendigen HTML und CSS, das benötigt wird, um diese in der eigentlichen Anwendung zu verwenden. Die Dokumentation sollte dabei neben technischen Belangen vor allem beschreiben, für welche Anwendungsfälle die Komponente gedacht ist. Je nach Komponentenbibliothek stellt diese sogar fertige Bibliotheken für eine bestimmte Templateengine, wie Material UI für React, zur Verfügung.

Schnittstelle einer Komponente

Wie bereits angeteasert, besitzt jede Komponente, neben dem eigentlichen Markup und Styling, auch eine Schnittstelle. Betrachten wir hierzu die Card-Komponente aus Bootstrap, die wie in Abbildung 1 dargestellt aussieht.

Abb. 1: Bootstrap-Card-Beispiel

Hierbei handelt es sich um eine primär visuelle Komponente, die in der Basis um einen Block von Text einen Rahmen setzt, um diesen Block hervorzuheben. Zusätzlich ist es jedoch auch möglich, einen Header und Footer zu verwenden. Und auch der Inhalt ist nicht rein auf Text beschränkt, sondern kann andere Komponenten, wie hier einen Button, beinhalten.

Wollen wir nun exakt diese Card in einer Anwendung einsetzen, müssen wir das dokumentierte HTML-Markup, siehe Listing 1, in diese kopieren. Dieses Markup besteht nun aus technischen Details, wie den konkreten CSS­Klassen, und dem eigentlichen Inhalt. Hierzu zählt neben den eigentlichen Texten auch der enthaltene Button. Bevor wir also diesen Code blind in unsere Anwendung kopieren, müssen wir uns, mithilfe der Dokumentation der Komponente, erarbeiten, welche Teile wirklich zur eigentlichen Komponente gehören, welche variabler Bestandteil dieser sind und was dann individueller Inhalt ist.

<div class="card text-center">
  <div class="card-header">
    Featured
  </div>
  <div class="card-body">
    <h5 class="card-title">Special title treatment</h5>
    <p class="card-text">With supporting text ...</p>
    <a href="#" class="btn btn-primary">Go somewhere</a>
  </div>
  <div class="card-footer text-body-secondary">
    2 days ago
  </div>
</div>
Listing 1: HTML-Markup für Bootstrap-Card-Beispiel

Benötigen wir in unserer Anwendung diese Card an mehreren Stellen, können wir an jeder Stelle das notwendige Markup erneut, meistens durch Copy & Paste, schreiben und die Stellen mit individuellem Inhalt anpassen. Dabei laufen wir allerdings in die üblichen Probleme, die ein solches Vorgehen mit sich bringt. Primär sind das Kopieren und Anpassen fehleranfällig und ineffizient. Außerdem verursacht eine Änderung am Markup der Komponente hohen Aufwand. Anschließend muss jede kopierte Stelle gefunden und nachgezogen werden. Wer einmal von Bootstrap 4 auf 5 migrieren musste, weiß, wovon ich spreche. Kurz gesagt, wir verstoßen hier sehr stark gegen das Konzept von Don’t Repeat Yourself.

Aufgrund dieser Erkenntnis enthalten alle gängigen, neueren, JavaScript basierten Templateengines, wie Angular, lit oder JSX, Unterstützung, um solche Komponenten zu kapseln. Dadurch werden wir bei der Komponentennutzung nicht nur von der Aufgabe befreit, größere Mengen HTML-Markup zu kopieren, sondern uns steht auch eine klare und definierte Schnittstelle zur Verfügung. Außerdem kann nun die Implementierung innerhalb der Komponente geändert werden, ohne dass dies Aufwand an allen Nutzungsstellen verursacht. Je nach konkreter Technologie können wir nun sogar die Schnittstelle ändern und werden von einem Compiler unterstützt, um alle Stellen, die wir ändern müssen, bequem zu finden.

Klassische Templateengines

Im Gegensatz zu den oben genannten neuen JavaScript basierten Templateengines bieten uns die „klassischen“ Vertreter, die wir in der Regel auf Server-Seite nutzen, wie jinjava, mustache.java oder Thymeleaf, keine direkte Abstraktion, um Komponenten zu schreiben und zu verwenden. Je nach Engine können wir aber die vorhandenen Mittel nutzen, um zu einem ähnlichen Ergebnis zu gelangen. Das funktioniert aber nicht immer.

Um herauszufinden, in welcher Engine was, wie funktioniert und um beurteilen zu können, welche Engine eher geeignet ist, einen solchen Komponentenansatz konsequent umzusetzen, haben ein paar meiner Kollegen von INNOQ eine Komponenten-Challenge ins Leben gerufen. Diese definiert, aktuell, sechs Komponenten, die implementiert werden sollen. Diese enthalten dabei exemplarische Herausforderungen, die uns in Projekten immer wieder begegnet sind und im Folgenden vorgestellt werden.

Komponenten der Challenge

Die erste Komponente ist das Badge, eine visuelle Komponente, die einen Text auf einem farbigen Hintergrund darstellt und häufig mit abgerundeten Ecken daherkommt. Innerhalb der Challenge dient diese vor allem zur Überprüfung, ob es überhaupt möglich ist, in einer Templateengine eine Komponente zu definieren und wiederzuverwenden. Listing 2 zeigt das zu erzeugende Markup und, als Kommentar, eine mögliche Syntax zur Verwendung.

<!-- <badge type="alert">Critical!</badge> -->
<span class="badge bg-danger">Critical!</span>
Listing 2: Badge-Komponente

Die zweite Komponente ist ein Button. Dieser basiert auf einem der mächtigsten nativen HTML-Elemente, dem <button>. Dieses unterstützt, selbst ohne Aria mit einzubeziehen, eine sehr hohe Anzahl von Attributen. Diese Komponente prüft deshalb, ob es in der Templateengine möglich ist, einen Mechanismus anzubieten, um quasi beliebige Attribute durchzureichen, ohne jedes explizit in der Schnittstelle definieren zu müssen. Das Ganze klingt erst mal wie ein Hack oder Workaround, ist aber in der Praxis oft notwendig. Listing 3 zeigt auch hier, beispielhaft, wie die Komponente aussehen soll. Die Challenge selbst listet noch weitere Kombinationen auf.

<!-- <mybutton cta class="text-uppercase">Click me!</mybutton> -->
<button class="btn btn-primary text-uppercase">Click me!</button>
Listing 3: Button-Komponente

Als Nächstes ist auch die schon vorgestellte Card-Komponente Teil der Challenge. Diese dient vor allem dazu zu prüfen, ob es möglich ist, benannte Blöcke zu verwenden. Eine Card besteht dabei aus den beiden optionalen Blöcken header und footer und einem verpflichtenden Hauptblock. Wichtig ist dabei vor allem, dass diese Blöcke nicht nur reinen Text akzeptieren, sondern es uns auch ermöglichen, HTML-Markup, natürlich inklusive weiterer eigener Komponenten, zu übergeben. Nur so kann eine Komposition von verschiedenen Komponenten ermöglicht werden. Auch hier zeigt Listing 4, wie so eine Card aussehen kann.

<!--
<card>
  Some content with a <badge>Badge</badge>
  <slot="footer">Footer with <mybutton>Button</mybutton></slot>
</card>
-->
<section class="card">
  <div class="card-body">
    <p class="card-body">
      Some content with a <span class="badge bg-default">Badge</span>
    </p>
  </div>
  <div class="card-footer">
    Footer with <button class="btn">Button</button>
  </div>
</section>
Listing 4: Card-Komponente

Mit der List-Komponente wird vor allem überprüft, ob es möglich ist, innerhalb der Komponenten auch mit komplexeren Datentypen aus der Hostumgebung, wie Listen oder Maps, umzugehen. Weiterhin kann diese Komponente auch dazu genutzt werden, um zu gucken, ob es möglich ist, konkrete Typen zu erzwingen und diese gegebenenfalls auch noch zur Kompilierungszeit zu verifizieren. Listing 5 enthält auch hier wieder ein Beispiel.

<!--
<list ratio="1:3" items="{ 'key': 'value', 'more': 'content' }">
-->
<dl class="row">
  <dt class="col-sm-3">key</dt>
  <dd class="col-sm-9">value</dd>
  <dt class="col-sm-3">more</dt>
  <dd class="col-sm-9">content</dd>
</dl>
Listing 5: List-Komponente

Die Magic-Header-Komponente benötigt, im Gegensatz zu allen vorherigen, Zustand. Dieser wird benötigt, um innerhalb dieser Komponente Überschriften automatisch mit dem richtigen Level zu versehen, ohne dass dies bei der Verwendung explizit angegeben werden muss. Listing 6 zeigt auch dies.

<!--
<magic-header>
  <header>Überschrift (h1)</header>
  <magic-header>
    <header>Überschrift (h2)</header>
  </magic-header>
  <header>Überschrift (wieder h1)</header>
</magic-header>
-->
<h1>Überschrift (h1)</h1>
<h2>Überschrift (h2)</h2>
<h1>Überschrift (wieder h1)</h1>
Listing 6: Magic-Header-Komponente

Die sechste, und letzte, Komponente ist die Field-Group. Diese hat eigentlich keine neue Herausforderung mehr, die nicht bereits von einer der vorherigen Komponenten überprüft wurde. Vielmehr dient diese als komplexeres Beispiel aus der realen Welt, da eine solche Komponente in fast jedem Projekt benötigt wird. Dabei ist diese Komponente dafür verantwortlich, innerhalb einer <form> ein <input>-Feld, inklusive <label> und optionalen Validierungsfehlern, darzustellen.

Diese Komponente kann optional auch noch als erweiterte Version umgesetzt werden. Diese Version sollte sehr stark mit dem verwendeten Webframework interagieren können und Aspekte wie Validierungsfehler oder Internationalisierung unterstützen.

Fazit

In dieser Kolumne haben wir die Idee von UI­Komponenten kennengelernt. Dabei handelt es sich, zusammengefasst, um gekapselte und damit wiederverwendbare Elemente einer Benutzungsoberfläche. Diese ermöglichen es uns, im Gegensatz zum Ansatz mit Copy & Paste, Benutzungsoberflächen effizient und wartbar zu bauen.

Da viele der klassischerweise in Java eingesetzten Templateengines kein direktes Konzept für solche Komponenten mitbringen, haben wir zusätzlich die INNOQ-Komponenten-Challenge kennengelernt. Diese definiert, aktuell, sechs beispielhafte Komponenten, um überprüfen zu können, ob und wie gut der Komponentenansatz in einer konkreten Templateengine funktioniert. Neben der Challenge enthält diese auch bereits eine Reihe von Implementierungen.

Auch wenn diese Kolumne etwas theoretischer und weniger Java zentriert war, hoffe ich, dass Sie etwas dabei gelernt haben. An dieser Stelle möchte ich mich gerne auch noch bei meinen Kollegen FND, Joachim, Lucas, Michael und besonders bei Till bedanken. In diesem Artikel steckt meine Essenz von vielen Diskussionen mit euch. Ich hoffe, er wird euren Ansprüchen gerecht.