htmx

HTMX – Was ist das und was kann das?

Mit htmx ist es möglich AJAX-Calls, CSS Transitions, WebSockets und Server Sent Events direkt im HTML anzusteuern. Dabei werden HTML-Attribute verwendet. htmx ist dabei Dependency frei und mit leichtgewichtig.

Auch, wenn htmx darauf ausgelegt ist, möglichst viel im HTML (via Attribute) abzubilden, bietet es zusätzlich einiges an JavaScript Methoden und Events, sodass man beides (Markup und Funktionalität) recht einfach koppeln kann. htmx bietet auch einige Extensions, die man zusätzlich einbinden kann. Es besteht ebenfalls die Möglichkeit eigene Extensions zu erstellen und entsprechend einzubinden.

Bevor im unteren Teil des Artikels weiter auf die verschiedenen Funktionalitäten eingegangen wird, soll htmx nachfolgend zunächst, anhand eines simplen, kleinen Beispiels, näher erklärt werden.

Infinite Scroll – Ein htmx Beispiel

Ein gutes und einfaches Beispiel für htmx ist ein Infinite Scroll, also das Laden von Daten, beim Scrollen. Für dieses Beispiel wird auf ein Backend verzichtet und lediglich mit Content aus verschiedenen HTML Files gearbeitet. Ausgangslage ist die folgende Ordnerstruktur:

htmx
  |-- infinite-scroll
  |  |-- contacts
  |  |  |-- page1.html
  |  |  |-- page2.html
  |  |  |-- page3.html
  |  |-- index.html

Die index.html ist die Seite, die im Frontend ausgegeben wird. Nachfolgend der Code der index.html, der anschließend näher erklärt wird:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>HTMX</title>
    <script src="https://unpkg.com/htmx.org@1.9.11"></script>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
      table {
        width: 800px;
        margin: 0 auto;
        margin-top: 85vh;
      }

      tr {
        height: 150px;
      }
    </style>
  </head>
  <body>
    <table>
      <tbody>
        <tr hx-get="/htmx/infinite-scroll/contacts/page2.html" hx-trigger="revealed" hx-swap="afterend">
          <td>Max Mustermann</td>
          <td>m.mustermann1@null.org</td>
          <td>1</td>
        </tr>
      </tbody>
    </table>
  </body>
</html>

Wie o.g. Code zeigt, wird in diesem Beispiel Version 1.9.11 (zum Zeitpunkt des Artikels die neueste Version) von htmx eingebunden.

Im body gibt es eine Tabelle mit Kontaktdaten. Das Beispiel ist darauf ausgelegt, dass beim Scrollen weitere Kontaktdaten geladen werden, bei denen lediglich die Ziffer in der Mailadresse und die Ziffer in der letzten Spalte sich erhöht (um zu verdeutlichen, dass hier tatsächlich weitere Tabellenreihen geladen und nicht die o.g. dupliziert wird).

Um ein Infinite Scroll zu ermöglichen, gibt es einige Styles im head der Seite, die bewirken, dass die Tabelle am unteren Ende des Viewports sitzt. Denn htmx ist in diesem so konfiguriert, dass die weiteren Daten geladen werden, wenn die „Trigger-Reihe“ mit den htmx Attributen im Viewport angelangt ist. Dies ist beim Laden der Seite direkt der Fall, sodass auch direkt die weiteren Daten aus page2.html geladen werden. Die Daten aus page3.html und page4.html werden dann beim weiteren runterscrollen nach und nach geladen und angezeigt.

htmx Attribute beginnen mit hx, gefolgt vom eigentlichen Attribut (z.B. der Request-Methode „get“ oder der Trigger-Eigenschaft („trigger“)). Wer bedenken hat, dass dies in Lighthouse-Bewertungen negativ bewertet wird, der kann auch vor hx ein „data“ verwenden, also z.B. data-hx-get. Auch diese Schreibweise wird von htmx unterstützt.

In der o.g. Tabelle existiert zunächst eine Reihe mit Daten. Diese besitzt u.a. das Attribut hx-get welches dafür sorgt, dass ein GET-Request an die, dem Attribut als Wert übergebene Seite, stattfindet. In diesem einfachen Beispiel wird also direkt eine HTML Seite (page2.html) angefragt. htmx bietet an dieser Stelle unterschiedliche Request-Methoden an, z.B. auch POST (hx-post) oder PUT (hx-put).

Mittels hx-trigger=“revealed“ wird htmx mitgeteilt, dass der GET-Request dann gestartet werden soll, wenn das Element (also die tr) in den Viewport kommt bzw. beim Laden bereits im Viewport ist.

