diff --git a/packages/validate/docs/tutorials/CustomValidatorsTutorial.md b/packages/validate/docs/tutorials/CustomValidatorsTutorial.md index f6f650253..17a62488e 100644 --- a/packages/validate/docs/tutorials/CustomValidatorsTutorial.md +++ b/packages/validate/docs/tutorials/CustomValidatorsTutorial.md @@ -2,63 +2,83 @@ Validators consist of an array in the format of `[, , ]`. 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 -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 ``. 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 ``. +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}.', }, -} +}; ``` + -## 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 - + ```