Skip to content

Commit 47b56d1

Browse files
committed
feat(pat-validation): Support definition of minimum or maximum number of selections.
1 parent 6602f23 commit 47b56d1

File tree

3 files changed

+240
-2
lines changed

3 files changed

+240
-2
lines changed

src/pat/validation/documentation.md

+5
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ These extra validation rules are:
2020

2121
- Equality checking between two fields (e.g. password confirmation).
2222
- Date and datetime validation for before and after a given date or another input field.
23+
- Minimum and maximum number of checked, selected or filled-out fields. Most useful for checkboxes, but also works for text-inputs, selects and other form elements.
2324

2425

2526
### HTML form validation framework integration.
@@ -130,7 +131,11 @@ ValidationPattern.prototype.error_template = (message) =>
130131
| message-number | The error message for numbers. | This value must be a number. | String |
131132
| message-required | The error message for required fields. | This field is required. | String |
132133
| message-equality | The error message for fields required to be equal | is not equal to %{attribute} | String |
134+
| message-min-values | The error message when the minimim number of checked, selected or filled-out fields has not been reached. | You need to select at least %{count} item(s). | String |
135+
| message-max-values | The error message when the maximum number of checked, selected or filled-out fields has not been reached. | You need to select at most %{count} item(s). | String |
133136
| equality | Field-specific extra rule. The name of another input this input should equal to (useful for password confirmation). | | String |
134137
| not-after | Field-specific extra rule. A lower time limit restriction for date and datetime fields. | | CSS Selector or a ISO8601 date string. |
135138
| not-before | Field-specific extra rule. An upper time limit restriction for date and datetime fields. | | CSS Selector or a ISO8601 date string. |
139+
| min-values | Minimum number of checked, selected or filled out form elements. | null | Integer (or null) |
140+
| max-values | Maximum number of checked, selected or filled out form elements. | null | Integer (or null) |
136141
| delay | Time in milliseconds before validation starts to avoid validating while typing. | 100 | Integer |

src/pat/validation/index.html

+181-2
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,6 @@
115115
yellow</label>
116116
</fieldset>
117117

118-
<hr />
119-
120118
<label>Planning start
121119
<input class="pat-date-picker"
122120
id="planning-start"
@@ -250,6 +248,187 @@
250248
</fieldset>
251249
</form>
252250

251+
<h2>Demo with max-values / min-values support</h2>
252+
<form class="pat-validation pat-checklist"
253+
action="."
254+
method="post"
255+
>
256+
<fieldset>
257+
<legend>Multi select</legend>
258+
<select
259+
name="select"
260+
multiple
261+
required
262+
data-pat-validation="
263+
min-values: 2;
264+
max-values: 3;
265+
"
266+
>
267+
<option value="a">a</option>
268+
<option value="b">b</option>
269+
<option value="c">c</option>
270+
<option value="d">d</option>
271+
</select>
272+
</fieldset>
273+
274+
<fieldset>
275+
<legend>Multiple checkboxes</legend>
276+
<label>
277+
a
278+
<input
279+
type="checkbox"
280+
name="checkbox[]"
281+
value="a"
282+
data-pat-validation="
283+
min-values: 1;
284+
max-values: 3;
285+
"
286+
/>
287+
</label>
288+
<label>
289+
b
290+
<input
291+
type="checkbox"
292+
name="checkbox[]"
293+
value="b"
294+
data-pat-validation="
295+
min-values: 1;
296+
max-values: 3;
297+
"
298+
/>
299+
</label>
300+
<label>
301+
c
302+
<input
303+
type="checkbox"
304+
name="checkbox[]"
305+
value="c"
306+
data-pat-validation="
307+
min-values: 1;
308+
max-values: 3;
309+
"
310+
/>
311+
</label>
312+
<label>
313+
d
314+
<input
315+
type="checkbox"
316+
name="checkbox[]"
317+
value="d"
318+
data-pat-validation="
319+
min-values: 1;
320+
max-values: 3;
321+
"
322+
/>
323+
</label>
324+
</fieldset>
325+
326+
<fieldset>
327+
<legend>Demo with mixed inputs and max/min values support.</legend>
328+
<fieldset>
329+
<select
330+
name="multiple"
331+
multiple
332+
data-pat-validation="
333+
min-values: 2;
334+
max-values: 3;
335+
"
336+
>
337+
<option value="a">a</option>
338+
<option value="b">b</option>
339+
<option value="c">c</option>
340+
<option value="d">d</option>
341+
</select>
342+
</fieldset>
343+
<fieldset>
344+
<label>
345+
a
346+
<input
347+
type="checkbox"
348+
name="multiple"
349+
value="a"
350+
data-pat-validation="
351+
min-values: 1;
352+
max-values: 3;
353+
"
354+
/>
355+
</label>
356+
<label>
357+
b
358+
<input
359+
type="checkbox"
360+
name="multiple"
361+
value="b"
362+
data-pat-validation="
363+
min-values: 1;
364+
max-values: 3;
365+
"
366+
/>
367+
</label>
368+
<label>
369+
c
370+
<input
371+
type="checkbox"
372+
name="multiple"
373+
value="c"
374+
data-pat-validation="
375+
min-values: 1;
376+
max-values: 3;
377+
"
378+
/>
379+
</label>
380+
<label>
381+
d
382+
<input
383+
type="checkbox"
384+
name="multiple"
385+
value="d"
386+
data-pat-validation="
387+
min-values: 1;
388+
max-values: 3;
389+
"
390+
/>
391+
</label>
392+
</fieldset>
393+
<fieldset>
394+
<label>
395+
input 1
396+
<input
397+
name="multiple"
398+
data-pat-validation="
399+
min-values: 1;
400+
max-values: 3;
401+
"
402+
/>
403+
</label>
404+
<label>
405+
input 2
406+
<input
407+
name="multiple"
408+
data-pat-validation="
409+
min-values: 1;
410+
max-values: 3;
411+
"
412+
/>
413+
</label>
414+
<label>
415+
input 3
416+
<input
417+
name="multiple"
418+
data-pat-validation="
419+
min-values: 1;
420+
max-values: 3;
421+
"
422+
/>
423+
</label>
424+
</fieldset>
425+
</fieldset>
426+
<fieldset class="buttons">
427+
<button>Submit</button>
428+
<button formnovalidate>Cancel</button>
429+
</fieldset>
430+
</form>
431+
253432
<div class="pat-modal">
254433
<form class="pat-inject vertical pat-validation"
255434
action="."