hx-swap ist ein Attribut, dass in htmx sehr häufig zum Einsatz kommt. Es besagt, dass Content an dieser Stelle entsprechend verändert werden soll. D.h. mit hx-swap teilt man htmx mit, dass an dieser Stelle die Response auf den vorher getätigten Request eingefügt werden soll. Dabei gibt es unterschiedliche Eigenschaften, die man hier als Wert übergeben kann. In diesem Beispiel wird „afterend“ übergeben, was besagt, dass die Response nach dem Ende der tr eingefügt werden soll. In anderen Usecases können andere Eigenschaften verwendet werden, die bspw. das Element komplett ersetzten oder den Code der Response innerhalb des Elements platzieren.

Vergleicht man nun die drei verwendeten Attribute und ihre jeweiligen Werte mit einer selbst gebauten, vergleichbaren Funktionalität im JavaScript, erkennt man sehr schnell die Leichtgewichtigkeit von htmx. htmx ermöglicht es hier also mit einfachen, schnellen Mitteln Requests zu senden und den DOM mit den Responses zu manipulieren.

Laden weiterer Daten

Im vorherigen Abschnitt wurde bereits der Grundstein für das Laden der Daten aus der page2.html gelegt. Hier der Code dieser Datei:

<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann2@null.org</td>
  <td>2</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann3@null.org</td>
  <td>3</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann4@null.org</td>
  <td>4</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann5@null.org</td>
  <td>5</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann6@null.org</td>
  <td>6</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann7@null.org</td>
  <td>7</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann8@null.org</td>
  <td>8</td>
</tr>
<tr hx-get="/htmx/infinite-scroll/contacts/page3.html" hx-trigger="revealed" hx-swap="afterend">
  <td>Max Mustermann</td>
  <td>m.mustermann9@null.org</td>
  <td>9</td>
</tr>

Wie zu erkennen ist, beinhaltet die page2.html lediglich tr-Elemente mit den weiteren Daten. Die letzte Reihe besitzt dabei wieder dieselben Attribute wie die erste Tabellenreihe innerhalb der index.html. Der Unterschied hier ist allerdings, dass der GET-Request (hx-get) an die page3.html geht.

Das bedeutet, dass die Daten von page3.html angefragt und eingefügt werden, wenn die Tabellenreihe mit Max Mustermann, m.mustermann9@null.org, 9, in den Viewport gelangt.

page3.html ist identisch aufgebaut und funktioniert identisch zur page2.html, sie fragt allerdings in hx-get die Daten von page4.html an.

<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann10@null.org</td>
  <td>10</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann11@null.org</td>
  <td>11</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann12@null.org</td>
  <td>12</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann13@null.org</td>
  <td>13</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann14@null.org</td>
  <td>14</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann15@null.org</td>
  <td>15</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann16@null.org</td>
  <td>16</td>
</tr>
<tr hx-get="/htmx/infinite-scroll/contacts/page4.html" hx-trigger="revealed" hx-swap="afterend">
  <td>Max Mustermann</td>
  <td>m.mustermann17@null.org</td>
  <td>17</td>
</tr>

page4.html beinhaltet erneut tr-Elemente mit den letzten Daten:

<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann18@null.org</td>
  <td>18</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann19@null.org</td>
  <td>19</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann20@null.org</td>
  <td>20</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann21@null.org</td>
  <td>21</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann22@null.org</td>
  <td>22</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann23@null.org</td>
  <td>23</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann24@null.org</td>
  <td>24</td>
</tr>
<tr>
  <td>Max Mustermann</td>
  <td>m.mustermann25@null.org</td>
  <td>25</td>
</tr>

Innerhalb der page4.html gibt es keine hx-Attribute mehr. Diese werden hier nicht mehr benötigt, da es die letzte „Seite“ mit Tabellenreihen ist. Weitere Daten werden nicht mehr geladen und das Ende der Seite wurde erreicht.

Wie beschrieben, ist dies ein sehr einfaches und simples Beispiel für die Verwendung von htmx. Da htmx u.a. auch die Möglichkeit bietet, die Borwser History zu manipulieren, könnte man o.g. Beispiel dahingehend ergänzen, dass beim Laden jeder „Seite“ die History im Browser mit dem entsprechend angefragten Url manipuliert wird.

htmx bietet darüber hinaus noch weitaus mehr an Funktionalitäten. Einige davon sind im folgenden Abschnitt kurz angerissen, um zu verdeutlichen, dass Infinite Scroll nur ein simples Beispiel ist und man deutlich mehr Usecases für htmx findet.

Weitere Funktionen von htmx

AJAX Request-Methoden

htmx bietet die folgenden Methoden für AJAX-Requests:

  • GET (hx-get Attribut)
  • POST (hx-post Attribut)
  • PUT (hx-put Attribut)
  • PATCH (hx-patch Attribut)
  • DELETE (hx-delete Attribut)

Dadurch ermöglicht htmx den Einsatz der für den jeweiligen Usecase am besten geeigneten Methode.

