Ga naar hoofdinhoud

Zonder herladen met htmx

Hier bouw je op verder: Een script koppelen met script src

Weet je dit niet meer zeker? Lees het terug voordat je verdergaat.

Tot nu toe betekent "iets aan de server vragen" dat de hele pagina opnieuw laadt: je klikt op een link of verstuurt een formulier, en alles verspringt. Hier vraagt een knop iets aan de server terwijl de pagina blijft staan. Daar gebruik je htmx voor: een bestand dat je HTML twee nieuwe attributen geeft. Je schrijft er zelf geen JavaScript voor.

Waar het bestand komt​

Maak een map static/js/ en zet daar htmx.min.js in (versie 2.0.10):

je-project/
├── main.py
├── static/
│ ├── js/
│ │ └── htmx.min.js
│ └── pages/
│ └── home.html
└── templates/

Het bestand komt van htmx.org. Staat het in je eigen project, dan werkt je site ook op een netwerk dat externe scripts blokkeert, zoals op school. Aan main.py hoef je niets te veranderen, want app.mount serveert de map static al.

Een knop en een lege alinea​

Open static/pages/home.html uit HTML in bestanden. Wat er al in staat, zoals je <title> en de <link> naar je stylesheet, blijft staan. Zet in de <head> deze regel erbij:

static/pages/home.htmlgaat zoals hij is naar de browser
<script src="/static/js/htmx.min.js"></script>

En zet in de <body>, onder je kop, een knop met een lege <p> erbij:

static/pages/home.htmlgaat zoals hij is naar de browser
<button hx-get="/tijd" hx-target="#uitkomst">Hoe laat is het?</button>
<p id="uitkomst"></p>

De script-tag geldt alleen voor de pagina waar hij in staat. Gebruik je later hx--attributen op een andere pagina, dan zet je hem daar ook in de <head>.

hx-get doet wat een link doet, een GET naar /tijd, met één verschil: htmx stuurt het verzoek, en de browser blijft op de pagina. hx-target zegt waar het antwoord van de server komt: in het element met id uitkomst.

Het endpoint​

main.pydraait op de server
from datetime import datetime
from fastapi.responses import HTMLResponse

@app.get("/tijd")
async def tijd():
return HTMLResponse(f"Het is nu {datetime.now().strftime('%H:%M:%S')}")
  1. Regel 1-2:

    Zet deze imports bovenaan je main.py, bij de andere. datetime hoort bij Python zelf, en HTMLResponse heb je al sinds HTML tonen.

  2. Regel 4-5:

    Een endpoint zoals je eerste, uit Je eerste endpoint. Voor je Python-code maakt het niet uit of htmx het verzoek stuurde of de browser zelf.

  3. Regel 6:

    Het antwoord is één zin, geen hele pagina. Je geeft hier zelf een HTMLResponse met de tekst terug in plaats van response_class=HTMLResponse bij de decorator te zetten; dat komt op hetzelfde neer. datetime.now() is de tijd van de server, en strftime maakt er "14:32:07" van. htmx zet de zin in #uitkomst.

Kijk wat er over de lijn gaat​

Open het tabblad Netwerk zoals in Kijken wat de browser doet en klik op de knop. Er komt één regel bij, en de pagina wordt niet opnieuw geladen:

Ontwikkelaarstools, tabblad Netwerk geopend
NaamStatusTypeGrootte
tijd200xhr151 B

Het type is xhr: een verzoek dat een script stuurde, geen pagina die de browser opvroeg. De grootte verschilt per computer; de zin zelf is maar 18 bytes, de rest zijn de headers die er altijd bij zitten. Klik de regel aan. Bij Reactie staat alleen je zin, geen <html> en geen <head>. In de terminal staat één regel met "GET /tijd HTTP/1.1" 200.

Testen​

  1. Download htmx.min.js naar static/js/ en pas home.html aan
  2. Voeg het endpoint /tijd toe
  3. Klik op de knop: de tijd verschijnt onder de knop, de pagina verspringt niet en de adresbalk verandert niet
  4. Klik nog een keer: de tijd is bijgewerkt
Gaat er iets mis?

Gebeurt er niets bij een klik? Dan is htmx niet geladen: kijk in Netwerk of htmx.min.js een 200 kreeg, en of de script-tag in de <head> staat. Verschijnt er een hele pagina onder de knop? Dan geeft je endpoint een pagina of een omleiding terug in plaats van een stukje. htmx volgt een omleiding net als de browser, en zet dan de hele pagina waar hij uitkomt in het doel. Meer oplossingen staan op Er gaat iets mis.

Controlevraag
Je klikt op de knop en ziet "Het is nu 14:32:07". Waar wordt die tijd opgezocht?
Kies wat jij denkt. Bij het goede antwoord zie je de uitleg.

Opdrachten​

Opdracht 1: Predict - Twee keer klikken​

Je klikt twee keer op de knop, met een paar seconden ertussen.

Voorspel
Hoeveel regels staan er in de terminal, en hoeveel tijden staan er op de pagina?
Kies wat jij denkt. Bij het goede antwoord zie je de uitleg.

Opdracht 2: Run​

Bouw het voorbeeld na. Kijk bij elke klik in de terminal en in Netwerk.

Opdracht 3: Investigate - Zonder htmx​

Haal de script-tag uit de <head> en klik op de knop.

Antwoord

Er gebeurt niets. hx-get en hx-target zijn voor de browser onbekende attributen, en een <button> buiten een formulier doet uit zichzelf niets. Het is htmx dat die attributen leest en het verzoek stuurt. Zonder het bestand staan er alleen woorden in je HTML.

Opdracht 4: Make - Hoeveel berichten?​

Zet een tweede knop op de pagina: "Hoeveel berichten staan er in het gastenboek?". Het antwoord komt uit de database.

Tip

Een nieuw endpoint met een nieuw pad, bijvoorbeeld /aantal-zin. /aantal heb je misschien al uit Elk bericht bewaren: een eigen sleutel, en dat geeft JSON terug. Binnen het with-blok geeft len(db) het aantal sleutels. De knop wijst met hx-get naar dat endpoint, en mag hetzelfde doel gebruiken.

Antwoord
main.pydraait op de server
@app.get("/aantal-zin")
async def aantal_zin():
with SqliteDict("gastenboek.db") as db:
aantal = len(db)
return HTMLResponse(f"Er staan {aantal} berichten in het gastenboek.")
static/pages/home.htmlgaat zoals hij is naar de browser
<button hx-get="/aantal-zin" hx-target="#uitkomst">Hoeveel berichten?</button>

Beide knoppen schrijven in dezelfde <p>, dus daar staat het antwoord van de laatste klik. Het nieuwe pad heb je nodig, want bestaan er twee endpoints met @app.get("/aantal"), dan gebruikt FastAPI zonder waarschuwing het eerste en staat er JSON onder je knop.

Opdracht 5: Make - Een klok​

Laat de tijd elke seconde vanzelf bijwerken, zonder knop.

Tip

Zet hx-get op de <p> zelf, en zeg met hx-trigger="every 1s" wanneer het verzoek moet gaan. Zonder hx-target komt het antwoord in het element zelf.

Antwoord
static/pages/home.htmlgaat zoals hij is naar de browser
<p hx-get="/tijd" hx-trigger="every 1s">Even geduld</p>

Kijk in de terminal: elke seconde een regel, ook als niemand kijkt. Voor een klok is dat de bedoeling, maar het laat zien dat vanzelf verversen de server werk kost. In de htmx-recepten staat wanneer dat de moeite waard is.