Search code examples
vue.jsalpine.jshtmx

change Alpine.js data from HTMX response


HTMX + AlpineJS + Tailwind. I think I'm overthinking something pretty easy...

My application has a dropdown list, upon selecting from the dropdown, HTMX queries the backend for some information about the chosen record, including whether it may be deleted. It's very important NOT to display a DELETE button until the backend has confirmed the chosen record is safe for deletion.

With Alpine.js, we define x-data="{showDelete : false}", and reset it to false whenever the selection from the primary dropdown is changed. The goal is to set it to TRUE when the backend confirms (via HTMX) that deletion is ok.

The bones of the page look like this:

<div x-data="{showDelete : false}"> <!-- goal is to change this -->
  <select x-model="currentRecord" id="recordSelector" name="recordId"
          x-on:deleteOK="showDelete = true" 
          x-on:change="showDelete = false"
          hx-get="/api/records/deletestatus" hx-trigger="click changed" hx-target="#deleteable" hx-swap="outerHTML"
          hx-get="/api/records/list" hx-trigger="load">
    <!-- htmx fills in all the record options below on page load -->
    <option value="-1" id="recordOption">Choose a record for more info...</option>
  </select>

  <!-- this div returned by HTMX when select option changes -->
  <div id="deleteable" :class="showDelete ? 'inline' : 'hidden'">
    <button type="button" id="deleteButton" hx-post="/api/records/78453/delete">Delete Record</button>
  </div>
</div>


<script>
    // this function does run, due to HTMX's HX-Trigger-After-Swap header
    // but cannot change the showDelete paramter above
    document.body.addEventListener('deleteConfirmed', function(evt) {
        console.log(evt);
        document.getElementById('recordSelector').dispatchEvent(new CustomEvent('deleteOK'));
        // Alpine.data('showDelete', () => {showDelete = true});
    })
</script>

The #deleteable div is the HTMX response, which also includes the header HX-Trigger-After-Swap set to deleteConfirmed. This header does trigger the function at the bottom (confirmed by the console output) but doesn't trigger the deleteOK function in the select element. I tried changing the data value directly with Alpine.data() but can't get that to work. I think using a separate function is probably overkill, it should be pretty easy to change one Alpine data point, right?

NOTE: why do we need showDelete at all, if HTMX is returning the div? Just return an empty div, right? The issue is when a deleteable record is returned, and the button is visible -- we still need the option to remove the button. For example, when a new record is selected, but before the new HTMX response is returned. We'd have an orphaned DELETE button which is now especially dangerous because a new record has been chosen which might not be safe to delete. (yes we validate all input on the server but still don't want a DELETE button out there).


Solution

  • Event names in Alpine need to be all lowercase https://alpinejs.dev/directives/on

    So for example:

    x-on:delete-ok="showDelete = true"
    

    Then trigger your event using new CustomEvent('delete-ok')

    Personally, I would avoid all this and just modify x-data directly on the server by changing showDelete to true when you update the HTML:

    <div x-data="{showDelete : true}">
    

    My understanding is that you are already updating this part of the code to include the button html, so you can also change the x-data value.