Skip to content

Commit 7d0e6c2

Browse files
committed
feat(pat-validation): Support form elements outside the form.
1 parent 6894ff9 commit 7d0e6c2

File tree

3 files changed

+60
-11
lines changed

3 files changed

+60
-11
lines changed

src/pat/validation/documentation.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,22 @@ ValidationPattern.prototype.error_template = (message) =>
109109
`<em class="invalid-feedback">${message}</em>`;
110110
```
111111

112+
113+
### Form elements outside the form
114+
115+
Input elements outside of form elements are fully supported.
116+
pat-validation can handle structures like these:
117+
118+
```html
119+
<input name="outside" form="myform" required>
120+
<form id="myform">
121+
</form>
122+
<button form="myform">submit</button>
123+
```
124+
125+
More information on the `form` attribute can be found at [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#form).
126+
127+
112128
### Options reference
113129

114130
> **_NOTE:_** The form inputs must have a `name` attribute, otherwise the

src/pat/validation/validation.js

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,20 @@ class Pattern extends BasePattern {
6969
this.form.setAttribute("novalidate", "");
7070
}
7171

72+
get inputs() {
73+
// Return all inputs elements
74+
return [...this.form.elements].filter((input) =>
75+
input.matches("input[name], select[name], textarea[name]")
76+
);
77+
}
78+
79+
get disableable() {
80+
// Return all elements, which should be disabled when there are errors.
81+
return [...this.form.elements].filter((input) =>
82+
input.matches(this.options.disableSelector)
83+
);
84+
}
85+
7286
validate_all(event) {
7387
// Check all inputs.
7488
for (const input of this.inputs) {
@@ -77,13 +91,6 @@ class Pattern extends BasePattern {
7791
}
7892

7993
initialize_inputs() {
80-
this.inputs = [
81-
...this.form.querySelectorAll("input[name], select[name], textarea[name]"),
82-
];
83-
this.disabled_elements = [
84-
...this.form.querySelectorAll(this.options.disableSelector),
85-
];
86-
8794
for (const [cnt, input] of this.inputs.entries()) {
8895
// Cancelable debouncer.
8996
const debouncer = utils.debounce((e) => {
@@ -365,7 +372,7 @@ class Pattern extends BasePattern {
365372
let inputs = [input];
366373
if (all_of_group) {
367374
// Get all inputs with the same name - e.g. radio buttons, checkboxes.
368-
inputs = this.inputs.filter((it) => it.name === input.name);
375+
inputs = [...this.form.elements].filter((_input) => _input.name === input.name);
369376
}
370377
for (const it of inputs) {
371378
if (clear_state) {
@@ -378,7 +385,7 @@ class Pattern extends BasePattern {
378385

379386
// disable selector
380387
if (this.form.checkValidity()) {
381-
for (const it of this.disabled_elements) {
388+
for (const it of this.disableable) {
382389
if (it.disabled) {
383390
it.removeAttribute("disabled");
384391
it.classList.remove("disabled");
@@ -402,7 +409,7 @@ class Pattern extends BasePattern {
402409

403410
// Do not set a error message for a input group like radio buttons or
404411
// checkboxes where one has already been set.
405-
const inputs = this.inputs.filter((it) => it.name === input.name);
412+
const inputs = [...this.form.elements].filter((_input) => _input.name === input.name);
406413
if (inputs.length > 1 && inputs.some((it) => !!it[KEY_ERROR_EL])) {
407414
// error message for input group already set.
408415
return;
@@ -426,7 +433,7 @@ class Pattern extends BasePattern {
426433
input[KEY_ERROR_EL] = error_node;
427434

428435
let did_disable = false;
429-
for (const it of this.disabled_elements) {
436+
for (const it of this.disableable) {
430437
// Disable for melements if they are not already disabled and which
431438
// do not have set the `formnovalidate` attribute, e.g.
432439
// `<button formnovalidate>cancel</button>`.

src/pat/validation/validation.test.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,32 @@ describe("pat-validation", function () {
538538
expect(event.detail.dom).toBe(el);
539539
expect(event.detail.action).toBe("valid");
540540
});
541+
542+
it("1.22 - Supports validation of inputs outside forms.", async function () {
543+
document.body.innerHTML = `
544+
<input name="outside" form="form" required/>
545+
<form class="pat-validation" id="form">
546+
</form>
547+
<button form="form">submit</button>
548+
`;
549+
const form = document.querySelector(".pat-validation");
550+
const input = document.querySelector("[name=outside]");
551+
const button = document.querySelector("button");
552+
553+
const instance = new Pattern(form);
554+
await events.await_pattern_init(instance);
555+
556+
input.value = "";
557+
input.dispatchEvent(events.change_event());
558+
await utils.timeout(1); // wait a tick for async to settle.
559+
560+
expect(document.querySelectorAll("em.warning").length).toBe(1);
561+
// Skip, as jsDOM does not support the `:invalid` or `:valid`
562+
// pseudo selectors on forms.
563+
//expect(form.matches(":invalid")).toBe(true);
564+
expect(input.matches(":invalid")).toBe(true);
565+
expect(button.matches(":disabled")).toBe(true);
566+
});
541567
});
542568

543569
describe("2 - required inputs", function () {

0 commit comments

Comments
 (0)