This article is also available in English
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.
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 CSSKlassen, 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.
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.
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.
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.
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.
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.
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.