Skip to content

Commit bc27e41

Browse files
committed
feat(pat validation): Validate whole form on submit or single error.
Validate the whole form when a single error happens and some elements were disabled or when a form submit is done. This gives the user a better feedback about any data problems in the form and allows the user to see any other errors at all since the submit elements could have been disabled and form validation would eventually not be triggered.
1 parent 11e9a0b commit bc27e41

File tree

3 files changed

+130
-26
lines changed

3 files changed

+130
-26
lines changed

src/pat/validation/index.html

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -258,18 +258,46 @@
258258
>
259259
<fieldset class="horizontal">
260260
<label>First of all!
261-
<input autocomplete="off"
262-
name="first"
261+
<input name="first"
263262
required
264-
type="text"
263+
autocomplete="off"
265264
/></label>
266265

267266
<label>Optional subtext
268-
<input autocomplete="off"
269-
name="optional"
270-
type="text"
267+
<input name="optional"
268+
autocomplete="off"
271269
/></label>
272270

271+
<label>Start
272+
<input class="pat-date-picker"
273+
name="mod-3"
274+
type="date"
275+
required
276+
autocomplete="off"
277+
data-pat-validation="not-after: [name=mod-4]"
278+
/>
279+
</label>
280+
281+
<label>End
282+
<input class="pat-date-picker"
283+
name="mod-4"
284+
type="date"
285+
required
286+
autocomplete="off"
287+
data-pat-date-picker="after: [name=mod-3]"
288+
data-pat-validation="not-before: [name=mod-3]"
289+
/>
290+
</label>
291+
292+
<label>Preference
293+
<input class="pat-autosuggest"
294+
name="mod-5"
295+
required
296+
autocomplete="off"
297+
data-pat-autosuggest="words: Gibson, Fender, Washburn, Strandberg"
298+
/>
299+
</label>
300+
273301
</fieldset>
274302
<fieldset class="buttons">
275303
<button class="close-panel" type="submit">Submit</button>

