Merge pull request #186 from hardikpthv/fix/custom-validator-docs

Update custom validator docs
This commit is contained in:
Mikhail Bashkirov 2019-07-19 15:02:12 +02:00 committed by GitHub
commit f4ef3a4a11
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -2,63 +2,83 @@
Validators consist of an array in the format of `[<function>, <?params>, <?opts>]`.
They are implemented via pure functions and can be coupled to localized messages.
They should be used in conjunction with a [`Form Control`](../../../field/docs/FormFundaments.md) that implements
the [`ValidateMixin`](../../).
They should be used in conjunction with a [`Form Control`](../../../field/docs/FormFundaments.md) that implements the [`ValidateMixin`](../../).
This tutorial will show you how to build your own custom iban validator, including a factory
function that makes it easier to apply and share it.
This tutorial will show you how to build your own custom IBAN validator, including a validator factory function that makes it easy to apply and share it.
## Adding a validation method
## Implement the validation logic
It's a good habit to make your Validator compatible with as many locales and languages as possible.
It's a good habit to make your validators compatible with as many locales and languages as possible.
For simplicity, this tutorial will only support Dutch and US zip codes.
As a best practice, create your validator in a new file from which it can be exported for eventual
reuse.
As can be read in [`Validate`](../ValidationSystem.md), a validator applied to
`.errorValidators` expects an array with a function, a parameters object and an additional
configuration object.
As a best practice, create your validator in a new file from which it can be exported for eventual reuse.
First we will define the most important part, the function. We will start with the function body:
We will start with this simple function body:
```js
export function ibanValidateFunction() {
return {
isIban : true;
}
export function validateIban() {
return true;
}
```
As you can see, the validator returns an object with the name, followed by an expression
determining its validity.
It return a boolean, which is `true` if the value is valid.
Which value? The [modelValue](../../../field/docs/FormattingAndParsing.md) like this:
```js
export function ibanValidateFunction(modelValue) {
return {
isIban : /\d{5}([ \-]\d{4})?$/.test(modelValue.trim());
}
export function validateIban(modelValue) {
return /\d{5}([ \-]\d{4})?$/.test(modelValue.trim());
}
```
The above function validates ibans for the United States ('us').
As you can see, the first parameter of a validator is always the
[`modelValue`](../../../field/docs/FormattingAndParsing.md). This is the value that our validity should depend upon.
Suppose we want to support 'nl' ibans as well. Since our validator is a pure function,
we need a parameter for it:
The above function validates IBANs for the United States ('us').
Suppose we want to support 'nl' IBANs as well.
Since our validator is a pure function, we need a parameter for it:
```js
export function ibanValidateFunction(modelValue, { country } = {}) {
const regexes = {
'us' : /\d{5}([ \-]\d{4})?$/,
'nl' : /^[1-9]{1}[0-9]{3} ?[a-zA-Z]{2}$/,
}
export function validateIban(modelValue, { country } = {}) {
const regexpes = {
us: /\d{5}([ \-]\d{4})?$/,
nl: /^[1-9]{1}[0-9]{3} ?[a-zA-Z]{2}$/,
};
return {
isIban : regexes(country || 'us').test(modelValue.trim());
}
return regexpes[country || 'us'].test(modelValue.trim());
}
```
## Creating a validate function
As can be read in [validate](../ValidationSystem.md), a validator applied to `.errorValidators` expects an array with a validate function, a parameters object and an additional configuration object.
It expects a function with such an interface:
```js
export function isIban(...args) {
return {
'my-app-isIban': validateIban(...args),
};
}
```
`my-app-isIban` is the unique id of the validator.
The naming convention for this unique id will be explained later in this document.
## Creating a reusable factory function
But this is way easier to create a factory function which will automatically create an array for `.errorValidators`.
We recommend using this approach and from now on when saying a validator function we mean this one:
```js
export const isIbanValidator = (...factoryParams) => [
(...params) => ({ 'my-app-isIban': isIban(...params) }),
...factoryParams,
];
```
Here the naming convention is `validator name` + `Validator`.
Thus, `isIbanValidator` is your validator factory function.
## Adding an error message
<!-- TODO: increase DX here and probably deprecate this approach.
@ -68,72 +88,56 @@ in prebaked factory functions. There's no need for a global namespace then.
On top/better: consider a less coupled design (localization outside of validation and strings being passed
to feedback renderer) -->
Now, we want to add an error message. For this, we need to have a bit more knowledge about how the
ValidateMixin handles translation resources.
Now, we want to add an error message.
For this, we need to have a bit more knowledge about how the `ValidateMixin` handles translation resources.
As can be read in [Validate](../../), the `ValidateMixin` considers all namespaces
configured via `get loadNamespaces`. By default, this contains at least the `lion-validate`
namespace which is added by the `ValidateMixin`. On top of this, for every namespace found, it adds
an extra `{namespace}-{validatorName}` namespace.
Let's assume we apply our validator on a regular `<lion-input>`. That would mean on validation these
two namespaces are considered, and in this order:
As can be read in [validate](../../), the `ValidateMixin` considers all namespaces configured via `get loadNamespaces`.
By default, this contains at least the `lion-validate` namespace which is added by the `ValidateMixin`.
On top of this, for every namespace found, it adds an extra `{namespace}-{validatorUniqueId}` namespace.
Let's assume we apply our validator on a regular `<lion-input>`.
If our `validatorUniqueId` was `isIban`, that would mean on validation these two namespaces are considered, and in this order:
* lion-validate+isIban
* lion-validate
- lion-validate+isIban
- lion-validate
One should be aware that localize namespaces are defined in a global scope, therefore the approach
above would only work fine when the iban validator would be part of the core code base (lion-web).
One should be aware that localize namespaces are defined in a global scope.
Therefore the approach above would only work fine when the IBAN validator would be part of the core code base ([validate](../../)).
As long as validators are part of an application, we need to avoid global namespace clashes.
Therefore, we recommend to prefix the application name, like this:
Therefore, we recommend to prefix the application name, like this: `my-app-isIban`.
```js
export function ibanValidateFunction(modelValue, { country } = {}) {
...
return {
'my-app-isIban' : regexes(country || 'us').test(modelValue.trim());
}
}
```
The resulting `lion-validate+my-app-isIban` namespace is now guaranteed to be unique.
The resulting `lion-validate+my-app-isIban` namespace is now guarantueed to be unique.
In order for the localization data to be found, the translation files need to be added to the
manager of [localize](../../../localize/).
In order for the localization data to be found, the translation files need to be added to the manager of [localize](../../../localize/).
The recommended way to do this (inside your `validators.js` file):
```js
localize.loadNamespace({
'lion-validate+my-app-isIban': (locale) => {
'lion-validate+my-app-isIban': locale => {
return import(`./translations/${locale}.js`);
},
})
});
```
In (for instance) `./translations/en-GB.js`, we will see:
In (for instance) `./translations/en.js`, we will see:
```js
export default {
error: {
'my-app-isIban': 'Please enter a(n) valid IBAN number for {fieldName}.',
'my-app-isIban':
'Please enter a(n) valid {validatorParams.country} IBAN number for {fieldName}.',
},
warning: {
'my-app-isIban': 'Please enter a(n) valid IBAN number for {fieldName}.',
'my-app-isIban':
'Please enter a(n) valid {validatorParams.country} IBAN number for {fieldName}.',
},
}
};
```
<!-- TODO: use error messages for warning validators as backup, so they can be omitted for almost all use cases -->
## Creating a factory function
Now, with help of the __validate function__, we will create the __Validator__: a factory function.
```js
export const IbanValidator = (...factoryParams) => [
(...params) => ({ country: ibanValidateFunction(...params) }),
...factoryParams,
];
```
`validatorParams` is the second argument passed to the validator.
In this case this is the object `{ country: '%value%' }` where `%value%` is the one passed by an app developer.
## Conclusion
@ -141,5 +145,5 @@ We are now good to go to reuse our validator in external contexts.
After importing it, using the validator would be as easy as this:
```html
<validatable-el .errorValidators="${[IbanValidator({ country: 'nl' })]}"></validatable-el>
<validatable-el .errorValidators="${[isIbanValidator({ country: 'nl' })]}"></validatable-el>
```