Für die Umsetzung des Frontends einer Webanwendung war in den letzten Jahren meistens eine Single-Page-Anwendung (SPA) erste Wahl. Diese Art komplett im Browser laufendes JavaScript, das per JSON mit dem Backend spricht, hatte in kurzer Zeit alles andere überrannt. Doch seit Kurzem scheint das Pendel wieder in die andere Richtung zu schwingen, denn bei SPAs entsteht eine Menge an JavaScript.
Natürlich darf kein Trend ohne Bibliotheken und Frameworks auskommen. Mit htmx gibt es aktuell eine solche Bibliothek, welche uns verspricht, beim Bau moderner Frontends mit einem simplen und deklarativen Ansatz zu unterstützen. Deshalb wollen wir uns hier einmal anschauen, was htmx zu bieten hat.
htmx
Das Kernziel von htmx ist es, die bereits in HTML vorhandenen Eigenschaften von Hypertext zu erweitern. Hypertext als Kern des World Wide Web (WWW), wie wir es heute kennen, ist dabei der Teil, der es uns erlaubt, zwischen den zahlreichen Dokumenten im WWW zu springen. HTML unterstützt dies durch Links und Formulare. Links sind dabei auf die Nutzung des HTTP-Verbs GET
beschränkt. Formulare unterstützen neben GET
auch noch POST
als Verb. Beide führen dabei zum Laden eines neuen Dokuments und damit auch zu einem kompletten Neuzeichnen der Seite im Browser. Zur Ausführung der Aktion muss entweder der Link geklickt oder per Tastatur ausgelöst werden oder das Formular muss abgeschickt werden.
Genau hier setzt htmx an und möchte es uns ermöglichen, auch andere HTML-Elemente zur Hypertextnavigation zu verwenden. Dabei sollen uns auch die nicht von HTML unterstützten HTTP-Verben, wie DELETE
oder PUT
, zur Verfügung stehen. Und auch die Möglichkeiten, diese Aktion auszulösen, werden erweitert. Zuletzt wird dabei noch, als Optimierung, unterstützt, dass nur Teile der aktuellen Seite durch das Ergebnis der Aktion ersetzt werden und somit nicht zwangsweise die ganze Seite neu gezeichnet werden muss. Dabei wird standardmäßig davon ausgegangen, dass der Server mit HTML antwortet und nicht, wie bei SPAs üblich, mit JSON.
Als Programmiermodell setzt htmx primär darauf, dass das vom Server generierte HTML deklarative Anweisungen, mithilfe von Attributen, enthält. Im Browser wird dann die, minifiziert um die 16 KByte große, JavaScript-Bibliothek hinzugefügt Diese kommt ohne eigene Abhängigkeiten daher und bietet uns neben den Kernfunktionen auch einen Erweiterungsmechanismus an.
Doch beginnen wollen wir, wie es sich gehört, am Anfang und damit bei Ajax, der Kernfunktionalität von htmx.
Ajax
Ajax, Asynchronous JavaScript and XML, gibt es bereits seit den 90ern und bezeichnet die Technik, im Browser mittels JavaScript dynamisch Inhalte nachzuladen oder zu aktualisieren, ohne die gesamte Seite neu zu laden. Da das JavaScript hierbei im Hintergrund, asynchron, ausgeführt wird, bleibt die Seite auch während der Aktion benutzbar. Im Grunde nutzen also auch die modernen SPAs diese Funktionalität, auch wenn wir in diesem Zusammenhang den Begriff Ajax in der Regel nicht mehr nutzen.
Aber htmx hat sich dazu entschlossen, seine Kernfunktionalität mit diesem Begriff zu bezeichnen. Um diese Funktionalität zu nutzen, reicht uns bereits, neben dem Einbinden der Bibliothek, ein einzelnes Attribut, wie in Listing 1 zu sehen. Klicken wir nun auf dieses div
-Element, wird eine HTTP-GET-Anfrage an den Uniform Resource Identifier (URI) /employees gesendet und der Inhalt des geklickten Elements wird durch das Ergebnis ersetzt (s. Abb. 1).
Um ein anderes HTTP-Verb für die Anfrage zu nutzen, stehen uns mit hx-post
, hx-put
, hx-patch
und hx-delete
noch vier weitere Attribute zur Verfügung, die sich ansonsten identisch zu hx-get
verhalten. Das Nachladen und Ersetzen geschieht dabei stets, nachdem ein Event auf dem Element ausgelöst wurde. Für input
, textarea
und select
wird dabei standardmäßig auf change
, bei einer form
auf submit
und für alle anderen Elemente auf click
reagiert.
Wollen wir diesen Auslöser ändern, können wir das Attribut hx-trigger
, siehe Listing 2, nutzen. In diesem Fall wird htmx nur dann eine Anfrage erzeugen, wenn gleichzeitig auf den Button und die Shift-Taste gedrückt wird, da wir neben der Definition, das click
-Event zu verarbeiten, einen Filter mit shiftKey
in eckigen Klammern angegeben haben. Zusätzlich haben wir mit once
und delay:1s
noch zwei Event Modifier angegeben. Diese führen hier dazu, dass die Aktion nur einmal ausgeführt wird und die Anfragen an den Server erst eine Sekunde nach dem Klick ausgeführt werden. Die Dokumentation von hx-trigger zeigt neben allen Events und Modifiers auch noch die Möglichkeit von Polling. Listing 2 enthält zudem noch das Attribut hx-target
. Durch dieses können wir angeben, welches Element auf unserer Seite durch das Ergebnis des HTTP-Aufrufs ersetzt werden soll, anstatt das Element zu ersetzen, welches die Anfrage ausgelöst hat.
Das vierte wichtige Attribut für Ajax ist hx-swap
. Dieses ermöglicht uns, eine Strategie für die Ersetzung anzugeben. Standardmäßig wird hierfür innerHTML
genutzt und somit der Inhalt des ausgewählten Elements ersetzt. Wollen wir aber beispielsweise eine auf der Seite vorhandene Liste um die Elemente aus der Antwort erweitern, können wir, siehe Listing 3, beforeend
nutzen. Es gibt für hx-swap
noch eine Reihe von weiteren Ersetzungsmöglichkeiten und Modifikationen. Beispielsweise kann mit dem Modifier scroll:top
dafür gesorgt werden, dass der Browser nach der Ersetzung zum neuen Inhalt scrollt und diesen am oberen Rand anzeigt.
Neben den eingebauten Möglichkeiten gibt es für das Ersetzen auch die Möglichkeit, mittels Erweiterungen sogenannte Morph Swaps durchzuführen. Hierbei wird der neue Inhalt nicht nur eingefügt, sondern es werden nur geänderte Elemente aus der Antwort hinzugefügt. Das hat vor allem den Vorteil, dass der Browser Dinge wie den aktuellen Fokus nicht verliert und dass die Operation unter Umständen performanter abläuft, da der Browser weniger Dinge erneut zeichnen muss.
Zusätzlich erlaubt es uns htmx noch, mittels Out of Band Swapping auch Elemente durch mehrere verschiedene Elemente zu ersetzen. Hierzu muss in der Antwort auf eine htmx-Anfrage an einem mit id
versehenen Element das Attribut hx-swap-oob
auf true
gesetzt werden. Anschließend wird htmx unabhängig vom angegebenen hx-target
das aktuelle Element mit derselben id
zusätzlich durch das aus der Antwort ersetzen.
Listing 3 zeigt, dass htmx zusätzlich zu dem angegebenen URI auch weitere Parameter mit an den Server schickt. Im Fall eines form
mit POST
werden dazu, wie bei einem regulären Absenden des Formulars, die input
-Elemente als Body übertragen. Mithilfe der beiden Attribute hx-include
und hx-params
lässt sich aber auch hier das Verhalten noch weiter anpassen.
Ohne weitere Anpassungen antwortet der Server auf die Anfragen stets mit der gesamten Seite, da er nicht wissen kann, dass es sich um eine htmx-Anfrage handelt. Häufig brauchen wir aber nur einen kleinen Ausschnitt aus der Antwort für unsere Ersetzung. Zur Lösung dieses Problems gibt es zwei Wege. Wir können entweder für das Element, welches die Anfrage auslöst, mittels hx-select
-Attribut einen CSS-Selektor angeben. Aus der Antwort wird dann vor der Ersetzung der Teil, der durch den Selektor beschrieben wurde, ausgeschnitten.
Die andere Möglichkeit besteht darin, bereits auf dem Server bei der Erzeugung des HTMLs darauf zu reagieren. Damit der Server dann doch erkennen kann, welche Anfragen von htmx kommen, wird dort eine Anzahl von HTTP-Headern spezifiziert, die bei Anfragen von htmx zusätzlich übermittelt werden. Der Server kann diese anschließend auswerten und dementsprechend andere Antworten liefern. Zudem steht auch eine Menge an HTTP-Headern für die Antwort zur Verfügung, mit denen der Server erweitertes Verhalten der Clientseite steuern kann.
Server-Sent Events und WebSockets
Neben HTTP-Anfragen via Ajax gibt es für htmx zwei Erweiterungen, um die beiden anderen Arten der Kommunikation, Server-Sent Events (SSE) und WebSockets, zwischen Browser und Server nutzen zu können. SSE ist dabei ein auf HTTP basierender Mechanismus, über den der Server dem Client Nachrichten schicken kann. Normalerweise benötigen wir hierzu eigenes JavaScript und können über eine EventSource
auf die Nachrichten reagieren. htmx bietet uns hierfür die Möglichkeit, auf eigenes JavaScript zu verzichten und wie für Ajax diese Funktionalität deklarativ zu beschreiben.
Hierzu binden wir, wie in Listing 4 zu sehen, die SSE-Erweiterung ein und nutzen das Attribut sse-connect
, um die Verbindung zum Server aufzubauen. Dabei wird, wie für Ajax, davon ausgegangen, dass der Server die Nachrichten als HTML schickt. Im Beispiel wird nun immer, wenn eine neue Nachricht vom Server an den Browser geschickt wird, diese an die vorhandene Liste angehängt, ohne dass eine Interaktion stattfinden muss. Dafür nutzen wir auch hier das bereits aus Ajax bekannte hx-swap
-Attribut.
Benötigen wir zusätzlich auch noch die Möglichkeit, Nachrichten vom Client an den Server zu senden, können WebSockets verwendet werden. Auch hier gibt es mit der WebSockets-Erweiterung die Möglichkeit, die bereits bekannten htmx-Mechanismen einzusetzen. Listing 5 zeigt den Einsatz dieser Erweiterung.
Neu ist hierbei vor allem das Attribut ws-send
, über das wir den Inhalt der Nachrichten bestimmen können. In diesem Fall sendet der Browser diese in Form von JSON über den WebSocket-Kanal an den Server. Neben der eigentlichen Nachricht werden, wie in Listing 6 zu sehen, auch die bereits in Ajax gesehenen Metadaten mit an den Server übermittelt, damit dieser bei Bedarf spezifisch antworten kann.
Spring- und Thymeleaf-Integration
Da htmx im Kern daraus besteht, HTML-Elemente um eigene Attribute zu erweitern, funktioniert es in Kombination mit den meisten serverseitigen Template-Engines ohne spezifische Integrationslogik. Bei der Nutzung von Thymeleaf gibt es jedoch die Besonderheit, dass Ausdrücke nur in bekannten Attributen ausgewertet werden. Wir könnten nun die hx-*
-Attribute mittels th:attr
erzeugen. Wie Listing 7 zeigt, ist der resultierende Code jedoch etwas schwer zu lesen. Deswegen gibt es in Kombinaten mit Spring im htmx-spring-boot-Projekt einen spezifischen Dialekt für htmx. Dieser erlaubt es uns, htmx-Attribute mittels hx:*
zu definieren. Diese Art, siehe Listing 8, ist deutlich lesbarer.
Neben diesem Dialekt enthält das Projekt auch noch eine Autokonfiguration für Spring Boot. Diese ermöglicht es uns, auf Serverseite spezifisch auf von htmx ausgelöste Anfragen zu antworten. Hierfür, siehe Listing 9, stehen uns sowohl Annotationen, wie @HxRequest
für das Mapping von Anfragen auf Methoden, als auch Klassen, wie HtmxResponse
, um komplexere Antworten inklusive der von htmx unterstützten Response-Header zu erzeugen, zur Verfügung. Es ist auch zu sehen, wie wir über mehrere Views und Thymeleaf-Selektoren wie employees/index :: #employees
eine Antwort mit Out of Band Swaps erzeugen können und dadurch mehrere Bereiche der Seite dynamisch ausgetauscht werden.
Die Readme des Projekts zeigt außerdem, wie die HtmxResonse
für die Behandlung von Fehlern innerhalb eines @ExceptionHandler
genutzt werden kann. Zudem gibt es auch ein wenig Unterstützung für Spring Security in Form eines HxRefreshHeaderAuthenticationEntryPoint
. Konfigurieren wir diesen, verhindern wir, dass im Falle eines Fehlers die Login-Seite anstelle des korrekten Inhalts für die Ersetzung verwendet wird.
Derzeit enthält die Bibliothek noch keinen Support für die beiden Erweiterungen um Server-Sent Events oder WebSockets. Alles in allem ist das Projekt aber zu empfehlen, denn es stellt doch eine Erleichterung dar.