2.0.0 • Published 5 years ago

lc-form-validation v2.0.0

Weekly downloads
121
License
MIT
Repository
github
Last release
5 years ago

lcFormValidation

build status

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).

Quick start

Defining validation constraints

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);

Validating the entire form

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
  });

Validate a single field

// 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
  });

Trigger certain validations in each constraint:

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.

Passing extra arguments

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$/
        }
      }
    ]
  }
};

Custom validators

Field validator

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 }
    ]
  }
};

Global form validator

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
  });

Documentation

You can see the full documentation in the github.io page.

Examples

React examples

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).

jQuery examples

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

Why another form library?

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).

License

MIT

About Lemoncode

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

2.0.1-0

5 years ago

2.0.0

6 years ago

1.0.3

6 years ago

1.0.2

6 years ago

1.0.1

6 years ago

1.0.0

7 years ago

1.0.0-beta.0

7 years ago

0.1.7

7 years ago

0.1.6

7 years ago

0.1.5

7 years ago

0.1.4

7 years ago

0.1.3

7 years ago

0.1.2

8 years ago

0.1.1

8 years ago

0.1.0

8 years ago