Skip to content

Validation maintainence #1250

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

Merged
merged 4 commits into from
May 13, 2025
Merged
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
126 changes: 84 additions & 42 deletions src/pat/validation/documentation.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
## Description

This pattern provides form validation based on the HTML standard and offers extended functionality like custom error messages and extra validation rules.

This pattern provides form validation with standard HTML validation attributes,
extra validation rules, custom error messages and a custom error template. It
is based HTML form validation framework and therefor supports form state
checking via CSS peudo classes like `:valid` and `:invalid`.

## Documentation

Expand All @@ -10,13 +12,23 @@ The rest is handled mostly with standard HTML validation attributes.

This patterns offers:

- extra validation rules like checking for equality or checking is one date it after another.
- custom error messages.
- Custom error messages for the standard HTML validation attributes.
- Custom error template to display error messages.
- Extra validation rules where the standard HTML validation attributes are not enough.

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.

Since it is based on the HTML standard you can still use the `:valid`, `:invalid` and `:out-of-range` CSS pseudo classes.

You can use any HTML form validation attributes but here are some examples:
### HTML form validation framework integration.

This pattern uses the [JavaScript Constraint Validation API](https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Constraint_validation).
Valid formns or inputs can be selected with the `:valid` pseudo class, invalid ones with the `:invalid` pseudo class.


### Validation rules

| Name | Syntax | Description |
| ------------- | -------------------------- | ------------------------------------------------------------ |
Expand All @@ -31,64 +43,94 @@ You can use any HTML form validation attributes but here are some examples:
> **_NOTE:_** The form inputs must have a `name` attribute, otherwise the validation would not happen.

### Error messages
### Custom error messages

Error messages are inserted into the DOM as `em` elements with a `message warning` class.
For most input elements error messages are inserted immediately after the input element.
In addition both the input element and its label will get an `warning` class.
Error messages are unique per type of validation (e.g. `required`, `email` or `number`) and can be overridden:

<label class="warning">First name
<input type="text" required="required" />
<em class="message warning">Please fill out this field</em>
</label>
```html
<form method="post" class="pat-validation"
data-pat-validation="
message-date: This value must be a valid date;
message-datetime: This value must be a valid date and time;
message-email: This value must be a valid email address;
message-number: This value must be a number;
message-required: This field is required;">
<!-- Form fields come here -->
</form>
```

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.
Error messages can also be overridden on a per-field basis, for example:

<fieldset class="checklist radio">
<label><input type="radio" name="radio" /> Strawberry</label>
<label><input type="radio" name="radio" /> Banana</label>
<label><input type="radio" name="radio" /> Raspberry</label>
<em class="message warning">Please make a choice</em>
</fieldset>
```html
<input
type="date"
name="date"
data-pat-validation="
not-after: #planning-end-${number};
message-date: This date must be on or before the end date.
"
/>
```

