htmx icon indicating copy to clipboard operation
htmx copied to clipboard

hx-boost & support for form* (formaction, formmethod etc) attributes

Open edgar-tamm-uptime opened this issue 4 years ago • 2 comments

Potentially again a very esoteric use case, but would be nice if htmx natively supported submit button form overriding attributes: formaction, formmethod. At least these I think are handled by htmx on regular forms, the other formenctype, formnovalidate, formtarget I don't think are handled or even need to be (other than maybe enctype multipart/form-data).

In my case this is needed for bulk action use cases. E.g. we have a data table, which allows selecting rows & then doing different bulk actions on the selected rows (e.g. bulk delete, bulk activate etc). Something like this:

<form>
   <button type="submit" formaction="BulkMethod1">Do bulk action 1</button>
   <button type="submit" formaction="BulkMethod2">Do bulk action 2</button>
   <table>
    <thead>
      <tr>
        <th><input type="checkbox" title="Select/Deselect all"/></th>
        ...
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><input type="checkbox" name="ids" value="Id1" /></td>
        ...
      </tr>
      <tr>
        <td><input type="checkbox" name="ids" value="Id2" /></td>
        ...
      </tr>
    </tbody>
  </table>
</form>

At least for now it is possible to manually handle this case:

htmx.on('htmx:configRequest', function (e) {
  const element = e.detail.elt;
  if (element && element.nodeName === 'FORM') {
    let submitter = e.detail.triggeringEvent.submitter;
    if (submitter) {
     // Element also has "formAction" field, but if "formaction" attribute is not filled then it has a default value.
     // So we specifically need to check for the existence of the attribute
     const form_action = submitter.attributes['formaction'];
      if (form_action && form_action.value) {
        e.detail.path = form_action;
      }
      
      const form_method = submitter.attributes['formmethod']
      if (form_method && form_method.value) {
        e.detail.verb = form_method.value.toLowerCase();
      }
    }
  }
});

edgar-tamm-uptime avatar Oct 18 '21 07:10 edgar-tamm-uptime

This looks pretty cool. Since you're hooking into htmx:configRequest, you could probably wrap this up as an extension that could be activated/deactivated as needed. I think that's the current direction that the project is taking -- using extensions as something of a playground to experiment with new features, then roll the most ubiquitous ones into the core library. Care to write up docs and a PR?

benpate avatar Oct 26 '21 04:10 benpate

The suggested workaround is not working for me in the case of formnovalidate because configRequest happens after the validation fails.

cachitas avatar Dec 18 '21 19:12 cachitas

As a first time HTMX user, I am currently primarily relying on hx-boost=true and the lack of support for formaction and formmethod created some confusion. It actually makes things non-functional which isn't great. The code snippet above works like a charm though, so thanks for sharing that!

thomaseizinger avatar Dec 06 '22 01:12 thomaseizinger

Actually, it doesn't work. e.detail.triggeringEvent.submitter is not defined for me.

thomaseizinger avatar Dec 06 '22 02:12 thomaseizinger

The submitter event exists on the FormSubmit event if I add an event handler myself. It seems htmx is not passing on all attributes of the event to configRequest?

thomaseizinger avatar Dec 06 '22 03:12 thomaseizinger

I found the bug and submitted a patch here: https://github.com/bigskysoftware/htmx/pull/1156

thomaseizinger avatar Dec 06 '22 04:12 thomaseizinger

I ran into this today and had to make a small tweak to the workaround above which might be useful to others; when setting the verb we also need to set useUrlParams so that htmx knows whether to send the form values by body or query string:

...
if (form_method && form_method.value) {
  e.detail.verb = form_method.value.toLowerCase();
  e.detail.useUrlParams = htmx.config.methodsThatUseUrlParams.indexOf(e.detail.verb) >= 0;
}
...

iantonge avatar Sep 03 '23 17:09 iantonge

Another thing that needs to be dealt with is setting the correct content-type.

if (formMethod === 'post') {
    event.detail.headers['Content-Type'] = 'application/x-www-form-urlencoded';
}

thomaseizinger avatar Sep 04 '23 04:09 thomaseizinger

Should probably note that my original workaround was made for htmx version 1.7.0 (and inside an asp.net core 6 app) and at least there it doesn't seem that the above two additions are needed.

edgar-tamm-uptime avatar Sep 06 '23 05:09 edgar-tamm-uptime

This can probably be closed.

jacob-ebey avatar Sep 24 '23 18:09 jacob-ebey

This can probably be closed.

Thanks for landing support for this in main library!

thomaseizinger avatar Sep 24 '23 22:09 thomaseizinger