Stop nesting form elements in the checkout address step - #1099
Conversation
The address step wrapped the whole block in a <form>, and rendered full address <form> elements inside it. The HTML parser resolves that by dropping the inner start tag and letting the inner </form> close the outer form, so every control after the address form loses its form owner. Measured on 9.2.0: with two saved addresses, a separate invoice address and the delivery address open for editing, the Continue button, both invoice radios and the not-valid-addresses input all report form === null. Attach those controls to the step form through the form attribute instead, and leave the form element itself empty so the address forms stay siblings. Form validation now looks the submit button up in form.elements, which covers both DOM containment and the form attribute.
tblivet
left a comment
There was a problem hiding this comment.
Hi @boo-code, and thank you !
Two non-blocking remarks:
-
form.elementstrades a documented capability for one nothing uses. It only contains listed form controls, so an<a data-ps-action="form-validation-submit">or an<input type="image">is never found, wherequerySelectormatched any descendant andCONTEXT.mddocumentsdata-ps-actionas "used on buttons/links". Nothing breaks today (all 9 marked buttons are real<button>s sitting inside their form), but a marked link would silently stop being wired and the form would submit unvalidated, with no error.In exchange, the new capability finding a button attached through
form=isn't used anywhere: the buttons carrying the marker and the ones carryingform=are two disjoint sets. Marking the Continue button wouldn't help either, sincewas-validatedwould land on the empty#checkout-addresses-formand every Bootstrap rule is a descendant selector (.was-validated .form-control:invalid).So i would advice to revert to
querySelectorand keep the test fileinitFormValidationhad no coverage at all before, and cases 1 and 3 pass either way. -
form=on#not-valid-addressesdoes nothing the input has noname, so it's never submitted either way. Core only reads it with.val().
form.elements only lists form controls, so a link marked with data-ps-action="form-validation-submit" was no longer wired, and nothing used the form-attribute case it added. The test that pinned that case now pins the link instead. Also drop the form attribute from the not-valid-addresses input, which has no name and is only read by script.
|
@tblivet Both applied: |
|
🟢 Approved: the bug reproduces on the code this branched from, and is gone on this branch. AI-assisted QA: an agent drove a real browser through the steps below and drafted this comment. Worth a sanity check. Tested on PrestaShop 9.3.0, PHP 8.1.33, hummingbird 2.1.0, signed in with two saved addresses, "Billing address differs from shipping address" selected, then the delivery address opened for editing.
The screenshots show the same moment in both states: pressing Continue does nothing before, and reaches the Shipping Method step after. Also checked in both states and unchanged: the checkout, login and contact pages still render, the smoke pages still answer, and the front page, login and cart show no sideways scrolling at 375 and 768 wide. |


<form>around the whole block and then renders full address<form>elements inside it. HTML forbids nesting, and the parser resolves it by ignoring the inner start tag and letting the inner</form>close the OUTER form - so every control rendered after an address form loses its form owner. This attaches the step's own controls (address selector radios and the Continue button) to the step form through theformattribute and leaves that form element empty so the address forms are siblings rather than children.document.querySelector('button[name=confirm-addresses]').formisnull), so Continue does nothing. After, they belong to the step form.Measured on 9.2.0 with this theme active, counting
<form>tags in the address step of the served HTML andreading form ownership from the parsed DOM:
Form ownership in the two-address state, before -> after:
Tests
src/js/form-validation.test.ts- 3 cases forinitFormValidation, which had none: a submit button nested in the form, a link markedas the submit control, and a form with no submit button. The link case is the one a
form.elementslookup wouldmiss, and it fails if the lookup is switched to it. Full jest suite 42/42, eslint clean.
Why the
formattribute rather than moving the markupThe step form is needed for the address selectors and the Continue button; the address form is needed for
the address fields. They are never the same submission, but they interleave: a delivery selector can be
followed by an invoice form, so no arrangement of open/close tags puts every selector in the same form as
the Continue button. Explicit form ownership is the platform feature for exactly that, and it keeps source
order, markup and CSS unchanged.
Companion
PrestaShop/classic-themeneeds the same change (branchfix/checkout-address-form-nesting-36563, basedevelop), plus replacing the extra<form>itscheckout/_partials/address-form.tplopens around theContinue button with a
<div>- hummingbird already does that.