-
Notifications
You must be signed in to change notification settings - Fork 4
proposal
Let's assume we want to validate a form called "MyForm"
Covering cases here:
-A field could include a validator that doesn't need params (just pass a function).
-If we need to do extra stuff we can use an object, use cases:
- Inform here custom args (e.g. a maxLenght validator a custom args that indicates the max length).
- Inform custom validation error for this args
- We could combine both.
const myFormValidationSchema = {
firstname: [
Validators.required,
{ validator: Validators.minLenght, customArgs: { minLength: 5 } }
],
email: [{ validator: Validators.regEX, errorMessage: "Not a valid mail" }],
password: [
{
validator: Validators.passwordStrenght,
customArgs: { strenght: strength.strong },
errorMessage:
"Your password is weak please ensure lenght is at least 8 characters and includes..."
}
]
};
const myFormValidation = createFormValidation(myFormValidationSchema);
About validators you can combine both sync and async, the engine will be able to autodetect which approach are you using.
Definitions of a Validation Schema:
export class ValidationResult {
key?: string;
type: string;
succeeded: boolean;
errorMessage: string;
}
type ValidationResultSyncAsync = ValidationResult | Promise<ValidationResult>;
interface FieldValidationFunction {
(value: any, vm: any, customParams: any): ValidationResultSyncAsync;
}
interface FieldValidationFull {
validator: FieldValidationFunction;
customArgs?: object;
errorMessage : (string | string[]),
}
export type FieldValidation = (FieldValidationFunction | FielValidationFull);
export interface RecordValidationResult {
succeeded: boolean;
fieldErrors: { [key: string]: ValidationResult };
formGlobalErrors: Array<ValidationResult>;
}
export interface ValidationSchema {
global?: RecordValidationFunction[];
fields?: { [key: string]: FieldValidation[] }
}
Proposal:
validateField(fieldId : string, value : any, viewModel? : any) : Promise<ValidationResult>
Samples:
Simple validation
myFormValidation.validateField("firstname", "John");
Passing whole record (viewmodel)
myFormValidation.validateField('creditCard', '888-333-222-111', vm);
Example where passing a record (viewmodel) is mandatory: when you a given field validation is dependant on another viewModel field.
api
validateRecord(record : any) : Promise<FormValidationResult>
Record === ViewModel
Example
const viewModel = { login: "[email protected]", password: "jdoe3981" };
myFormValidation
.validateForm(viewModel)
.then(validationResult => {
console.log(validationResult.success); // true
console.log(validationResult.formGlobalErrors); // []
console.log(validationResult.fieldErrors);
})
.catch(error => {
// handle unexpected errors
});
interface ValidationResult {
errorMessage : string;
type : string;
succeeded : boolean;
}
export let errorMessage = 'Please review field sintax'
export const validateRegEx(fieldName : string, value : string, customArgs? : object, record?: object, customErrorMessage? : (string | string[])) : FieldValidationResult;
interface ValidationResult {
errorMessage : string;
type : string;
succeeded : boolean;
}
export const setErrorMessage = (customErroMessage : string) =>
errorMessage = customErroMessage;
let errorMessage = 'Minimum order is 20 €';
export const validateTotalShoppingCart(viewModel : string, customErrorMessage?: (string | string[])) : FormValidationResult;
How to create asynchrounous validators (based on promises beware for old browsers to have the corresponding promise polyfill):
sintax
export const myAsyncValidator = (fieldName : string,
value : string,
customArgs? : object,
record?: object,
customErrorMessage? : (string | string[])
) : Promise<FieldValidationResult>
Example
export let errorMessage = `The username already exists`
export const userExistsOnGitHubValidator = (value, vm, customParams) {
const validationResult = new FieldValidationResult();
validationResult.type = 'GITHUB_USER_EXISTS';
return new Promise(resolve => {
fetch(`https://api.github.com/users/${value}`)
.then(result => {
// Status 200, meaning user exists, so the given user is not valid
validationResult.isValid = false;
validationResult.errorMessage = errorMessage;
resolve(validationResult);
})
.catch(error => {
if(error.status === 404) {
// User does not exists, so the given user is valid
validationResult.isValid = true;
validationResult.errorMessage = '';
resolve(validationResult);
} else {
// Unexpected error
reject(error);
}
});
});
}
On approach to handle internationalization is to move it away from the form validation library, each field validator will return the following structure:
interface ValidationResult {
errorMessage : string;
type : string;
succeeded : boolean;
}
The TYPE field contains the name of validator that failed we can use it to map it to an error message.
Pros of this approach:
- Single responsibility principle: the validation library just validates forms, an external helper takes care of mapping type to the proper string.
Limitations:
- Cannot handle validator that generate more than on error message.
- Need additional plumbing to be coded to handle the mapping between type and translation.
Validators expose a setErrorMessage function that allow you to override default error message:
By doing this you setup a given errorMessage that will be consistent in any validation form using this validator.
You can setup this for instance at application startup.
Pros of this approach:
- You can just setup all the validators message definition in a single entry point.
- You can treat single string error messsage and multiple ones.
Limitations:
- If you need to override this message for a given form read the section at validation schema level.
Sometimes you need to customize the error message just for a single use case on a given form, e.g. a RegEx validator, you want to display for a given form field "Invalid credit card", and in another case "Not a valid DUNS number", how can you achieve this?
const myFormValidationSchema = {
creditCard: [
{ validator: Validators.regEx, customArgs: { regEx: '\d{3}-\d{3}-\d{3}-d{3}', errorMessage='Not a valid credit card' } }
],
duns: [{ validator: Validators.regEX, customArgs: { regEx: '\d{2}-\d{3}-\d{4}'},errorMessage: "Not a valid duns number" }],
};
const myFormValidation = createFormValidation(myFormValidationSchema);
This is a validation schema library, out of scope checking form state, validations triggers (onBlur, onFocus), but:
- We have to check how to integate react final forms on scenarios like: i want to trigger this validation only on Blur.
How does React Final Form solves this?
- You can setup this at field level, a field has a flag named validationOnBlur that triggers all that field validations on blur.
This solution can cobver 90% of the cases and is easy for us (in lc-form-validation we have a flag to control single validations at blur / onChange / or other custom events but probably we shouldn't include this on the base library).