bigskysoftware / htmx

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

hx-trigger~~'s consume modifier~~ has surprising behaviour with forms #2919

Open robryk opened 3 weeks ago

robryk commented 3 weeks ago

E: see below for details, consume is completely orthogonal to the issue (it's rather about hx-trigger's presence alone affecting the two forms differently)

hx-trigger="click consume" seems to prevent the button from triggering its form when its enclosed in that form, but not when it references its form by id. In the following example, "click me 1" does nothing while "click me 2" causes an attempt at form submission:

<html>
<head>
<script src="https://unpkg.com/htmx.org@2.0.2" integrity="sha384-Y7hw+L/jvKeWIRRkqWYfPcvVxHzVzn5REgzbawhxAuQGwX1XWe70vji+VSeHOThJ" crossorigin="anonymous"></script>
</head>
<body>
<form action="http://foo" method="POST"><button hx-trigger="click consume">click me 1</button></form>
<form id="bar" action="http://bar" method="POST"></form><button form="bar" hx-trigger="click consume">click me 2</button>
</body>
</html>

The documentation doesn't explicitly say that such form submissions will be prevented (it only mentions other htmx requests; admittedly it calls out htmx requests on parents), but I'd expect that not to differ between these two setups, given that they're used interchangeably (and ttbomk do not differ in behaviour in HTML at all).

MichaelWest22 commented 3 weeks ago

Yeah It is interesting. documentation points out or on elements listening on parents and forms have a browser built in listener on button clicks.

in the first form you have there the button click event is consumed so it will not bubble up to the form and trigger the form submit as this is how buttons in forms trigger things. And because the button has no other hx-attributes htmx has no action to complete itself in this case so does nothing.

In the second situation you have used the form=id attribute on the button which the browser uses to retarget the submit trigger from the button click to the form located somewhere else on the page. So you would expect the htmx consume here would prevent the button click bubbling up to its parent form (but there is none) in this case and its probably working as expected. But the default browser behavior of redirecting events to a remote form will still be in place as htmx doesn't alter default browser functions by design.

robryk commented 3 weeks ago

If you read the documentation very literally, it only claims that hx-trigger's consume modifier affects other htmx requests, which should not include vanilla HTML form submission. So, regardless of what's the intended behaviour wrt forms here, the documentation doesn't describe the current behaviour accurately.

Telroshan commented 3 weeks ago

Yeah after all it all comes down to this about consume: https://github.com/bigskysoftware/htmx/blob/326ff3b296cf5e3eb0ec7799a9f607818776628c/src/htmx.js#L2439-L2441

The docs could probably be improved to specify that consume will simply call stopPropagation

Though, it should be noted here that it's not consume that prevents your form from submitting, as the event.stopPropagation doc mentions

The stopPropagation() method of the Event interface prevents further propagation of the current event in the capturing and bubbling phases. It does not, however, prevent any default behaviors from occurring; for instance, clicks on links are still processed. If you want to stop those behaviors, see the preventDefault() method.

Using your example above, you can see on this JSFiddle that even without the consume keyword, the form is still prevented from submitting.

The preventDefault call comes from here: https://github.com/bigskysoftware/htmx/blob/b71af75f1af7d5d3ae562686625fe131f6923e5a/src/htmx.js#L2396-L2398 shouldCancel itself, will return true if the element is a submit button inside a form and the event is a click or a submit https://github.com/bigskysoftware/htmx/blob/b71af75f1af7d5d3ae562686625fe131f6923e5a/src/htmx.js#L2320-L2322

The idea being that if a button has a hx-trigger attribute defined, it's normally going to make a request (you didn't put any hx-get/post/whatever in your example on the buttons themselves but I assume there would be one of those in a real situation), thus we want to prevent the enclosing form's default submit. You can see the problematic behavior on this JSFiddle, almost the same example as before, I just added hx-post attributes to the button. See how the second one does 2 requests ; the htmx one + the default submission. We probably don't ever want that to happen.

So I would say this is a bug, we actually didn't have any support for the form attribute a year back (see #1559 #1815), it's very likely to have simply been overlooked (by none other than myself, sigh) at that time. Maybe it coud simply be fixed by replacing the closest form check in shouldCancel by this one: https://github.com/bigskysoftware/htmx/blob/b71af75f1af7d5d3ae562686625fe131f6923e5a/src/htmx.js#L2715

So, to sum it up:

If you'd like to look into it, feel free to investigate and submit a bugfix PR! And add test cases of course 😁