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>]`. 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 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 They should be used in conjunction with a [`Form Control`](../../../field/docs/FormFundaments.md) that implements the [`ValidateMixin`](../../).
the [`ValidateMixin`](../../).
This tutorial will show you how to build your own custom iban validator, including a factory 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.
function that makes it easier 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. 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 As a best practice, create your validator in a new file from which it can be exported for eventual reuse.
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.
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 ```js
export function ibanValidateFunction() { export function validateIban() {
return { return true;
isIban : true;
}
} }
``` ```
As you can see, the validator returns an object with the name, followed by an expression It return a boolean, which is `true` if the value is valid.
determining its validity.
Which value? The [modelValue](../../../field/docs/FormattingAndParsing.md) like this:
```js ```js
export function ibanValidateFunction(modelValue) { export function validateIban(modelValue) {
return { return /\d{5}([ \-]\d{4})?$/.test(modelValue.trim());
isIban : /\d{5}([ \-]\d{4})?$/.test(modelValue.trim());
}
} }
``` ```
The above function validates ibans for the United States ('us'). 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.
Suppose we want to support 'nl' ibans as well. Since our validator is a pure function, Since our validator is a pure function, we need a parameter for it:
we need a parameter for it:
```js ```js
export function ibanValidateFunction(modelValue, { country } = {}) { export function validateIban(modelValue, { country } = {}) {
const regexes = { const regexpes = {
'us' : /\d{5}([ \-]\d{4})?$/, us: /\d{5}([ \-]\d{4})?$/,
'nl' : /^[1-9]{1}[0-9]{3} ?[a-zA-Z]{2}$/, nl: /^[1-9]{1}[0-9]{3} ?[a-zA-Z]{2}$/,
} };
return { return regexpes[country || 'us'].test(modelValue.trim());
isIban : regexes(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 ## Adding an error message
<!-- TODO: increase DX here and probably deprecate this approach. <!-- 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 On top/better: consider a less coupled design (localization outside of validation and strings being passed
to feedback renderer) --> to feedback renderer) -->
Now, we want to add an error message. For this, we need to have a bit more knowledge about how the Now, we want to add an error message.
ValidateMixin handles translation resources. 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 As can be read in [validate](../../), the `ValidateMixin` considers all namespaces configured via `get loadNamespaces`.
configured via `get loadNamespaces`. By default, this contains at least the `lion-validate` By default, this contains at least the `lion-validate` namespace which is added by the `ValidateMixin`.
namespace which is added by the `ValidateMixin`. On top of this, for every namespace found, it adds On top of this, for every namespace found, it adds an extra `{namespace}-{validatorUniqueId}` namespace.
an extra `{namespace}-{validatorName}` namespace. Let's assume we apply our validator on a regular `<lion-input>`.
Let's assume we apply our validator on a regular `<lion-input>`. That would mean on validation these If our `validatorUniqueId` was `isIban`, that would mean on validation these two namespaces are considered, and in this order:
two namespaces are considered, and in this order:
* lion-validate+isIban - lion-validate+isIban
* lion-validate - lion-validate
One should be aware that localize namespaces are defined in a global scope, therefore the approach One should be aware that localize namespaces are defined in a global scope.
above would only work fine when the iban validator would be part of the core code base (lion-web). 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. 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 The resulting `lion-validate+my-app-isIban` namespace is now guaranteed to be unique.
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 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): The recommended way to do this (inside your `validators.js` file):
```js ```js
localize.loadNamespace({ localize.loadNamespace({
'lion-validate+my-app-isIban': (locale) => { 'lion-validate+my-app-isIban': locale => {
return import(`./translations/${locale}.js`); 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 ```js
export default { export default {
error: { 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: { 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 --> <!-- TODO: use error messages for warning validators as backup, so they can be omitted for almost all use cases -->
## Creating a factory function `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.
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,
];
```
## Conclusion ## 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: After importing it, using the validator would be as easy as this:
```html ```html
<validatable-el .errorValidators="${[IbanValidator({ country: 'nl' })]}"></validatable-el> <validatable-el .errorValidators="${[isIbanValidator({ country: 'nl' })]}"></validatable-el>
``` ```