HTMX: Request not firing when hx attributes are added dynamically from JavaScript function
Asked Answered
M

2

8

I have a form with multiple select elements where the available choices of some are dependent on the chosen value in others. I'm using HTMX to achieve this. For one element, I also want to dynamically set the hx-post and hx-target attributes depending on what value is chosen in the previous element.

I'm able to set the attributes succesfully with JavaScript, but then when the element changes the request never actually fires.

HTML

<form>
    <select id="dropdown_A" onchange="dropdownAChanged(this.value);" hx-post="/choices/dropdown_B/" hx-target="#dropdown_B" required>
        <option value="">--</option>
        <option value="1">1</option>
        <option value="2">2</option>
        <option value="3">3</option>
    </select>

    <select id="dropdown_B" required>
        <option value="">--</option>
    </select>

    <select id="dropdown_C" class="inactive">
        <option value="">--</option>
    </select>

    <select id="dropdown_D" class="inactive">
        <option value="">--</option>
    </select>
</form>

JavaScript

function dropdownAChanged(value) {
    const b = document.querySelector('#dropdown_B');
    const c = document.querySelector('#dropdown_C');
    const d = document.querySelector('#dropdown_D');

    switch (value) {
        case '1':
            makeInactive([c, d]);
            break;

        case '2':
            makeActive(c);
            makeInactive(d);
            b.setAttribute('hx-post', '/choices/dropdown_C/');
            b.setAttribute('hx-target', '#dropdown_C');
            break;

        case '3':
            makeActive(d);
            makeInactive(c);
            b.setAttribute('hx-post', '/choices/dropdown_D/');
            b.setAttribute('hx-target', '#dropdown_D');
    }
}

Basically there are four dropdowns (A, B, C, and D). By default, A (with choices 1, 2, and 3) and B (empty) are shown, and C and D are hidden. If value 1 is chosen in A, B gets populated with choices while C and D remain hidden. But if 2 or 3 is chosen in A, either C or D will become visible, and will then need to be able to have its own choices populated once a selection is made in B. So B might need hx attributes for updating C, or D, or neither, all depending on the value of A.

Since I'm already using a JavaScript function when A changes to handle activating/inactiving C and D, I figured I could just set the hx-post and hx-target attributes there as well. It works in so far as the attributes do get succesfully updated on B when A changes, but then when a selection is made in B, the expected request never actually fires.

I'm guessing it's because HTMX is adding the necessary EventListeners on load, and then even though hx attributes are getting added after the fact, the necessary EventListeners are never getting created/updated. But I'm not totally sure if that's the case or what, if anything, can be done about it.

Mcrae answered 9/9, 2022 at 7:1 Comment(0)
A
17

Yes, HTMX creates the event listeners on page load and after a swapping event. If you manipulate the DOM with 3rd party JS, you need to trigger HTMX to reprocess the respective section looking for new/updated HTMX-specific attributes. For this purpose it provides the htmx.process(element) JS API, where you need to pass the modified element as the argument. So in your case putting htmx.process(b) after the switch block in the dropdownAChanged function is enough to fix the issue. Or you can give and ID to the form and put htmx.process(htmx.find('#myform')) at the end of the function, to make HTMX reprocess the whole form.

Antonioantonius answered 9/9, 2022 at 8:59 Comment(1)
good to know!! you saved my day!!Bales
O
4

Using the MutationObserver DOM API you can run this code once on page load and it'll work for all elements subsequently added to the DOM excluding elements added by htmx.

const observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    mutation.addedNodes.forEach((node) => {
      if (node.nodeType === 1 && !node["htmx-internal-data"]) {
        htmx.process(node)
      }
    })
  })
})
observer.observe(document, {childList: true, subtree: true})
Oxtail answered 22/5 at 15:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.