#### Overriding error messages
For a list of all available error messages see the [Options reference](#options-reference).

Error messages are unique per type of validation (e.g. `required`, `email` or `number`) and can be overridden:

<form method="post" class="pat-validation"
data-pat-validation="
message-date: This value must be a valid date;
message-datetime: This value must be a valid date and time;
message-email: This value must be a valid email address;
message-number: This value must be a number;
message-required: This field is required;">
### Error message rendering

<!-- Form fields come here -->
Error messages are inserted into the DOM as `em` elements with a `message warning` class.
For most input elements error messages are inserted immediately after the input element.
In addition both the input element and its label will get an `warning` class.

</form>
```html
<label class="warning">First name
<input type="text" required="required" />
<em class="message warning">Please fill out this field</em>
</label>
```

Error messages can also be overridden on a per-field basis, for example:
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.

```html
<fieldset class="checklist radio">
<label><input type="radio" name="radio" /> Strawberry</label>
<label><input type="radio" name="radio" /> Banana</label>
<label><input type="radio" name="radio" /> Raspberry</label>
<em class="message warning">Please make a choice</em>
</fieldset>
```

<input type="date" name="date" data-pat-validation="not-after: #planning-end-${number}; message-date: This date must be on or before the end date."/>
The error message template is be overridden via JavaScript by customizing the error_template method of the Pattern API.
This is an [example taken from Mockup](https://github.com/plone/mockup/blob/6c93b810b2c07b5bd58eec80cd03f700c9447d8c/src/patterns.js#L67):

```javascript
import { Pattern as ValidationPattern } from "@patternslib/patternslib/src/pat/validation/validation";

ValidationPattern.prototype.error_template = (message) =>
`<em class="invalid-feedback">${message}</em>`;
```

### Options reference

> **_NOTE:_** The form inputs must have a `name` attribute, otherwise the validation would not happen.
> **_NOTE:_** The form inputs must have a `name` attribute, otherwise the
> validation would not happen.
> **_NOTE:_** If you need to exclude a submit button from form validation - like a cancel button which actually submits - add the `formnovalidate` attribute to the button.
> **_NOTE:_** If you need to exclude a submit button from form validation -
> like a cancel button which actually submits - add the `formnovalidate`
> attribute to the button.

| Property | Description | Default | Type |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | -------------------------------------- |
| disable-selector | A selector for elements that should be disabled when there are errors in the form. | | CSS Selector |
| equality | Field-specific. The name of another input this input should equal to (useful for password confirmation). | | String |
| message-date | The error message for date fields. | This value must be a valid date | String |
| message-datetime | The error message for datetime fields. | This value must be a valid date and time | String |
| message-email | The error message for email fields. | This value must be a valid email address | String |
| message-equality | The error message for fields required to be equal | is not equal to %{attribute} | String |
| message-max | The error message for max number values. | This value must be less than or equal to %{count} | String |
| message-min | The error message for min number values. | This value must be greater than or equal to %{count} | String |
| message-max | The error message for number values which are higher than max. | This value must be less than or equal to %{count} | String |
| message-min | The error message for number values which are lower than min. | This value must be greater than or equal to %{count} | String |
| 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 |
| not-after | Field-specific. A lower time limit restriction for date and datetime fields. | | CSS Selector or a ISO8601 date string. |
| not-before | Field-specific. An upper time limit restriction for date and datetime fields. | | CSS Selector or a ISO8601 date string. |
| message-equality | The error message for fields required to be equal | is not equal to %{attribute} | 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. |
| delay | Time in milliseconds before validation starts to avoid validating while typing. | 100 | Integer |
34 changes: 10 additions & 24 deletions src/pat/validation/validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,25 @@ import utils from "../../core/utils";
import registry from "../../core/registry";

const logger = logging.getLogger("pat-validation");
//logger.setLevel(logging.Level.DEBUG);


export const parser = new Parser("validation");
parser.addArgument("disable-selector", "[type=submit], button:not([type=button])"); // Elements which must be disabled if there are errors
parser.addArgument("message-date", ""); // "This value must be a valid date");
parser.addArgument("message-datetime", ""); // "This value must be a valid date and time");
parser.addArgument("message-email", ""); // "This value must be a valid email address");
parser.addArgument("message-max", ""); // "This value must be less than or equal to %{count}");
parser.addArgument("message-min", ""); // "This value must be greater than or equal to %{count}"); // prettier-ignore
parser.addArgument("message-number", ""); // "This value must be a number");
parser.addArgument("message-required", ""); // "This field is required");
parser.addArgument("message-date", ""); // "This value must be a valid date"
parser.addArgument("message-datetime", ""); // "This value must be a valid date and time"
parser.addArgument("message-email", ""); // "This value must be a valid email address"
parser.addArgument("message-max", ""); // "This value must be less than or equal to %{count}"
parser.addArgument("message-min", ""); // "This value must be greater than or equal to %{count}"
parser.addArgument("message-number", ""); // "This value must be a number"
parser.addArgument("message-required", ""); // "This field is required"
parser.addArgument("message-equality", "is not equal to %{attribute}.");
parser.addArgument("not-after", null);
parser.addArgument("not-before", null);
parser.addArgument("equality", null);
parser.addArgument("delay", 100); // Delay before validation is done to avoid validating while typing.

// BBB
// TODO: deprecated. Will be removed with next major version.
// Aliases
parser.addAlias("message-integer", "message-number");
parser.addArgument("error-template");

const KEY_ERROR_EL = "__patternslib__input__error__el";
const KEY_ERROR_MSG = "__patternslib__input__error__msg";
Expand Down Expand Up @@ -121,19 +119,7 @@ class Pattern extends BasePattern {
return;
}

logger.debug(`
validity_state.badInput ${validity_state.badInput}
validity_state.customError ${validity_state.customError}
validity_state.patternMismatch ${validity_state.patternMismatch}
validity_state.rangeOverflow ${validity_state.rangeOverflow}
validity_state.rangeUnderflow ${validity_state.rangeUnderflow}
validity_state.stepMismatch ${validity_state.stepMismatch}
validity_state.tooLong ${validity_state.tooLong}
validity_state.tooShort ${validity_state.tooShort}
validity_state.typeMismatch ${validity_state.typeMismatch}
validity_state.valid ${validity_state.valid}
validity_state.valueMissing ${validity_state.valueMissing}
`);
logger.debug(`validity_state: `, validity_state);

const input_options = parser.parse(input);

Expand Down
Loading