Skip to content

feat(pat-validation): Support definition of minimum or maximum number of selections. #1249

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/core/dom.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,24 @@ describe("core.dom tests", () => {
jest.restoreAllMocks();
});

describe("jsDOM tests", () => {
it("jsDOM supports input elements outside forms.", () => {
document.body.innerHTML = `
<input name="outside" form="a_form"/>
<form id="a_form">
<input name="inside"/>
</form>
`;

const outside = document.querySelector("input[name=outside]");
const inside = document.querySelector("input[name=inside]");
const form = document.querySelector("form");

expect(outside.form).toBe(form);
expect(inside.form).toBe(form);
});
});

describe("document_ready", () => {
it("calls the callback, once the document is ready.", async () => {
let cnt = 0;
Expand Down
18 changes: 17 additions & 1 deletion src/core/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ const await_pattern_init = (pattern) => {
* A event factory for a bubbling and cancelable generic event.
*
* @param {string} name - The event name.
* @returns {Event} - Returns a blur event.
* @returns {Event} - Returns a DOM event.
*/
const generic_event = (name) => {
return new Event(name, {
Expand Down Expand Up @@ -231,6 +231,20 @@ const focus_event = () => {
});
};

const focusin_event = () => {
return new Event("focusin", {
bubbles: true,
cancelable: false,
});
};

const focusout_event = () => {
return new Event("focusout", {
bubbles: true,
cancelable: false,
});
};

const input_event = () => {
return new Event("input", {
bubbles: true,
Expand Down Expand Up @@ -293,6 +307,8 @@ export default {
click_event: click_event,
change_event: change_event,
focus_event: focus_event,
focusin_event: focusin_event,
focusout_event: focusout_event,
input_event: input_event,
mousedown_event: mousedown_event,
mouseup_event: mouseup_event,
Expand Down
18 changes: 18 additions & 0 deletions src/core/events.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,24 @@ describe("core.events tests", () => {
expect(catched).toBe("inner");
});

it("focusin event", async () => {
outer.addEventListener("focusin", () => {
catched = "outer";
});
inner.dispatchEvent(events.focusin_event());
await utils.timeout(1);
expect(catched).toBe("outer");
});

it("focusout event", async () => {
outer.addEventListener("focusout", () => {
catched = "outer";
});
inner.dispatchEvent(events.focusout_event());
await utils.timeout(1);
expect(catched).toBe("outer");
});

it("input event", async () => {
outer.addEventListener("input", () => {
catched = "outer";
Expand Down
6 changes: 3 additions & 3 deletions src/pat/auto-suggest/auto-suggest.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,9 @@ export default Base.extend({
const val = $sel2.select2("val");
if (val?.length === 0) {
// catches "" and []
// blur the input field so that pat-validate can kick in when
// nothing was selected.
this.el.dispatchEvent(events.blur_event());
// focus-out the input field so that pat-validate can kick in
// when nothing was selected.
this.el.dispatchEvent(events.focusout_event());
}
};
this.$el.on("select2-close", initiate_empty_check.bind(this));
Expand Down
6 changes: 3 additions & 3 deletions src/pat/date-picker/date-picker.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,9 @@ export default Base.extend({
onSelect: () => this.dispatch_change_event(),
onClose: () => {
if (this.options.behavior === "styled" && !this.el.value) {
// blur the input field so that pat-validate can kick in when
// nothing was selected.
el.dispatchEvent(events.blur_event());
// focus-out the input field so that pat-validate can kick
// in when nothing was selected.
el.dispatchEvent(events.focusout_event());
}
},
};
Expand Down
31 changes: 29 additions & 2 deletions src/pat/validation/documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ These extra validation rules are:

- Equality checking between two fields (e.g. password confirmation).
- Date and datetime validation for before and after a given date or another input field.
- 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.


### HTML form validation framework integration.
Expand Down Expand Up @@ -88,10 +89,10 @@ In addition both the input element and its label will get an `warning` class.
</label>
```

Checkboxes and radio buttons are treated differently: if they are contained in a fieldset with class `checklist` error messages are added at the end of the fieldset.
Checkboxes and radio buttons are treated differently: The error message is alywas set after the last element of the inputs with the same name.

```html
<fieldset class="checklist radio">
<fieldset>
<label><input type="radio" name="radio" /> Strawberry</label>
<label><input type="radio" name="radio" /> Banana</label>
<label><input type="radio" name="radio" /> Raspberry</label>
Expand All @@ -109,6 +110,28 @@ ValidationPattern.prototype.error_template = (message) =>
`<em class="invalid-feedback">${message}</em>`;
```


### Form elements outside the form

Input elements outside of form elements are fully supported.
pat-validation can handle structures like these:

```html
<input name="outside" form="myform" required>
<form id="myform">
</form>
<button form="myform">submit</button>
```

More information on the `form` attribute can be found at [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#form).


### Dynamic forms

pat-validation supports dynamic forms where form elements are added after the Pattern was initialized.
There is no need to re-initialize the pattern of to dispatch a special event.


### Options reference

> **_NOTE:_** The form inputs must have a `name` attribute, otherwise the
Expand All @@ -130,7 +153,11 @@ ValidationPattern.prototype.error_template = (message) =>
| message-number | The error message for numbers. | This value must be a number. | String |
| message-required | The error message for required fields. | This field is required. | String |
| message-equality | The error message for fields required to be equal | is not equal to %{attribute} | String |
| 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 |
| 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 |
| equality | Field-specific extra rule. The name of another input this input should equal to (useful for password confirmation). | | String |
| not-after | Field-specific extra rule. A lower time limit restriction for date and datetime fields. | | CSS Selector or a ISO8601 date string. |
| not-before | Field-specific extra rule. An upper time limit restriction for date and datetime fields. | | CSS Selector or a ISO8601 date string. |
| min-values | Minimum number of checked, selected or filled out form elements. | null | Integer (or null) |
| max-values | Maximum number of checked, selected or filled out form elements. | null | Integer (or null) |
| delay | Time in milliseconds before validation starts to avoid validating while typing. | 100 | Integer |
153 changes: 149 additions & 4 deletions src/pat/validation/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,6 @@
yellow</label>
</fieldset>

<hr />

<label>Planning start
<input class="pat-date-picker"
id="planning-start"
Expand All @@ -134,8 +132,7 @@
autocomplete="off"
name="measure.planning_end:records"
type="date"
value="2015-09-10"
data-pat-date-picker="after: #planning-start"
value="2015-09-09"
data-pat-validation="not-before: #planning-start; message-date: This date must be on or after the start date."
/>
</label>
Expand Down Expand Up @@ -250,6 +247,154 @@
</fieldset>
</form>

<h2>Demo with max-values / min-values support</h2>
<form class="pat-validation pat-checklist"
action="."
method="post"
>
<fieldset>
<legend>Multi select</legend>
<select
name="select"
multiple
required
data-pat-validation="
min-values: 2;
max-values: 3;
"
>
<option value="a">a</option>
<option value="b">b</option>
<option value="c">c</option>
<option value="d">d</option>
</select>
</fieldset>

<fieldset>
<legend>Multiple checkboxes</legend>
<label>
a
<input
type="checkbox"
name="checkbox[]"
value="a"
data-pat-validation="
min-values: 1;
max-values: 3;
"
/>
</label>
<label>
b
<input
type="checkbox"
name="checkbox[]"
value="b"
data-pat-validation="
min-values: 1;
max-values: 3;
"
/>
</label>
<label>
c
<input
type="checkbox"
name="checkbox[]"
value="c"
data-pat-validation="
min-values: 1;
max-values: 3;
"
/>
</label>
<label>
d
<input
type="checkbox"
name="checkbox[]"
value="d"
data-pat-validation="
min-values: 1;
max-values: 3;
"
/>
</label>
</fieldset>

<fieldset
data-pat-validation="
min-values: 2;
max-values: 3;
"
>
<legend>Demo with mixed inputs and max/min values support.</legend>
<fieldset>
<select
name="multiple"
multiple
>
<option value="a">a</option>
<option value="b">b</option>
<option value="c">c</option>
<option value="d">d</option>
</select>
</fieldset>
<fieldset>
<label>
a
<input
type="checkbox"
name="multiple"
value="a"
/>
</label>
<label>
b
<input
type="checkbox"
name="multiple"
value="b"
/>
</label>
<label>
c
<input
type="checkbox"
name="multiple"
value="c"
/>
</label>
<label>
d
<input
type="checkbox"
name="multiple"
value="d"
/>
</label>
</fieldset>
<fieldset>
<label>
input 1
<input name="multiple"/>
</label>
<label>
input 2
<input name="multiple"/>
</label>
<label>
input 3
<input name="multiple"/>
</label>
</fieldset>
</fieldset>
<fieldset class="buttons">
<button>Submit</button>
<button formnovalidate>Cancel</button>
</fieldset>
</form>

<div class="pat-modal">
<form class="pat-inject vertical pat-validation"
action="."
Expand Down
Loading
Loading