Verschiedene Trigger

Über das hx-trigger Attribut können die verschiedensten Trigger für Requests eingbunden werden. Das können Mouse-Events, Keyboard-Events, Scroll-Events, Load-Events oder auch Intervalle sein (z.B. hx-trigger=“every 2s“). Weiter hat man die Möglichkeit Trigger zu kombinieren, mit einem Delay zu verzögern (z.B. hx-trigger=“keyup changed delay:500ms“) oder zu Filtern (hx-trigger=“click[ctrlKey]“).

Swap, Target & Synchronisation

Wie in o.g. Beispiel bereits beschrieben, wird mit hx-swap Content im DOM getauscht, eingefügt oder gelöscht. Dabei bietet hx-swap nicht nur die Möglichkeit anzugeben, was mit dem Content passieren soll (z.B. hx-swap=“innerHTML“ für das Einfügen innerhalb des Elements mit diesem Attribut) – dem Swap können auch weitere Optionen mitgegeben werden, die bspw. dafür sorgen, dass zum eingefügten Element gescrollt wird. Da htmx auch global konfiguriert werden kann, ist es u.a. auch möglich, die Transition von Swaps zu setzen, d.h. man kann angeben, mit welcher Transition z.B. neu eingefügte Elemente eingeblendet werden sollen.

Eine Alternative zum Swapping ist das Arbeiten mit hx-target. Diesem Attribut kann man einen CSS Selector mitgeben oder alternativ auch erweiterte Selectoren wie this, closest, next, previous, etc.. Mit hx-target teilt man htmx mit, dass die Daten aus der Reponse im Target-Element weiterverarbeitet (z.B. angezeigt) werden sollen. Das bietet sich dann an, wenn bspw. bedingt durch das Layout die Trigger- und Ziel-Elemente im Markup voneinander getrennt liegen.

Mit hx-sync lassen sich Requests und deren Trigger miteinander synchronisieren. Bei verschachtelten Requests bspw. kann man einen Request unterbinden, wenn gerade der andere Request stattfindet. Man z.B. ein Formular mit einem hx-post=“/save“ zum Speichern des Formulares, wenn der Submit-Button geklickt wird. Innerhalb des Formulars befindet sich ein input-Element mit einem hx-post=“/validate“, welches bei Änderungen eine Validierung vornimmt. Nun kann man durch das Hinzufügen von hx-sync=“closest form:abort“ auf dem input-Element htmx mitteilen, dass der Request zur Validierung nicht stattfinden soll, wenn der Request zum Speichern des Formulars (durch Klick auf den Submit-Button) getriggert wurde.

Boosting

Das Attribut hx-boost mit dem Wert „true“ bewirkt, dass alles a-Tags innerhalb des Elements, auf dem das Attribut sitzt, konvertiert werden zu AJAX Requests. Wenn man bspw. einen Link auf ein eine Blogseite im Content hat (href=“/blog“) und auf ein umliegendes Element hx-boost=“true“ anwendet, wird die Blogseite durch htmx via AJAX angefragt und das Ergebnis (die Blogseite) im body-Tag der aktuellen Seite eingefügt.

WebSockets & Server Sent Events

Mit dem hx-ws Attribut bietet htmx die Möglichkeit eine Verbindung zu einem WebSocket herzustellen. Dies bietet sich bspw. bei einem Chat an. Mit einfachen Mitteln kann man so die Usereingaben an das Chat-Backend senden und die Antworten in einem entsprechenden Bereich anzeigen.

Außerdem kann man mit hx-sse Server Sent Events verarbeiten. D.h. auch hier lässt sich mit einfachen Mitteln eine Verbindung zum Server aufbauen, die auf Events hört, die vom Server an den Browser gesendet werden. Ein gutes Beispiel dafür ist ein News-Feed Update.

Und noch mehr …

Darüber hinaus bietet htmx noch weitaus mehr an Möglichkeiten. Alle hier auch nur ansatzweise zu erwähnen, würde wohl den Rahmen sprengen. htmx bietet bspw. Attribute für Validierungen, unterstützt bei Animationen, bietet JavaScript Events und Methoden, mit denen bspw. die Daten einer Response „abgefangen“ und weiterverarbeitet werden können, es bietet 3rd Party Integrations uvm..

Fazit

Als Fazit bleibt festzuhalten, dass htmx sehr funktionsumfangreich und zugleich leichtgewichtig ist. Außerdem lässt sich htmx mit Extensions für verschiedene Usecases erweitern. Sicherlich lohnt es sich, sich näher mit htmx zu befassen und es für seine Projekte als Alternative oder auch Ergänzung zu den „großen Playern“ in diesem Bereich in Betracht zu ziehen. Hier gibt es weitere Informationen zu htmx: https://v1.htmx.org/

Übersicht von Webentwicklung