bigskysoftware / htmx

</> htmx - high power tools for HTML
https://htmx.org
Other
38.67k stars 1.32k forks source link

hx-trigger not working with dynamically loaded content. #2972

Closed JustSxm closed 1 month ago

JustSxm commented 1 month ago

Hello,

I've been using htmx in a C# project, and I've noticed that hx-trigger is not working. Ive tried whats mentionned in the documentation about dynamically loaded content (https://htmx.org/attributes/hx-trigger/#:~:text=The%20CSS%20selector%20is%20only%20evaluated%20once%20and%20is%20not%20re%2Devaluated%20when%20the%20page%20changes.%20If%20you%20need%20to%20detect%20dynamically%20added%20elements%20use%20a%20standard%20event%20filter%2C%20for%20example%20hx%2Dtrigger%3D%22click%5Bevent.target.matches(%27button%27)%5D%20from%3Abody%22%20which%20would%20catch%20click%20events%20from%20every%20button%20on%20the%20page.)

but it doesnt seem to work, here is a fully working attached demo, WebApplication1.zip

Or here are snippets of code to reproduce the error:

Home page:

<div id="demoContainer" hx-trigger="ItemSelected[event.target.matches('button')] from:[name='test']" hx-target="#demoContainer" hx-get="@Url.Action(nameof(HomeController.Privacy))">
    <vc:demo-component></vc:demo-component>
</div>

<script>
    document.addEventListener("DOMContentLoaded", () => {
        document.getElementById("btn").dispatchEvent(new CustomEvent("ItemSelected"));
    })
</script>

What happens here is as soon as the page is loaded, the event ItemSelected is dispatched. Hx-trigger works here and replace the demo-component inside the demoContainer with a new demo-component.

Demo Component:

HELLO

<button id="btn" name="test" style="width: 50px; height: 30px;">Click</button>

<script>
    document.getElementById("btn").addEventListener("click", () => {
        document.getElementById("btn").dispatchEvent(new CustomEvent("ItemSelected"));
    })
</script>

Once the demo-component was swapped with the same exact demo-component (so basically dynamically loaded) when Clicking the button does not trigger hx-trigger.

MichaelWest22 commented 1 month ago

You have from:[name='test'] and this may be your issue. in the example help you link to you note it uses from:body so that it will detect the event as it bubbles up to the body. css selectors in hx-trigger are only evaluated once on load as mentioned in the doc and this means that it will be always listening for events from the item found on first load with name test. If test is removed from the DOM and replaced with a new test then the event will still be listening from the old removed test and not the new one. This is why it uses from body which is not removed during partial page updates and most events bubble up to here.

JustSxm commented 1 month ago

Thanks for the response!

Is that bad for performance since it has to bubble?

MichaelWest22 commented 1 month ago

All events in the browser bubble like this up to all their parents unless you catch them explicitly on an element and manually stopPropogation(). So where you catch it won't really impact overall browser performance and the time to propagate up is very small indeed.

JustSxm commented 1 month ago

What happens if you dispatch them from window, is there any performance issue then? Theres no bubble up since its at the most top, if I understand correctly, but is there any reason why this would not be encouraged?

MichaelWest22 commented 1 month ago

I would not worry about the performance differences with event bubbling as this is all handled with native browser code and not JS so it will be so fast and optimized as not to have any impact. Note that the reason this trigger may need to use an event filter to find for in the example all button events is really for when you are trying to use from: a remote element. The point is if you wanted to listen for event ItemSelected on some remote button elsewhere on the page then you can't target it with from:#buttonid as the button may get replaced and this will orphan the event listener which is linked to the from: element. So to work around this you have to use something like from:body to listen to a higher parent element that you know won't change and then filter the trigger based on the event data to only trigger on button events. This is just an example though and often you don't even need the from like in your example if you place the trigger on a parent element of the button like in your setup then you don't even need all this. Instead you can just trigger with hx-trigger="ItemSelected" and any buttons wrapped inside this div that trigger this event will bubble up and trigger the request. Staying inside the standard browser event bubbling functionality as much as possible makes your app much simpler to develop and work with.

JustSxm commented 1 month ago

Right! Thank you.