LcFormValidation is a small JavaScript library that provides you form validation. Validate your viewmodel either on client or server side with Node.js. It is third party / framework agnostic, so you can easily add it in your stack, but it integrates quite well with libraries like React / Redux.
- Heavily based on JavaScript (no html attributes annotations).
- Full async, all validations are processed as async using native Promises (LcFormValidation already gives you a polyfill for browsers that do not support promises).
import { createFormValidation, Validators } from 'lc-form-validation'; const customerFormValidationConstraints = { fields: { firstName: [ // Mandatory field { validator: Validators.required }, ], lastName: [ // Mandatory field { validator: Validators.required } ] } };
Create a form validation instance
const customerFormValidation = createFormValidation(customerFormValidationConstraints);
const viewModel = { login: 'jdoe@example.com', password: 'jdoe3981' }; customerFormValidation .validateForm(viewModel) .then((validationResult) => { console.log(validationResult.success); // true console.log(validationResult.formGlobalErrors); // [] console.log(validationResult.fieldErrors); /*[ { succeeded: true, type: "REQUIRED", key: "firstName", errorMessage: "" }, { succeeded: true, type: "REQUIRED", key: "lastName", errorMessage: "" } ]*/ }) .catch((error) => { // handle unexpected errors });
// Successful validation customerFormValidation .validateField(null, 'firstName', 'John') .then((validationResult) => { console.log(validationResult.succeeded); // true console.log(validationResult.type); // "REQUIRED" console.log(validationResult.key); // "firstName" console.log(validationResult.errorMessage); // "" }) .catch((error) => { // handle unexpected errors }); // Unsuccessful validation customerFormValidation .validateField(null, 'lastName', '') .then((validationResult) => { console.log(validationResult.succeeded); // false console.log(validationResult.type); // "REQUIRED" console.log(validationResult.key); // "lastName" console.log(validationResult.errorMessage); // "Please, fill in this mandatory field." }) .catch((error) => { // handle unexpected errors });
Add eventsFilter
with on or more events:
import { createFormValidation, Validators, FieldValidationResult } from 'lc-form-validation'; const loginFormValidationConstraints = { fields: { login: [ // Mandatory field { validator: Validators.required }, // It has to be a valid email address { validator: Validators.email eventsFilter: { onBlur: true }, // <-- apply a filter } ] } }; const loginFormValidation = createFormValidation(loginValidationConstraints);
Trigger field validation:
loginFormValidation .validateField(null, 'login', 'jdoe', { onBlur: true }) .then((validationResult) => { console.log(validationResult.succeeded); // false console.log(validationResult.type); // "EMAIL" console.log(validationResult.key); // "login" console.log(validationResult.errorMessage); // 'Please enter a valid email address.' }) .catch((error) => { // handle unexpected errors });
Not passing
eventsFilter
as third parameter results in{ onChange: true }
by default.
You can pass custom parameters that will be injected into the validator using the customParams
property:
const loginFormValidationConstraints = { fields: { login: [ // Mandatory field { validator: Validators.required }, // It has to be a valid email address { validator: Validators.email }, { validator: Validators.pattern, customParams: { // login must belong to "lemoncode.net" domain pattern: /\@lemoncode.net$/ } } ] } };
A field validator must return a FieldValidationResult
. You can pass the entire viewModel
if you need to validate a field based on other fields:
function allowLowerCaseOnly(value, viewModel, customParams) { const isValid = value.toLowerCase() === value; const errorMessage = 'Field must be lowercase.'; const validationResult = new FieldValidationResult(); validationResult.succeeded = isValid; validationResult.errorMessage = isValid ? '' : errorMessage; validationResult.type = 'LOWER_CASE'; return validationResult; }
Apply inside your constraints:
const signupValidationConstraints = { fields: { username: [ { validator: allowLowerCaseOnly } ] } };
A global validator will accept the entire viewModel and return a FieldValidationResult
. Put your global validators inside global
property of your validation constraints. It is useful, e.g., for validating dynamic properties:
function validateQuestions(questions) { // All questions must be answered. return questions.every((question) => question.answer.trim().length > 0); } function questionsValidator(viewModel) { const isValid = validateQuestions(viewModel.questions); const errorMessage = 'You must answer all questions.'; const validationResult = new FieldValidationResult(); validationResult.succeeded = isValid; validationResult.errorMessage = isValid ? '' : errorMessage; validationResult.type = ''; return validationResult; } const testFormValidationConstraints = { global: [questionsValidator] }; const testFormValidation = createFormValidation(testFormValidationConstraints); const viewModel = { questions: [ { id: 29, title: 'What method does merge two or more objects?', anwser: 'Object.assign' }, { id: 14, title: 'What character do you need to do string interpolation?', anwser: 'Backticks' }, { id: 42, title: 'How to solve 0.1 + 0.2 === 0.3?', anwser: '+(0.1 + 0.2).toFixed(1)' }, { id: 85, title: 'What month will new Date("2017", "04", "19").getMonth() produce?', anwser: 'May' }, ] }; testFormValidation .validateForm(viewModel) .then((validationResult) => { console.log(validationResult.succeeded); // true console.log(validationResult.formGlobalErrors) // [] console.log(validationResult.fieldErrors); // [] }) .catch((error) => { // handle unexpected errors });
You can see the full documentation in the github.io page.
Simple form (ES6, TypeScript)
A simple form with fullname and password fields. Applied validations:
- Both fullname and password fields are mandatory (required validator + custom validator).
Signup form (ES6, TypeScript)
A sign up form with username, password and confirm password fields with the next validation constraints:
- username is mandatory and has to not exist on GitHub (required validator + custom validator).
- password is mandatory and has to be minimum 4 characters length (minLength validator).
- confirm password is mandatory and has to be the same value as password field (custom validator).
Quiz form (ES6, TypeScript)
A simple quiz with three options where there has to be at least one checked option to pass the validation (custom global validation).
Shopping form (ES6)
A little shopping form where the user has to select a product with a version and optionally apply a discount code and enter its NIF. Validations applied:
- The brand and product fields are mandatory (required validator).
- The discount code is optional but needs to have a valid format if fulfilled (pattern validator).
- NIF field is mandatory with a valid format (required validator + custom validator
Form validation is a complex issue, usually we can find solutions that cover the simple scenarios and are focused on building RAD development just by adding some attributes / annotations to given fields or HTML fields (e.g. input), the disadvantage that we have found to this approach:
-
There are different approaches for each scenario: sometimes you have to add some tweaking for async validations, some other take care of special scenarios (like validations that depends on more than one field).
-
Usually you can easily unit test one validation (directive / annotation), but testing the whole form is a complex task (directives are coupled to e.g. HTML).
-
Validations are tightly coupled to e.g. directives or markup is not easy to reuse this validation code in e.g. server side (universal javascript).
We are a team of long-term experienced freelance developers, established as a group in 2010. We specialize in Front End technologies and .NET. Click here to get more info about us.
For the LATAM/Spanish audience we are running an Online Front End Master degree, more info: http://lemoncode.net/master-frontend