src/pat/validation/validation.js

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,12 @@ export default Base.extend({
4444

4545
init() {
4646
this.options = parser.parse(this.el, this.options);
47-
this.inputs = this.el.querySelectorAll(
48-
"input[name], select[name], textarea[name]"
49-
);
47+
this.inputs = [
48+
...this.el.querySelectorAll("input[name], select[name], textarea[name]"),
49+
];
50+
this.disabled_elements = [
51+
...this.el.querySelectorAll(this.options.disableSelector),
52+
];
5053

5154
// Set ``novalidate`` attribute to disable the browser's validation
5255
// bubbles but not disable the validation API.
@@ -56,6 +59,16 @@ export default Base.extend({
5659
// Cancelable debouncer.
5760
const debouncer = utils.debounce((e) => {
5861
this.check_input({ input: input, event: e });
62+
if (this.disabled_elements.some((it) => it.disabled)) {
63+
// If there are already any disabled elements, do a check
64+
// for the whole form.
65+
// This is necessary otherwise the submit button is already
66+
// disabled and no other errors would be shown.
67+
// This is debounced, so it should not disturb too much while typing.
68+
for (const _input of this.inputs.filter((it) => it !== input)) {
69+
this.check_input({ input: _input });
70+
}
71+
}
5972
}, this.options.delay);
6073

6174
events.add_event_listener(
@@ -76,13 +89,21 @@ export default Base.extend({
7689
`pat-validation--blur-${input.name}--${cnt}--validator`,
7790
(e) => debouncer(e)
7891
);
79-
events.add_event_listener(
80-
this.el,
81-
"submit",
82-
`pat-validation--blur-${input.name}--${cnt}--validator`,
83-
(e) => this.check_input({ input: input, event: e }) // immediate check with submit. Otherwise submit is not cancelable.
84-
);
8592
}
93+
94+
events.add_event_listener(
95+
this.el,
96+
"submit",
97+
`pat-validation--submit--validator`,
98+
(e) => {
99+
// On submit, check all.
100+
// Immediate, non-debounced check with submit. Otherwise submit
101+
// is not cancelable.
102+
for (const input of this.inputs) {
103+
this.check_input({ input: input, event: e });
104+
}
105+
}
106+
);
86107
},
87108

88109
check_input({ input, event, stop = false }) {
@@ -289,7 +310,7 @@ export default Base.extend({
289310
let inputs = [input];
290311
if (all_of_group) {
291312
// Get all inputs with the same name - e.g. radio buttons, checkboxes.
292-
inputs = [...this.inputs].filter((it) => it.name === input.name);
313+
inputs = this.inputs.filter((it) => it.name === input.name);
293314
}
294315
for (const it of inputs) {
295316
const error_node = it[KEY_ERROR_EL];
@@ -298,11 +319,12 @@ export default Base.extend({
298319
}
299320

300321
// disable selector
301-
if (this.options.disableSelector && this.el.checkValidity()) {
302-
const disabled = document.querySelectorAll(this.options.disableSelector);
303-
for (const it of disabled) {
304-
it.removeAttribute("disabled");
305-
it.classList.remove("disabled");
322+
if (this.el.checkValidity()) {
323+
for (const it of this.disabled_elements) {
324+
if (it.disabled) {
325+
it.removeAttribute("disabled");
326+
it.classList.remove("disabled");
327+
}
306328
}
307329
}
308330
},
@@ -312,7 +334,7 @@ export default Base.extend({
312334

313335
// Do not set a error message for a input group like radio buttons or
314336
// checkboxes where one has already been set.
315-
const inputs = [...this.inputs].filter((it) => it.name === input.name);
337+
const inputs = this.inputs.filter((it) => it.name === input.name);
316338
if (inputs.length > 1 && inputs.some((it) => !!it[KEY_ERROR_EL])) {
317339
// error message for input group already set.
318340
return;
@@ -336,10 +358,9 @@ export default Base.extend({
336358
}
337359
input[KEY_ERROR_EL] = error_node;
338360

339-
if (options.disableSelector) {
340-
const disabled = document.querySelectorAll(options.disableSelector);
341-
for (const it of disabled) {
342-
it.setAttribute("disabled", "");
361+
for (const it of this.disabled_elements) {
362+
if (!it.disabled) {
363+
it.setAttribute("disabled", "disabled");
343364
it.classList.add("disabled");
344365
}
345366
}

src/pat/validation/validation.test.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,61 @@ describe("pat-validation", function () {
435435
expect(spy_destroy_modal).toHaveBeenCalled();
436436
});
437437

438+
it("1.18 - validates all inputs after submit", async function () {
439+
document.body.innerHTML = `
440+
<form class="pat-validation">
441+
<input name="i1" required>
442+
<input name="i2" required>
443+
<button>submit</button>
444+
</form>
445+
`;
446+
const el = document.querySelector(".pat-validation");
447+
448+
new Pattern(el);
449+
await utils.timeout(1); // wait a tick for async to settle.
450+
451+
document.querySelector("button").click();
452+
453+
expect(el.querySelectorAll("em.warning").length).toBe(2);
454+
});
455+
456+
it("1.19 - validates all inputs after one failed check and disabled button", async function () {
457+
document.body.innerHTML = `
458+
<form class="pat-validation">
459+
<input name="i1" required>
460+
<input name="i2" required>
461+
<button>submit</button> <!-- button will be disabled -->
462+
</form>
463+
`;
464+
const el = document.querySelector(".pat-validation");
465+
466+
new Pattern(el);
467+
await utils.timeout(1); // wait a tick for async to settle.
468+
469+
document.querySelector("[name=i1]").dispatchEvent(events.blur_event());
470+
await utils.timeout(1); // wait a tick for async to settle.
471+
472+
expect(el.querySelectorAll("em.warning").length).toBe(2);
473+
});
474+
475+
it("1.20 - does not validate all inputs after one failed check and no disabled button", async function () {
476+
document.body.innerHTML = `
477+
<form class="pat-validation">
478+
<input name="i1" required>
479+
<input name="i2" required>
480+
</form>
481+
`;
482+
const el = document.querySelector(".pat-validation");
483+
484+
new Pattern(el);
485+
await utils.timeout(1); // wait a tick for async to settle.
486+
487+
document.querySelector("[name=i1]").dispatchEvent(events.blur_event());
488+
await utils.timeout(1); // wait a tick for async to settle.
489+
490+
expect(el.querySelectorAll("em.warning").length).toBe(1);
491+
});
492+
438493
it("2.1 - validates required inputs", async function () {
439494
document.body.innerHTML = `
440495
<form class="pat-validation">

0 commit comments

Comments
 (0)