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.
- Webontwikkeling Een script koppelen met script src
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:
<script src="/static/js/htmx.min.js"></script>
En zet in de <body>, onder je kop, een knop met een lege <p> erbij:
<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
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')}")
- Regel 1-2:
Zet deze imports bovenaan je
main.py, bij de andere.datetimehoort bij Python zelf, enHTMLResponseheb je al sinds HTML tonen. - 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.
- Regel 6:
Het antwoord is één zin, geen hele pagina. Je geeft hier zelf een
HTMLResponsemet de tekst terug in plaats vanresponse_class=HTMLResponsebij de decorator te zetten; dat komt op hetzelfde neer.datetime.now()is de tijd van de server, enstrftimemaakt 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:
| Naam | Status | Type | Grootte |
|---|---|---|---|
| tijd | 200 | xhr | 151 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
- Download
htmx.min.jsnaarstatic/js/en pashome.htmlaan - Voeg het endpoint
/tijdtoe - Klik op de knop: de tijd verschijnt onder de knop, de pagina verspringt niet en de adresbalk verandert niet
- Klik nog een keer: de tijd is bijgewerkt
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.
Opdrachten
Opdracht 1: Predict - Twee keer klikken
Je klikt twee keer op de knop, met een paar seconden ertussen.
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
@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.")
<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
<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.