htmx icon indicating copy to clipboard operation
htmx copied to clipboard

HTMX doesn't fire a `formdata` event on form submission

Open vrish88 opened this issue 1 year ago • 9 comments

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.

vrish88 avatar Mar 07 '23 23:03 vrish88

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]

shimikano avatar Apr 06 '23 13:04 shimikano

This also breaks HTML5 custom elements with form association

Hades32 avatar May 08 '23 11:05 Hades32

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.

bryanvaz avatar Aug 04 '23 22:08 bryanvaz

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 avatar Aug 16 '23 15:08 alexpetros

@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)

vrish88 avatar Aug 21 '23 14:08 vrish88

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 avatar Aug 21 '23 16:08 alexpetros

@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.

dvogel avatar Nov 15 '23 04:11 dvogel

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.

dvogel avatar Nov 17 '23 05:11 dvogel

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.

markc-versa avatar Feb 23 '24 22:02 markc-versa

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!

pelme avatar Jun 06 '24 09:06 pelme

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 ! 😄

Telroshan avatar Jun 07 '24 07:06 Telroshan