In dit artikel beschrijft de auteur zijn aanpak voor het bouwen van interactieve formulieren met behulp van progressive enhancement en htmx. Centraal staat het beheren van 'tijdelijke status' via formuliervelden, queryparameters en paden om ervoor te zorgen dat de interface ook zonder JavaScript functioneert. De auteur adviseert om eerst een volledige HTML-basis te bouwen voordat htmx-functies zoals actieve zoekopdrachten en spinners worden toegevoegd. Daarnaast wordt ingegaan op het belang van toegankelijkheid (toetsenbordbediening) en de uitdagingen bij het bepalen van de juiste hx-target voor pagina-updates om data-inconsistenties te voorkomen.
Progressively Enhanced Formulieren Bouwen met htmx
De functie maakt gebruik van progressive enhancement (progressieve verbetering): het werkt zonder JavaScript, maar gebruikt HTMX om extra gemakken toe te voegen wanneer JS is ingeschakeld. In dit artikel loop ik door mijn aanpak en bespreek ik een aantal zaken om rekening mee te houden bij het bouwen van een gebruikersinterface in deze stijl.
Tijdelijke Status (Transient State)
Interfaces die zonder JS werken, verschillen fundamenteel van single page applications (SPA's): het bijwerken van de UI als reactie op gebruikersinteracties vereist vaak een volledige communicatieronde (roundtrip) naar de server (meestal gestart door <a> of <form> elementen).
Voordat de gebruiker zijn taak heeft voltooid en er iets is opgeslagen in de database, krijg je te maken met "tijdelijke status": invoer die de gebruiker heeft ingevoerd, die nog niet is opgeslagen, maar die wel bewaard moet blijven tijdens deze communicatieronde.
Een voorbeeld uit het hierboven beschreven formulier zou een nieuwe naam voor de bladwijzer zijn. Wanneer de gebruiker op de knop "hernoemen" klikt, wordt de naam naar de server gestuurd. Als er validatiefouten optreden, stuurt de server een respons terug waarin de fout wordt getoond. Deze respons bevat ook de ingevoerde waarde van de gebruiker, zodat deze niet opnieuw getypt hoeft te worden.
In een SPA zou de tijdelijke status in het JS-geheugen leven, en zou je waarschijnlijk een bibliotheek met een enorme hoeveelheid afhankelijkheden gebruiken om dit te beheren. Omdat we het zonder JS doen, moeten we klassieke HTML-functies gebruiken om deze status te beheren, elk met hun eigen voor- en nadelen. Nadenken over hoe deze tijdelijke status door het systeem stroomt, helpt bij het vertalen van complexe interacties naar HTML.
Formuliervelden
Dit wordt gebruikt voor het "hernoemen"-veld uit het voorbeeld en is de meest gebruikte methode. Wanneer het formulier wordt verzonden, ontvangt de server deze waarden en kan deze opnemen in de respons, zodat ze niet verdwijnen wanneer de gebruiker met de server communiceert. Het is eenvoudig en waarschijnlijk de techniek die je al gebruikt voor veel gebruikersinvoer.
Een nadeel van deze techniek is dat waarden verdwijnen als de gebruiker de pagina vernieuwt. Een ander nadeel is dat waarden alleen worden verzonden voor één formulier; als je meerdere formulieren op een enkele pagina hebt, zullen de waarden uit de andere formulieren verdwijnen bij het verzenden.
Het voorbeeldformulier voor bladwijzers is bijvoorbeeld gesplitst in twee formulieren: één voor het hernoemen van de bladwijzer en een ander voor het toewijzen van lijsten. Je kunt dit oplossen met HTMX, zodat gebruikers zonder JS een vloeiende degradatie (graceful degradation) ervaren.
Queryparameters en Paden
Door status in het pad of de queryparameters van de pagina te plaatsen, blijft deze behouden bij het vernieuwen van de pagina. Het overleeft ook het maken van bladwijzers of het delen als een link. Dit is een goed usecase voor bijvoorbeeld zoekopdrachten, zoals de invoer "zoek naar lijsten om te koppelen" in het formulier.
De meest gebruikte manieren om het pad en de queryparameters in te stellen zijn:
- De
href-attributen van links.
- De
action-attributen van formulieren.
- Het gebruik van een formulier met het attribuut
method="GET", wat alle invoerwaarden van dat formulier in de querystring plaatst.
Tot slot kun je de formaction-attributen van verzendknoppen gebruiken om het pad en de queryparameters voor een formulier te wijzigen, maar alleen wanneer een specifieke verzendknop wordt ingedrukt. Deze functie is zeer handig.
Detecteren Welke Knop is Ingedrukt
Iets wat me verraste bij het bouwen van het nieuwe formulier, was dat je geen controle hebt over wat de Enter-toets doet; deze verzendt simpelweg de eerste verzendknop van het formulier dat aan de invoer is gekoppeld.
Een toegankelijk formulier is een formulier dat met het toetsenbord bedienbaar is. Daarom heb ik het formulier voor het bewerken van bladwijzers gesplitst in tweeën, zodat het indrukken van Enter in het zoekveld voor lijsten niet per ongeluk de bladwijzer hernoemt.
Om te weten welke knop een gebruiker heeft ingedrukt bij het verzenden van een formulier, kun je:
- Het
formaction-attribuut van een knop gebruiken om het pad te wijzigen waarnaar het formulier wordt verzonden.
- De
name- en value-attributen van de knop instellen om een specifieke formulierwaarde naar de server te sturen wanneer die specifieke knop is ingedrukt.
Progressieve Verbetering (Progressive Enhancement)
Om gebruikers zonder JS te ondersteunen, vind ik het makkelijkst om een volledige functie eerst volledig zonder htmx te schrijven. Dit zorgt ervoor dat ik mezelf niet in een hoek schilder waar ik later alles moet herstructureren omdat ik erachter kom dat iets onmogelijk is zonder JS.
Zodra dat klaar is, voeg ik wat htmx toe om de ervaring te verbeteren voor gebruikers waarbij JS beschikbaar is. In mijn voorbeeld omvat dit:
- "Actief zoeken", waarbij gebruikers niet op Enter hoeven te drukken om zoekresultaten te zien.
- Een kleine spinner die verschijnt wanneer de pagina wacht op een respons van de server.
Het verrast me vaak hoe weinig htmx er daadwerkelijk nodig is wanneer ik deze aanpak volg. Bij het testen van wijzigingen schakel ik JS uit via de devtools van de browser, of ik voeg een hx-disable attribuut toe.
hx-target
Ik ben nog steeds op zoek naar de juiste manier om htmx swaps (vervangingen) te begrenzen. Ik heb vaak bugs gevonden omdat een te klein deel van de pagina werd bijgewerkt, waardoor andere delen van de pagina verouderde data bleven tonen.
Het experimenteren met out-of-band swaps om dit op te lossen voelt foutgevoelig en is lastig te onderhouden. Meestal eindig ik met het vervangen van de hele pagina, omdat dit de kleinste kans geeft om in de toekomst kapot te gaan. Een nadeel hiervan is dat gebruikersinvoer soms verloren gaat tijdens de swap, maar dat gebeurt zelden en is meestal acceptabel.
Verder Lezen
Als je wilt ontdekken of deze aanpak geschikt is voor je volgende project, of als je al html/htmx gebruikt en meer wilt leren, zijn dit enkele van mijn favoriete bronnen:
- De blog van Alexander Petros, "Unplanned Obsolescence". Vooral de artikelen "Less htmx is more" and "Who’s Afraid of a Hard Page Load?" zijn goede startpunten.
- "How I use HTMX with Go and Django + htmx patterns" voor patronen om htmx in het backend-gedeelte te gebruiken. Deze vertalen zich goed naar andere talen en ecosystemen.
- "If not React, then what?" kan je helpen beslissen of server-rendered HTML en/of htmx een goede match is voor jouw project.
- "Resilient Web Design" is een ongelooflijk goed geschreven boek over het ontwerpen van robuuste websites. Het gaat meer over design in holistische zin dan in visuele zin, en heeft veel van mijn technologische keuzes beïnvloed. Als je één ding van deze lijst leest, laat het dit zijn.
- "Plain Vanilla Web" is een referentie voor moderne mogelijkheden van de browser waar je wellicht niet van op de hoogte bent, gepresenteerd als een kookboek voor veelvoorkomende use-cases.
Progressively Enhanced Formulieren Bouwen met htmx
De functie maakt gebruik van progressive enhancement (progressieve verbetering): het werkt zonder JavaScript, maar gebruikt HTMX om extra gemakken toe te voegen wanneer JS is ingeschakeld. In dit artikel loop ik door mijn aanpak en bespreek ik een aantal zaken om rekening mee te houden bij het bouwen van een gebruikersinterface in deze stijl.
Tijdelijke Status (Transient State)
Interfaces die zonder JS werken, verschillen fundamenteel van single page applications (SPA's): het bijwerken van de UI als reactie op gebruikersinteracties vereist vaak een volledige communicatieronde (roundtrip) naar de server (meestal gestart door <a> of <form> elementen).
Voordat de gebruiker zijn taak heeft voltooid en er iets is opgeslagen in de database, krijg je te maken met "tijdelijke status": invoer die de gebruiker heeft ingevoerd, die nog niet is opgeslagen, maar die wel bewaard moet blijven tijdens deze communicatieronde.
Een voorbeeld uit het hierboven beschreven formulier zou een nieuwe naam voor de bladwijzer zijn. Wanneer de gebruiker op de knop "hernoemen" klikt, wordt de naam naar de server gestuurd. Als er validatiefouten optreden, stuurt de server een respons terug waarin de fout wordt getoond. Deze respons bevat ook de ingevoerde waarde van de gebruiker, zodat deze niet opnieuw getypt hoeft te worden.
In een SPA zou de tijdelijke status in het JS-geheugen leven, en zou je waarschijnlijk een bibliotheek met een enorme hoeveelheid afhankelijkheden gebruiken om dit te beheren. Omdat we het zonder JS doen, moeten we klassieke HTML-functies gebruiken om deze status te beheren, elk met hun eigen voor- en nadelen. Nadenken over hoe deze tijdelijke status door het systeem stroomt, helpt bij het vertalen van complexe interacties naar HTML.
Formuliervelden
Dit wordt gebruikt voor het "hernoemen"-veld uit het voorbeeld en is de meest gebruikte methode. Wanneer het formulier wordt verzonden, ontvangt de server deze waarden en kan deze opnemen in de respons, zodat ze niet verdwijnen wanneer de gebruiker met de server communiceert. Het is eenvoudig en waarschijnlijk de techniek die je al gebruikt voor veel gebruikersinvoer.
Een nadeel van deze techniek is dat waarden verdwijnen als de gebruiker de pagina vernieuwt. Een ander nadeel is dat waarden alleen worden verzonden voor één formulier; als je meerdere formulieren op een enkele pagina hebt, zullen de waarden uit de andere formulieren verdwijnen bij het verzenden.
Het voorbeeldformulier voor bladwijzers is bijvoorbeeld gesplitst in twee formulieren: één voor het hernoemen van de bladwijzer en een ander voor het toewijzen van lijsten. Je kunt dit oplossen met HTMX, zodat gebruikers zonder JS een vloeiende degradatie (graceful degradation) ervaren.
Queryparameters en Paden
Door status in het pad of de queryparameters van de pagina te plaatsen, blijft deze behouden bij het vernieuwen van de pagina. Het overleeft ook het maken van bladwijzers of het delen als een link. Dit is een goed usecase voor bijvoorbeeld zoekopdrachten, zoals de invoer "zoek naar lijsten om te koppelen" in het formulier.
De meest gebruikte manieren om het pad en de queryparameters in te stellen zijn:
- De
href-attributen van links.
- De
action-attributen van formulieren.
- Het gebruik van een formulier met het attribuut
method="GET", wat alle invoerwaarden van dat formulier in de querystring plaatst.
Tot slot kun je de formaction-attributen van verzendknoppen gebruiken om het pad en de queryparameters voor een formulier te wijzigen, maar alleen wanneer een specifieke verzendknop wordt ingedrukt. Deze functie is zeer handig.
Detecteren Welke Knop is Ingedrukt
Iets wat me verraste bij het bouwen van het nieuwe formulier, was dat je geen controle hebt over wat de Enter-toets doet; deze verzendt simpelweg de eerste verzendknop van het formulier dat aan de invoer is gekoppeld.
Een toegankelijk formulier is een formulier dat met het toetsenbord bedienbaar is. Daarom heb ik het formulier voor het bewerken van bladwijzers gesplitst in tweeën, zodat het indrukken van Enter in het zoekveld voor lijsten niet per ongeluk de bladwijzer hernoemt.
Om te weten welke knop een gebruiker heeft ingedrukt bij het verzenden van een formulier, kun je:
- Het
formaction-attribuut van een knop gebruiken om het pad te wijzigen waarnaar het formulier wordt verzonden.
- De
name- en value-attributen van de knop instellen om een specifieke formulierwaarde naar de server te sturen wanneer die specifieke knop is ingedrukt.
Progressieve Verbetering (Progressive Enhancement)
Om gebruikers zonder JS te ondersteunen, vind ik het makkelijkst om een volledige functie eerst volledig zonder htmx te schrijven. Dit zorgt ervoor dat ik mezelf niet in een hoek schilder waar ik later alles moet herstructureren omdat ik erachter kom dat iets onmogelijk is zonder JS.
Zodra dat klaar is, voeg ik wat htmx toe om de ervaring te verbeteren voor gebruikers waarbij JS beschikbaar is. In mijn voorbeeld omvat dit:
- "Actief zoeken", waarbij gebruikers niet op Enter hoeven te drukken om zoekresultaten te zien.
- Een kleine spinner die verschijnt wanneer de pagina wacht op een respons van de server.
Het verrast me vaak hoe weinig htmx er daadwerkelijk nodig is wanneer ik deze aanpak volg. Bij het testen van wijzigingen schakel ik JS uit via de devtools van de browser, of ik voeg een hx-disable attribuut toe.
hx-target
Ik ben nog steeds op zoek naar de juiste manier om htmx swaps (vervangingen) te begrenzen. Ik heb vaak bugs gevonden omdat een te klein deel van de pagina werd bijgewerkt, waardoor andere delen van de pagina verouderde data bleven tonen.
Het experimenteren met out-of-band swaps om dit op te lossen voelt foutgevoelig en is lastig te onderhouden. Meestal eindig ik met het vervangen van de hele pagina, omdat dit de kleinste kans geeft om in de toekomst kapot te gaan. Een nadeel hiervan is dat gebruikersinvoer soms verloren gaat tijdens de swap, maar dat gebeurt zelden en is meestal acceptabel.
Verder Lezen
Als je wilt ontdekken of deze aanpak geschikt is voor je volgende project, of als je al html/htmx gebruikt en meer wilt leren, zijn dit enkele van mijn favoriete bronnen:
- De blog van Alexander Petros, "Unplanned Obsolescence". Vooral de artikelen "Less htmx is more" and "Who’s Afraid of a Hard Page Load?" zijn goede startpunten.
- "How I use HTMX with Go and Django + htmx patterns" voor patronen om htmx in het backend-gedeelte te gebruiken. Deze vertalen zich goed naar andere talen en ecosystemen.
- "If not React, then what?" kan je helpen beslissen of server-rendered HTML en/of htmx een goede match is voor jouw project.
- "Resilient Web Design" is een ongelooflijk goed geschreven boek over het ontwerpen van robuuste websites. Het gaat meer over design in holistische zin dan in visuele zin, en heeft veel van mijn technologische keuzes beïnvloed. Als je één ding van deze lijst leest, laat het dit zijn.
- "Plain Vanilla Web" is een referentie voor moderne mogelijkheden van de browser waar je wellicht niet van op de hoogte bent, gepresenteerd als een kookboek voor veelvoorkomende use-cases.