Netzkompetenz

Blog mit Snippets, Tipps und Tricks rund um Webprogrammierung der Taywa aus Zürich

Keep your head

Wenn man ein Django/Wagtail Webprojekt startet, steht man vor der Entscheidung, welche Art von Frontend-Stil man im Projekt verwenden möchte.

Es gibt den traditionellen Weg, der in Django standardmässig verwendet wird: Das Backend rendert die gesamte Seite und liefert sie im Request/Response-Zyklus an den Client aus. Dies ist der einfachste und geradlinigste Weg, eine Website zu erstellen.

Oder es gibt den „headless“ Weg mit JavaScript Focus beim Datenaustausch, bei dem man den API-Weg geht und JSON statt HTML zwischen Server und Client austauscht. Wagtail unterstützt dies auch.

Wir haben Projekte auch mit diesem Ansatz gebaut. In der Regel mit:

  • Nuxt frontend framework (VueJS)
  • GraphQL als API-Layer zwischen Client und Server / oder REST

Die Vorteile des API/JavaScript-Ansatzes sind:

  • kein Neuladen der gesamten Seite, schnellere Seitenübergänge, nur json payloads
  • in der Regel komponentenbasierte Entwicklung, ideal für grössere Projekte und Teams
  • gut für komplexe Interaktionen

Aber es gibt auch Nachteile:

  • im Fall von Vue.js braucht man für SEO zusätzlich Nuxt, damit es für bots/SEO eine HTML-Repräsentation der Seite gibt
  • man hat zwei Kontexte: Client und Server (SSR), dies macht die Dinge komplizierter
  • das Routing der Seiten fällt nun auch in die eigene Verantwortlichkeit, was zusätzlichen Code erfordert
  • zusätzliche grosse Frontend-Stacks mit all den Node.js-Bibliotheken erhöhen die Komplexität

Je nach Anforderungen des Projekts, Budget und Teamgrösse, macht das eine oder andere Sinn.

Mittelweg: HTML over the wire

Es gibt auch etwas dazwischen, bei dem man viele Vorteile des API/JavaScript-Weges erhält, aber ohne dessen Nachteile. Insbesondere für nicht zu komplexe Projekte.

Mit z.b. htmx können wir nur Teile der Seite laden, anstatt die gesamte Seite. Das bringt die meisten Vorteile der Benutzererfahrung des API/JavaScript-Ansatzes. Aber anstatt JSON zwischen Client und Server zu senden, senden wir HTML-Snippets (HTML over the wire). Das ist die „alte“ AJAX-Art, Dinge zu tun, die um 2005 herum populär war.

Weil wir HTML senden, können wir alle Werkzeuge nutzen, die Django und Wagtail von Haus aus anbieten, dies führt zu weniger Aufwand.

Es ist so nicht nötig, Routing und SSR für SEO selbst zu erstellen; alles ist bereits im Django/Wagtail HTML-Stack enthalten.

htmx Minitutorial

Es gibt eine Django-App für die Serverseite von htmx: `django-htmx`.

Installiere die Python-Bibliothek mit:

python -m pip install django-htmx

Füge sie zu deinen Apps in settings.py hinzu:

INSTALLED_APPS = [
    ...,
    "django_htmx",
    ...,
]

MIDDLEWARE = [
    ...,
    "django_htmx.middleware.HtmxMiddleware",
    ...,
]

Templates & Views

Templates

Füge Folgendes in den head-Bereich deines base.html-Templates ein:

<script src="{% static 'js/htmx.min.js' %}" defer></script>
{% django_htmx_script %}

Um htmx zu aktivieren, musst du nur Folgendes zu deinem body-Tag hinzufügen:

<body hx-boost="true" hx-target="#main" hx-swap="show:window:top">
  • hx-boost: aktiviert das Laden aller <a>-Links und <form>-Formulare über AJAX
  • hx-target: #main, dein Inhaltsziel innerhalb deines HTMLs, passe es an dein benutzerdefiniertes HTML an
  • hx-swap: show:window:top, nach dem HTML-Austausch nach oben scrolle