src/pat/validation/validation.js

+54
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,13 @@ parser.addArgument("message-min", ""); // "This value must be greater than or eq
2222
parser.addArgument("message-number", ""); // "This value must be a number"
2323
parser.addArgument("message-required", ""); // "This field is required"
2424
parser.addArgument("message-equality", "is not equal to %{attribute}.");
25+
parser.addArgument("message-min-values", "You need to select at least %{count} item(s).");
26+
parser.addArgument("message-max-values", "You need to select at most %{count} item(s).");
2527
parser.addArgument("not-after", null);
2628
parser.addArgument("not-before", null);
2729
parser.addArgument("equality", null);
30+
parser.addArgument("min-values", null);
31+
parser.addArgument("max-values", null);
2832
parser.addArgument("delay", 100); // Delay before validation is done to avoid validating while typing.
2933

3034
// Aliases
@@ -245,6 +249,56 @@ class Pattern extends BasePattern {
245249
logger.debug("Check `no-before` input.", not_after_el);
246250
this.check_input({ input: not_before_el, stop: true });
247251
}
252+
} else if (input_options.minValues || input_options.maxValues) {
253+
const min_values = input_options.minValues !== null && parseInt(input_options.minValues, 10) || null;
254+
const max_values = input_options.maxValues !== null && parseInt(input_options.maxValues, 10) || null;
255+
256+
let number_values = 0;
257+
for (const _inp of this.el.elements) {
258+
// Filter for siblings with same name.
259+
if (
260+
// Keep only inputs with same name
261+
_inp.name !== input.name
262+
// Skip all form elements which are no input elements
263+
|| ! ["INPUT", "SELECT", "TEXTAREA"].includes(_inp.tagName)
264+
) {
265+
continue;
266+
}
267+
268+
// Check if checkboxes or radios are checked ...
269+
if (_inp.type === "checkbox" || _inp.type === "radio") {
270+
if (_inp.checked) {
271+
number_values++;
272+
}
273+
continue;
274+
}
275+
276+
// Select, if select is selected.
277+
if (_inp.tagName === "SELECT") {
278+
number_values += _inp.selectedOptions.length;
279+
continue;
280+
}
281+
282+
// For the rest a value must be set.
283+
if (_inp.value === 0 || _inp.value) {
284+
number_values++;
285+
}
286+
}
287+
288+
if (max_values !== null && number_values > max_values) {
289+
this.set_error({
290+
input: input,
291+
msg: input_options.message["max-values"],
292+
max: max_values,
293+
})
294+
}
295+
if (min_values !== null && number_values < min_values) {
296+
this.set_error({
297+
input: input,
298+
msg: input_options.message["min-values"],
299+
min: min_values,
300+
})
301+
}
248302
}
249303

250304
if (!validity_state.customError) {

0 commit comments

Comments
 (0)