I was going to say that I was surprised htmx wasn't mentioned in the article. It's backend-agnostic and extremely easy to use. Drop in Alpine.js and I think you have a really powerful setup without writing any JS. I've been using this with Go[0] and enjoying it.
So, I have only been working in htmx for about a week on a multi step checkout app, and while it's been mostly awesome, I think I ran into the Achilles heel of htmx, form state and the back button.
In short of you enter data into a form then htmx push navigate away, when you click the browser back button you get the dom as it was originally delivered from the server, without any data the user might have entered into text boxes. This is a show stopper problem and been working on work arounds, not sure any is good. Basically we have resorted to plain old full page reloads with client side redirect to resolve this.
The thing about HTMX is that the mental model about state should tend towards "the server is aware of everything". With the added benefit that you can achieve better RESTful URLs that respect the navigation actions. Without having tried this (but having used HTMX quite a lot), I would try the following (let me know if I didn't understand the problem correctly):
Checkout has the following URLs, one for each step:
1) Making sure the server knows how to render all those pages independently (like if the user does a hard-refresh or if they open that URL directly, without navigating to it through a link). Note: I believe this should be the default in any website if you respect the concept of a URL (whether client or server-side rendered).
2) If there's a form input at each step, the server needs to store that info and be aware that the user has an incomplete checkout.
Now the user is at step=2 and presses the back button, or clicks on the step=1 link. In that case, the server should know the information stored in the point no. 2 above, and return an HTML form with the data pre-filled based on the last state. E.g:
1. You need different URLs for each step.
2. Each URL should work both with HTMX (maybe using hx-push-url or the HX-Push header) and *without*. That is, any navigation to that page should also render the same HTML.
3. The server needs to be aware of the state. When a user requests page.com/checkout?step=2, the server should know if the HTML form requires pre-filled values.
This increases a bit the complexity on the server, but I believe it reduces the client-side complexity a lot more.
> 2) If there's a form input at each step, the server needs to store that info and be aware that the user has an incomplete checkout. Now the user is at step=2 and presses the back button, or clicks on the step=1 link. In that case, the server should know the information stored in the point no. 2 above, and return an HTML form with the data pre-filled based on the last state.
The problem is that when you press back button in the browser, it doesn't make another request to the server. It reloads the page from browser memory. A link back can be made to work, but that's not what I was speaking to.
I tested the most trivial case, when you return a response, and push navigate to next step, then press browser back button, form state is not retained. Both chrome and firefox do not restore the form state on navigation back after a url push navigation.
I don't think that without some client side JS that saves/reloads prior form state this is a solvable problem because of browser behavior. Either that, or perhaps use different divs for each step then have js hide/show them which is what I will try next but involves writing js to do so.
EDIT: tried multiple divs on same page, same behavior so that will not work.
It's pretty clear that htmx hasn't considered back button much at all, it also clobbers the page titles in history as well (https://github.com/bigskysoftware/htmx/issues/746), but that's a fixable problem.
What about using hx-trigger="load" for the form? Maybe that makes the browser reload it even after hitting the back button? (Sorry I can't check this right now, just a random idea).
Edit: I quickly tried it (mixing hx-from="#some-other-element" and hx-trigger="revealed", and it seems to be doing the request, but I haven't looked a lot.
Ok, so turns out default chrome/firefox on windows/android were not issuing the history refresh request on back button click. Turns out you need to add some additional config to make that happen. It's now working, and it's excellent.
Set htmx to not cache prior pages by setting `htmx.config.historyCacheSize = 0` in window.onload.
Also set the http caching header `cache-control: no-cache`
One approach you can try is using the localStoeage API to temporarily store the form data of the user. I have done something similar in the past. You can use a light library like Alpine JS to make this work nicely. When the form renders, check if there is any temporary form data in localStorage, if so initialize the form with that. When the user saves the form remember to clear localStorage. This works nicely because even if the user closes the browser or something by mistake it is all there, and you don't need to keep temporary data around in your database.
> What is old is new again I suppose: we used to do this with PHP and jQuery once upon a time too — though LiveView and similar are far nicer of course.
I used to do the same with PHP + Prototype.js back in 2006-2007 before jQuery existed, including pretty weird hacks for non-AJAX supported browsers (using JS to append <script></script> from server-side to the DOM).
PHP, Django, Rails, whatever generates HTML, basically. I also slightly prefer Unpoly.com, because it takes whole pages instead of fragments. This means that if JS is disabled the site just keeps working, though with reloads.
You can achieve full support with JS disabled using HTMX as well. It takes a little more work but HTMX provides headers[0] which you can evaluate on the backend to determine if you should return a partial or not. If JS is disabled, the HTMX headers will be missing and you know it's not an HTMX request.
I think my next site is going to be php+Laravel. I had a lot of fun doing web dev in php. Nothing has matched that since. I never tried rails though. If I see another bumblefucked SPA I'm gonna cry.
If someone took blazor away from me tomorrow, I'd probably consider PHP for a while. The most important part of the programming model is very similar in my experience. I could take a Razor component and convert it directly to a PHP partial pretty quickly.
Tiny amount of glue HTML attributes, a heap of partials that are your PHP files and you’re good to go.
What is old is new again I suppose: we used to do this with PHP and jQuery once upon a time too — though LiveView and similar are far nicer of course.
I’ve been working on some personal tools with nothing more than Deno and htmx. Works quite well for my needs!
[0] https://htmx.org/