Du musst deine Templates in zwei Teile aufteilen: eines für die vollständige Seite, das base.html erweitert, und das andere, das nur das Inhaltsfragment enthält.

Eine Beispielimplementierung:

# product_detail.html
{% extends "base.html" %}
{% block content %}
    {% include 'myproject/product_detail_fragment.html' %}
{% endblock content %}

# myproject/product_detail_fragment.html
… dein gewohnter Django/Wagtail-Template-Code

Views

Du musst lediglich den Aufruf des Templates anpassen, um das vollständige Template oder das Fragment-Template zu erhalten.

class ProductView(DetailView):
    model = Product
    …
    def get_template_names(self):
        if self.request.htmx:
            return "libucore/product_detail_fragment.html"
        return "libucore/product_detail.html"

JavaScript

Auf der Frontend-Seite kannst du reines JavaScript oder auch ein leichtgewichtiges Framework verwenden (z. B. https://alpinejs.dev, https://stimulus.hotwired.dev).

Die Beispiele hier verwenden reines JavaScript. Wenn nach dem Laden von Inhalten etwas passieren soll, kannst du einen Event Listener verwenden:

document.body.addEventListener('htmx:load', function (e) {
// dein Code
});

htmx kümmert sich um die URL in der Adressleiste. Wenn du nur einen Teil der Seite erstellst und die Navigation beibehältst, musst du dich selbst um den Navigationsstatus kümmern. Wenn du Statistiken zur Seitennutzung möchtest, musst du Pageviews ebenfalls selbst auslösen. Beides wird mit dem htmx:pushedIntoHistory Event erreicht (weil hier die aktuelle URL / Position verfügbar ist).

document.body.addEventListener(
    'htmx:pushedIntoHistory', function(e) {

        // Navigation aktualisieren
        const segments = e.detail.path.split('/')
        segments.forEach(part => {
            document.querySelectorAll('#mainnav [data-slug="' + part + '"]').forEach(el => el.classList.add('active'));
        });

        // add your analytics pageview event here
    }
); 

Für Seitenübergangsanimationen kannst du die Events htmx:beforeRequest und htmx:afterSettle verwenden.

Eine mögliche Falle ist: Wenn du eine Seite mit vielen schweren Inhalten (Video/Audio/Iframe) hast, lädt/preloaded der Browser diese standardmässig. Wenn auf einer „normalen“ Seite ein Link angeklickt wird, stoppt der Browser das Laden/Preloaden dieser Ressourcen und beginnt mit dem Laden der neuen Seite. Nicht so bei htmx. Die XMLHttpRequest wird in eine Warteschlange gestellt und wartet darauf, dass der Rest geladen wird. Es gibt verschiedene Ansätze, dies zu lösen. In unserem Fall hatten wir ein Problem mit dem Audio-Tag, daher haben wir dem Tag einfach das Attribut preload=“none“ hinzugefügt.

Dokumente & Dateien

Ein Problem, auf das du stossen wirst, ist, dass verknüpfte Dokumente und Dateien ebenfalls als XMLHttpRequest-Anfragen geladen werden. Dies führt dazu, dass Binärdaten auf deiner Website angezeigt werden.
Um das zu beheben, musst du eine Umleitung hinzufügen, um htmx-boost für Dokumente zu deaktivieren.

Eine beispielhafte Nginx-Konfiguration dafür:

location /media/ {
    alias //media/;
    location ~* ^/media/documents/.*$ {
        add_header HX-Redirect $request_uri;
    }
}

Dies behebt den Download / die Anzeige im Browser von Dokumenten und Dateien (PDF, Excel, etc…).

Fazit

htmx bietet einen guten Mittelweg an Komplexität, der sich gut für Inhaltsseiten mit einfacher JavaScript-Interaktion eignet.

Für ein Live-Beispiel einer Wagtail + htmx Website haben wir z.B. www.buchbasel.ch mit diesem Ansatz erstellt.

Links

Autor: am
Themen: Allgemein

TrackBack URL

Hinterlasse einen Kommentar, wir freuen uns über jedes Feedback.