Core: Add support for HTML5 form attribute on elements outside form (#2521)

* Initial plan

* Core: Add support for form attribute on elements outside form

Co-authored-by: bytestream <1788397+bytestream@users.noreply.github.com>

* Core: Also update findByName to support form attribute

Co-authored-by: bytestream <1788397+bytestream@users.noreply.github.com>

* Build: Remove package-lock.json and add to gitignore

Co-authored-by: bytestream <1788397+bytestream@users.noreply.github.com>

* Tests: Add assertion to check form exists before validating

Co-authored-by: bytestream <1788397+bytestream@users.noreply.github.com>

* Revert: "Tests: Add assertion to check form exists before validating"

This reverts commit e3c28f7912.

* Revert: "Build: Remove package-lock.json and add to gitignore"

This reverts commit dff7ce8640.

* Fix: dont use self closing tag on custom-text element

* Chore: remove package-lock.json

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: bytestream <1788397+bytestream@users.noreply.github.com>
Co-authored-by: bytestream <kieran@supportpal.com>
This commit is contained in:
Copilot
2025-11-14 11:16:27 +00:00
committed by GitHub
parent 94418e4283
commit 4a25a8ff62
3 changed files with 73 additions and 8 deletions

View File

@@ -700,14 +700,28 @@ $.extend( $.validator, {
elements: function() {
var validator = this,
rulesCache = {},
selectors = [ "input", "select", "textarea", "[contenteditable]" ];
selectors = [ "input", "select", "textarea", "[contenteditable]" ],
formId = this.currentForm.id,
elements;
// Select all valid inputs inside the form (no submit or reset buttons)
return $( this.currentForm )
elements = $( this.currentForm )
.find( selectors.concat( this.settings.customElements ).join( ", " ) )
.not( ":submit, :reset, :image, :disabled" )
.not( this.settings.ignore )
.filter( function() {
.not( this.settings.ignore );
// If the form has an ID, also include elements outside the form that have
// a form attribute pointing to this form
if ( formId ) {
elements = elements.add(
$( selectors.concat( this.settings.customElements ).join( ", " ) )
.filter( "[form='" + validator.escapeCssMeta( formId ) + "']" )
.not( ":submit, :reset, :image, :disabled" )
.not( this.settings.ignore )
);
}
return elements.filter( function() {
var name = this.name || $( this ).attr( "name" ); // For contenteditable
var isContentEditable = typeof $( this ).attr( "contenteditable" ) !== "undefined" && $( this ).attr( "contenteditable" ) !== "false";
@@ -1133,7 +1147,20 @@ $.extend( $.validator, {
},
findByName: function( name ) {
return $( this.currentForm ).find( "[name='" + this.escapeCssMeta( name ) + "']" );
var formId = this.currentForm.id,
selector = "[name='" + this.escapeCssMeta( name ) + "']",
elements = $( this.currentForm ).find( selector );
// If the form has an ID, also include elements outside the form that have
// a form attribute pointing to this form
if ( formId ) {
elements = elements.add(
$( selector )
.filter( "[form='" + this.escapeCssMeta( formId ) + "']" )
);
}
return elements;
},
getLength: function( value, element ) {

View File

@@ -498,13 +498,21 @@
</form>
<form id="escapeHtmlForm1">
<input name="escapeHtmlForm1text" id="escapeHtmlForm1text" data-rule-required="true" />
</form>
</form>
<form id="escapeHtmlForm2">
<input name="escapeHtmlForm2text" id="escapeHtmlForm2text" data-rule-required="true" />
</form>
<form id="customElementsForm">
<custom-text name="customTextElement" id="customTextElement" />
</form>
<custom-text name="customTextElement" id="customTextElement"></custom-text>
</form>
<form id="testForm29">
<input type="text" name="f29input1" required>
</form>
<input type="text" name="f29input2" form="testForm29" required>
<form id="testForm30">
<input type="checkbox" name="f30checkbox1" value="1" required>
</form>
<input type="checkbox" name="f30checkbox2" value="1" form="testForm30" required>
</div>
</body>
</html>

View File

@@ -369,6 +369,36 @@ QUnit.test( "Ignore elements that have form attribute set to other forms", funct
$( "#testForm28-other" ).remove();
} );
QUnit.test( "Validate elements outside form with form attribute", function( assert ) {
assert.expect( 3 );
var form = $( "#testForm29" );
var v = form.validate();
// The form has one input inside and one input outside with form attribute
assert.equal( v.elements().length, 2, "Both elements should be included" );
// Validate the form - both fields are required and empty
var result = v.form();
assert.ok( !result, "Form validation should fail when both inputs are empty" );
assert.equal( v.numberOfInvalids(), 2, "Should have 2 invalid elements" );
} );
QUnit.test( "Validate checkboxes outside form with form attribute", function( assert ) {
assert.expect( 3 );
var form = $( "#testForm30" );
var v = form.validate();
// The form has one checkbox inside and one checkbox outside with form attribute
assert.equal( v.elements().length, 2, "Both checkboxes should be included" );
// Validate the form - both checkboxes are required and unchecked
var result = v.form();
assert.ok( !result, "Form validation should fail when both checkboxes are unchecked" );
assert.equal( v.numberOfInvalids(), 2, "Should have 2 invalid elements" );
} );
QUnit.test( "addMethod", function( assert ) {
assert.expect( 3 );
$.validator.addMethod( "hi", function( value ) {