4

So I'm trying to display a feedback message saying "added" or "failed" when a form is submitted with HTMX to my Django backend.

Basically what I have right now is a form that performs an hx-post, and the reply is a div containing the updated information, which is swapped with the current contents of the div.

<div id="list">
    <!-- The stuff in here is loaded through another template, which is
         the result of a successful myApp:add operation. -->
</div>
<form hx-post="{% url 'myApp:add' eid=e.id %}" hx-swap="outerHTML" hx-target="#list">
    <!-- Some stuff in here -->
</form>
<div id="result">
    <!-- Here I should print a message saying correct or incorrect
         depending on the HTMX result (if the entry was added or there was
         any kind of error, even connection errors) -->
</div>

The thing is, in case of an error in the form or in the request itself, the list will remain the same, but I would like for it to print something like "error" in the result div. In case the new entry is added correctly I would like to print a "success" message in the result div.

Note that I can't return the result div as part of the hx-post response DOM since the connection might fail. So the failure message wouldn't be displayed.

I'm also using Alpine.js, if that's any help.

lpares12
  • 3,504
  • 4
  • 24
  • 45

1 Answers1

4

Here's one way to do it using Alpine if you can add a div around the existing code:

<div x-data="{ state: '' }" 
    @htmx:send-error="state = 'send error'" 
    @htmx:response-error="state = 'response error'" 
    @htmx:after-swap="state = 'success'"
>
    <div id="list"></div>

    <form hx-post="{% url 'myApp:add' eid=e.id %}" hx-swap="outerHTML" hx-target="#list">
        <button type="submit">Submit</button>
    </form>

    <div id="result">
        <span x-text="state"></span>
    </div>
</div>

The events are triggered on the target #list so they bubble up to the parent div. If you prefer to add the Alpine component to the #result div you can do it by listening for the events on the window:

<div id="result" x-data="{ state: '' }" 
    @htmx:send-error.window="state = 'send error'" 
    @htmx:response-error.window="state = 'response error'" 
    @htmx:after-swap.window="state = 'success'">
    <span x-text="state"></span>
</div>

If you use the second approach and you have multiple htmx requests happening on the same page you could also read the $event.detail.target.id value within the Alpine events to add some logic. In the above case the value should be list.

To deal with specific response codes from the server you can check the $event.detail.xhr.status value in events. For example:

<div id="result" x-data="{ state: '' }" 
    @htmx:send-error.window="state = 'send error'" 
    @htmx:response-error.window="state = $event.detail.xhr.status == 400 ? 'validation error' : 'response error'" 
    @htmx:after-swap.window="state = 'success'">
    <span x-text="state"></span>
</div>
Alejandro
  • 886
  • 5
  • 5
  • Thank you, will give it a try! Didn't know about these event handling. Just a couple of questions which might be very basic: If the validation of the form in the backend failed, what error code should I return? Would the div `#list` be updated or will it remain as it was? – lpares12 Jan 07 '22 at 12:54
  • 1
    According to the htmx documentation 4xx responses don't perform a swap, but you can also use the `htmx:beforeSwap` event to disable swapping or change the target https://htmx.org/docs/#modifying_swapping_behavior_with_events You can also use a `204` status code to ignore the content of the response https://htmx.org/docs/#response-headers – Alejandro Jan 07 '22 at 13:34
  • 1
    I updated my answer with an example using a 400 response status for failed validation https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.1 – Alejandro Jan 07 '22 at 13:55
  • For some reason `@htmx:after-swap` looks like is not being executed. Any idea what could it be? If I add an undefined variable as in `@htmx:after-swap="undef_variable = 'success'` the console does not complain, so it looks like this code is never executed, despite the DOM of the page changing. Also tried using `.window`, with the same result. Is this debuggable somehow? – lpares12 Jan 09 '22 at 14:08
  • I tried with some other events, and `@htmx:xhr:loadend="state = 'success'"` works. So for some reason the `after-swap` is not working correctly – lpares12 Jan 09 '22 at 14:53
  • For example, if I do this: `
    `` the text shown is always "Success" even though it should be executing the `afterSwap` (note that I changed it to `afterSwap` from `after-swap`, according to the docs https://htmx.org/events/#htmx:afterSwap).
    – lpares12 Jan 09 '22 at 15:27
  • The correct syntax for the event when using Alpine JS is `@htmx:after-swap` because HTML attributes are case-insensitive and htmx triggers the events with both the camelCase version and the hyphenated version https://htmx.org/docs/#event_naming. You can also use the examples on that documentation page to see different ways to check which events are being triggered. If the Alpine component is not wrapping the htmx code then you need to add the `window` modifier to the events. – Alejandro Jan 09 '22 at 20:17
  • I created a playground using PHP, but should be easy to follow and you can see it working there https://laravelplayground.com/#/snippets/b1965981-7c73-4af3-a89f-05bd614f52cf – Alejandro Jan 09 '22 at 20:26
  • You are right, maybe it was the cache or something that was causing my problems. Tried today and worked correctly – lpares12 Jan 11 '22 at 09:42