htmx trigger request via JS event
By Flavio Copes
Learn how to trigger an htmx request from a JavaScript event by dispatching a custom event on the body and listening for it with hx-trigger from:body.
Most htmx requests start from a natural element event: a click on a button, a submit on a form, a change on an input. Sometimes the thing that should start the request is not an element event at all, but something your own JavaScript knows about. htmx covers this case: hx-trigger can listen for any custom event.
I hit this with a kind of peculiar use case. I had a <select> element, and choosing one specific option had to perform a network request. Options do not fire their own events, so I could not hang the request off the option directly. The only signal available was the change event on the select, plus a value on the option to recognize which one was picked.
The pattern that solved it has two halves:
- your JavaScript dispatches a custom event on a well-known element, and the
bodyworks well - htmx listens for that event with
hx-trigger="eventname from:body"
In my project I dispatched a create-new-team event on the body, and used hx-trigger="create-new-team from:body" to fire the GET request. Here is the same structure with generic names. I am using Alpine.js for the listener because the project already used it, but it is not needed:
<select
x-on:change={`
if (event.target.value === 'myoption') {
document.querySelector('body').dispatchEvent(
new Event('myevent')
)
}
`}
>
<option>...</option>
<option>...</option>
<option
value="myoption"
hx-get=`/some-url`
hx-trigger="myevent from:body"
hx-target="#target">
Select this option
</option>
</select>
In vanilla JavaScript the dispatch is one line:
document.body.dispatchEvent(new Event('myevent'))
The from:body modifier is the part people miss. Without it, htmx waits for myevent on the option element itself, and that event never arrives because you dispatched it on the body. from:body tells htmx to attach its listener to the body instead, while the option keeps declaring the route and target.
That split is what I like about the pattern. The JavaScript only announces that something happened. The hx-get, hx-trigger, and hx-target attributes stay in the markup, so you can still read the HTML and see which URL gets called and where the response lands.
One thing to check when it does not work: dispatch the event on the same element you name in from:. Dispatching on document while listening with from:body fires nothing, and the symptom is an empty Network panel, not an error. Dispatch from the DevTools console to test the wiring without touching the select.
To put together combinations of hx-get, hx-trigger and hx-target like this without checking the docs every time, try my free htmx attribute builder.
Related posts about htmx: