htmx
htmx copied to clipboard
HTMX doesn't fire a `formdata` event on form submission
When HTMX submits a form, it does not fire the formdata
event on the form and this is a deviation from the default browser behavior. Other libraries like Shoelaces hook into the formdata
event to add custom element values to the form submission.
I've created a little example which exhibits the standard behavior and the behavior provided by HTMX. To reproduce the bug, submit both forms and then look at the inspector network tab. Compare the body of the requests /echo/html?with-htmx
and /echo/html?without-htmx
. You'll see the request body to ?with-htmx
is missing the lastName
attribute that is programmatically added by the formdata
event listener.
I ran into this too in conjunction with shoelace.
I was able to work around this by hooking into htmx:configRequest
and manually adding the data to the request for any form:
// hack to make htmx work with forms having shoelace components
document.body.addEventListener('htmx:configRequest', (event) => {
if (event.target.tagName === "FORM") {
const formData = new FormData(event.target); // this triggers a formdata event, which is handled by shoelace
// add the form data as request parameters
for (const pair of formData.entries()) {
const name = pair[0];
const value = pair[1];
const parameters = event.detail.parameters;
// for multivalued form fields, FormData.entries() may contain multiple entries with the same name
if (parameters[name] == null) {
parameters[name] = [value]; // single element array
} else if (Array.isArray(parameters[name]) && !parameters[name].includes(value)) {
parameters[name].push(value);
}
}
}
});
Of course, this is just a hack and not a stable solution.
[Edit: Adjustment to handle multivalued form fields]
This also breaks HTML5 custom elements with form association
This issue was also brought up on the shoelace side: https://github.com/shoelace-style/shoelace/discussions/866
So the original author of the issue built a fix via an htmx extention specifically for shoelace, though I think it exposes a larger question about whether HTMX by default should be coercing the values out of the forms a different way, or it should be triggering formdata
; or if this issue should be considered the realm of extensions, at which point the question is: "is there a general solution that can be embedded in a generic extension or does a specific extension need to be created for each component library in order to make it work with HTMX?".
Shoelace Extension for HTMX: https://github.com/benopotamus/htmx-ext-shoelace
I burned about a day trying to solve this problem and almost dumped HTMX because of it, so it might be an issue worth taking a look at.
Is there any reason why htmx shouldn't trigger the formdata
event before firing a form? That sounds like it would be backwards compatible to me.
@alexpetros It does not appear that there should be any reason for not submitting the formdata event (per the spec on constructing the form entry list)
The spec isn't really relevant as I understand it's a thing we could/should do, I'm just curious if there is anything about our existing implementation that makes it impossible.
So the original author of the issue built a fix via an htmx extention specifically for shoelace, though I think it exposes a larger question about whether HTMX by default should be coercing the values out of the forms a different way, or it should be triggering formdata
For people who have dug into this, why wouldn't htmx just fire that formdata
even when it sees a form? Is it more complicated than that for some reason?
@alexpetros I believe htmx firing the formdata
event would be to reverse the expected relationship. I've only briefly read through the htmx implementation but I believe a fix for this should involve rewriting getInputValues
to use FormData
. From the formdata
event MDN docs:
The formdata event fires after the entry list representing the form's data is constructed. This happens when the form is submitted, but can also be triggered by the invocation of a FormData() constructor.
I have not yet looked into the backward-compatibility issues that might arise from such a change.
Validation requires tracking processed input elements. Meanwhile the elements
method of the form does not return web-components meant to serve as form inputs. Therefore it seems an incomplete source of form inputs to consider. Meanwhile FormData
will capture data from web components serving as form controls but does not associate the data field names and values with the input element from which they were sourced. So it too seems like an incomplete source of form controls.
I've tried to obtain the best of both worlds by including all descendants of the form that have a name
attribute matching a name known to the FormData
object. This passes all tests and works for my use of Shoelace form controls. If someone were to include a non-control element with the same name
attribute as one of the form control elements, they could see some unexpected behavior from this heuristic but it seems to violate the principle of least surprise much less than the current behavior.
As an aside to the debate about what should go into htmx, either in core or extension, I'd like to suggest and action item here.
It would be nice to see an example workaround that uses htmx's hooks that will emit the formdata event, then take the post-munged form data and inject it back into htmx's ajax pipeline. It does not necessarily have to be in standard documentation. It could simply be a comment on this issue.
For anyone else finding this issue:
This is actually fixed in the htmx 2.0 beta (with 01b292ada48f31276642b587eb67128367349701). Just switching to 2.x works like a charm for me with Shoelace form controls!
We indeed switched to FormData internally for htmx 2, so you get the standard event fired when building one over a form element. We also introduced a FormData proxy object for retro-compatibility reason, and proxy objects aren't supported by IE11, which htmx 1 will always support.
As htmx 2 is now available in beta and on the dev
branch, I think we can close this issue now ; if you need form data, switch to htmx2 ! 😄