Installation Last updated: 2022-03-16

npm

npm install just-validate --save

yarn

yarn add just-validate
And then use it as an imported module:

import JustValidate from 'just-validate';
const validate = new JustValidate('#form');
Or if you don't use module bundlers, just include JustValidate script on your page from CDN and call it as window.JustValidate:

<script src="https://unpkg.com/[email protected]/dist/just-validate.production.min.js"></script>
<body>
  <script>
    const validate = new window.JustValidate('#form');
  </script>
</body>

Quick start Last updated: 2022-03-16

Let's say we have a basic HTML layout:

<form action="#" id="form" autocomplete="off">
  <label for="name">Enter your name</label>
  <input
    type="text"
    class="form__input form-control"
    placeholder="Enter your name"
    autocomplete="off"
    name="name"
    id="name"
  />
  <label for="email">Enter your email</label>
  <input
    type="email"
    class="form__input form-control"
    placeholder="Enter your email"
    autocomplete="off"
    name="email"
    id="email"
  />
  <button class="btn btn-primary" id="submit-btn">Submit</button>
</form>
Next, let's add JustValidate to our layout and define some simple rules.

First, we should create the instance new JustValidate('#form') by passing a form selector, or the element as an argument.
Second, we call .addField() with a field selector as the first argument and an array of rules as the second argument.

const validation = new JustValidate('#form');

validation
  .addField('#name', [
    {
      rule: 'minLength',
      value: 3,
    },
    {
      rule: 'maxLength',
      value: 30,
    },
  ])
  .addField('#email', [
    {
      rule: 'required',
      errorMessage: 'Email is required',
    },
    {
      rule: 'email',
      errorMessage: 'Email is invalid!',
    },
  ]);
And that's it! Now our form is validated!

Advanced usage Last updated: 2022-03-16

Let's considered the example with more fields and rules:

<form action="#" id="form" autocomplete="off">
  <div class="row">
    <div class="col">
      <label for="name">Enter your name</label>
      <input
        type="text"
        class="form__input form-control"
        placeholder="Enter your name"
        autocomplete="off"
        name="name"
        id="name"
      />
    </div>
    <div class="col">
      <label for="email">Enter your email</label>
      <input
        type="email"
        class="form__input form-control"
        placeholder="Enter your email"
        autocomplete="off"
        name="email"
        id="email"
      />
    </div>
  </div>
  <div class="form-group mt-3">
    <label for="password">Enter your password</label>
    <input
      type="password"
      class="form__input form-control"
      placeholder="Enter your password"
      autocomplete="off"
      name="password"
      id="password"
    />
  </div>
  <div class="form-group mt-3">
    <label for="password">Repeat your password</label>
    <input
      type="password"
      class="form__input form-control"
      placeholder="Repeat your password"
      autocomplete="off"
      name="repeat-password"
      id="repeat-password"
    />
  </div>
  <div class="form-group mt-3">
    <label for="password">Enter your message</label>
    <textarea
      name="msg"
      cols="30"
      rows="10"
      class="form__textarea form-control"
      id="message"
    ></textarea>
  </div>
  <div class="form-group mt-4">
    <label for="favorite_animal_select" class="form-label"
      >Select you favorite animal</label
    >
    <select name="pets" id="favorite_animal_select" class="form-select">
      <option value="">--Please choose an option--</option>
      <option value="dog">Dog</option>
      <option value="cat">Cat</option>
      <option value="hamster">Hamster</option>
      <option value="parrot">Parrot</option>
      <option value="spider">Spider</option>
      <option value="goldfish">Goldfish</option>
    </select>
  </div>

  <div class="form-group mt-4">
    <div class="form-check">
      <label class="form-check-label" for="consent_checkbox"
        >I agree to provide the information</label
      >
      <input type="checkbox" id="consent_checkbox" class="form-check-input" />
    </div>
  </div>
  <div
    class="form-group mt-4"
    id="read_terms_checkbox_group"
    style="width: 250px"
  >
    <div class="form-check">
      <label class="form-check-label" for="read_terms_checkbox_group_1"
        >I have read Privacy Policy</label
      >
      <input
        type="checkbox"
        name="checkbox-group-fruit"
        id="read_terms_checkbox_group_1"
        class="form-check-input"
      />
    </div>
    <div class="form-check">
      <label class="form-check-label" for="read_terms_checkbox_group_2"
        >I have read Terms Of Use</label
      >
      <input
        type="checkbox"
        name="checkbox-group-fruit"
        id="read_terms_checkbox_group_2"
        class="form-check-input"
      />
    </div>
    <div class="form-check">
      <label class="form-check-label" for="read_terms_checkbox_group_3"
        >I have read Cookies Policy</label
      >
      <input
        type="checkbox"
        name="checkbox-group-fruit"
        id="read_terms_checkbox_group_3"
        class="form-check-input"
      />
    </div>
  </div>
  <div class="mt-4 form-group">
    <div class="pb-1">Please select the preferred way for communication</div>
    <div
      class="form-check"
      id="communication_radio_group"
      style="max-width: 200px"
    >
      <input
        type="radio"
        name="radio"
        class="form-check-input"
        id="communication_radio_group_1"
      />
      <label class="form-check-label" for="communication_radio_group_1">
        Email
      </label>
      <br />
      <input
        type="radio"
        name="radio"
        class="form-check-input"
        id="communication_radio_group_2"
      />
      <label class="form-check-label" for="communication_radio_group_2">
        SMS
      </label>
    </div>
  </div>

  <div class="d-grid mt-4">
    <button class="btn btn-primary" id="submit-btn">Submit</button>
  </div>
</form>
For this form we will use the setup:

const validation = new JustValidate('#form', {
  errorFieldCssClass: 'is-invalid',
});

validation
  .addField('#name', [
    {
      rule: 'minLength',
      value: 3,
    },
    {
      rule: 'maxLength',
      value: 30,
    },
  ])
  .addField('#email', [
    {
      rule: 'required',
      errorMessage: 'Field is required',
    },
    {
      rule: 'email',
      errorMessage: 'Email is invalid!',
    },
  ])
  .addField('#password', [
    {
      rule: 'password',
    },
  ])
  .addField('#message', [
    {
      validator: (value) => {
        return value[0] === '!';
      },
    },
  ])
  .addField('#consent_checkbox', [
    {
      rule: 'required',
    },
  ])
  .addField('#favorite_animal_select', [
    {
      rule: 'required',
    },
  ])
  .addRequiredGroup(
    '#read_terms_checkbox_group',
    'You should select at least one communication channel'
  )
  .addRequiredGroup('#communication_radio_group')
  .onSuccess((event) => {
    console.log('Validation passes and form submitted', event);
  });

Instance setting Last updated: 2022-03-16

new JustValidate(
   form: string | Element,
   globalConfig?: {
     errorFieldStyle: Partial<CSSStyleDeclaration>,
     errorFieldCssClass: string | string[],
     errorLabelStyle: Partial<CSSStyleDeclaration>,
     errorLabelCssClass: string | string[],
     lockForm: boolean,
     testingMode: boolean,
     focusInvalidField?: boolean,
     tooltip?: {
       position: 'left' | 'top' | 'right' | 'bottom',
     },
     errorsContainer?: string | Element,
   },
   dictLocale?: {
     key: string;
     dict: {
       [localeKey: string]: string,
     };
   }[];
);

Example of the full setting:

const validation = new JustValidate(
  '#form',
  {
    errorFieldCssClass: 'is-invalid',
    errorFieldStyle: {
      border: '1px solid red',
    },
    errorLabelCssClass: 'is-label-invalid',
    errorLabelStyle: {
      color: 'red',
      textDecoration: 'underlined',
    },
    focusInvalidField: true,
    lockForm: true,
    tooltip: {
      position: 'top',
    },
    errorContainer: '.errors-container',
  },
  [
    {
      key: 'Name is too short',
      dict: {
        ru: 'Имя слишком короткое',
        es: 'El nombre es muy corto',
      },
    },
    {
      key: 'Field is required',
      dict: {
        ru: 'Обязательное поле',
        es: 'Se requiere campo',
      },
    },
  ]
);


Field Description Type
errorFieldCssClass Custom CSS class for invalid field string | string[]
errorFieldStyle Custom CSS styles for invalid field object CSSStyleDeclaration
errorLabelCssClass Custom CSS class for invalid label string | string[]
errorLabelStyle Custom CSS styles for invalid label object CSSStyleDeclaration
successFieldCssClass Custom CSS class for valid field string | string[]
successFieldStyle Custom CSS styles for valid field object CSSStyleDeclaration
successLabelCssClass Custom CSS class for valid label string | string[]
successLabelStyle Custom CSS styles for valid label object CSSStyleDeclaration
focusInvalidField If true, the first invalid field will be focused after the form submitting boolean
lockForm If true, lock form during validation. Could be useful for async validation to make it impossible for user to interact with the form boolean
tooltip If the field defined, tooltips will be displayed instead of regular error labels. It has position field which could be 'left' | 'top' | 'right' | 'bottom' { position: 'left' | 'top' | 'right' | 'bottom' }
errorContainer Custom CSS class or Element where to render the error labels string | Element
testingMode If true, it will add data-testid attributes for easier testing boolean

Methods Last updated: 2022-05-18

addField

Defines validation rules for the new field.

.addField(
  field: string,
  rules: {
    rule?: Rules;
    errorMessage?: string | (
      value: string | boolean,
      context: FieldsInterface
    ) => string);
    validator?: (
      value: string | boolean,
      context: FieldsInterface
    ) => boolean | (() => Promise<boolean>);
    value?: number | string | RegExp;
  },
  config?: {
    errorFieldStyle: Partial<CSSStyleDeclaration>;
    errorFieldCssClass: string;
    errorLabelStyle: Partial<CSSStyleDeclaration>;
    errorLabelCssClass: string;
    successFieldStyle?: Partial<CSSStyleDeclaration>;
    successFieldCssClass: string;
    successLabelStyle?: Partial<CSSStyleDeclaration>;
    successLabelCssClass: string;
    tooltip?: {
      position: 'left' | 'top' | 'right' | 'bottom';
    };
    successMessage?: string;
    errorsContainer?: string | Element;
  }[]
)

config overrides the global instance config, so you could customize it for each field.

Field Description Type
errorFieldCssClass Custom CSS class for invalid field string
errorFieldStyle Custom CSS styles for invalid field object CSSStyleDeclaration
errorLabelCssClass Custom CSS class for invalid label string
errorLabelStyle Custom CSS styles for invalid label object CSSStyleDeclaration
successFieldCssClass Custom CSS class for valid field string
successFieldStyle Custom CSS styles for valid field object CSSStyleDeclaration
successLabelCssClass Custom CSS class for valid label string
successLabelStyle Custom CSS styles for valid label object CSSStyleDeclaration
tooltip If the field defined, tooltips will be displayed instead of regular error labels. It has position field which could be 'left' | 'top' | 'right' | 'bottom' { position: 'left' | 'top' | 'right' | 'bottom' }
successMessage Custom text if the field is valid (validation passes for all rules) string
errorContainer Custom CSS class or Element where to render the error labels string | Element

addRequiredGroup

Make the new fields group required. It could be a group of checkboxes or radio buttons.
It means that at least one input in the group should be checked/selected.

.addRequiredGroup(
    groupField: string,
    errorMessage?: string,
    config?: {
      errorFieldStyle: Partial<CSSStyleDeclaration>;
      errorFieldCssClass: string;
      errorLabelStyle: Partial<CSSStyleDeclaration>;
      errorLabelCssClass: string;
      successFieldStyle?: Partial<CSSStyleDeclaration>;
      successFieldCssClass: string;
      successLabelStyle?: Partial<CSSStyleDeclaration>;
      successLabelCssClass: string;
      tooltip?: {
        position: 'left' | 'top' | 'right' | 'bottom';
      };
      successMessage?: string;
      errorsContainer?: string | Element;
    },
)

config overrides the global instance config, so you could customize it for each group.

Field Description Type
errorFieldCssClass Custom CSS class for invalid field string
errorFieldStyle Custom CSS styles for invalid field object CSSStyleDeclaration
errorLabelCssClass Custom CSS class for invalid label string
errorLabelStyle Custom CSS styles for invalid label object CSSStyleDeclaration
successFieldCssClass Custom CSS class for valid field string
successFieldStyle Custom CSS styles for valid field object CSSStyleDeclaration
successLabelCssClass Custom CSS class for valid label string
successLabelStyle Custom CSS styles for valid label object CSSStyleDeclaration
tooltip If the field defined, tooltips will be displayed instead of regular error labels. It has position field which could be 'left' | 'top' | 'right' | 'bottom' { position: 'left' | 'top' | 'right' | 'bottom' }
successMessage Custom text if the group is valid string
errorContainer Custom CSS class or Element where to render the error labels string | Element

onSuccess

Callback if validation passed.

.onSuccess(event)

onFail

Callback if validation failed.

.onFail(fields)

revalidateField

Method to trigger a field validation manually. Returns a promise with a boolean value - valid/invalid field.

.revalidateField(field: string).then(isValid => {})

revalidate

Method to trigger the whole form validation manually. Returns a promise with a boolean value - validation passes/fails.

.revalidate().then(isValid => {})

refresh

Method to refresh the whole form - fields settings, clear errors messages/styles/classes

.refresh()

destroy

Method to turn off the validation, remove all event listeners/errors/messages/styles/classes

.destroy()

removeField

Method to remove validation rules/events/errors from the particular field

.removeField(field: string)

removeGroup

Method to remove validation rules/events/errors from the particular group

.removeGroup(group: string)

showErrors

Method to show errors programmatically

.showErrors(errors: {key: message})

For example:

.showErrors({ '#email': 'The email is invalid' })

showSuccessLabels

Method to show success labels programmatically

.showSuccessLabels(errors: {key: message})

For example:

.showSuccessLabels({ '#email': 'The email looks good!' })

Rules Last updated: 2022-05-18

.addField() method takes rules argument, which is an array of objects. The format of this object is:

{
  rule: string,
  errorMessage?: string | (
      value: string | boolean,
      context: FieldsInterface
    ) => string);
  validator: (
    value: string | boolean,
    context: object
 ) => Boolean | Promise;
 value: number | string;
}

Where fields are:

Field Description Type
rule Defines which validation rule will be applied (see possible values below in the table) string
errorMessage Used for customizing the error message for this rule. It could be a string, or a function which returns a string (it helps to create conditional messages, based on the field value) string | ( value: string | boolean, context: FieldsInterface ) => string)
value This field setting used only with minLength, maxLength, minNumber, maxNumber, customRegexp, minFilesCount, maxFilesCount, files rules to define the rule value number | string | RegExp | FilesRuleValueInterface
validator Used for a custom validation. It should return a boolean or a function which returns a Promise. To mark the field as a valid, true should be passed to resolve callback: resolve(true) ( value: string | boolean, context: object ) => Boolean | () => Promise

rule field could be:

Rule Description Config value format
required Required field, not empty -
email Valid email address -
minLength Limit the minimum text length number
maxLength Limit the maximum text length number
number Value should be a number -
minNumber Number should be more than defined value number
maxNumber Number should be less than defined value number
password Minimum eight characters, at least one letter and one number -
strongPassword Minimum eight characters, at least one uppercase letter, one lowercase letter, one number and one special character -
customRegexp Custom regexp expression Regexp
minFilesCount Uploaded files count should be more than defined value number
maxFilesCount Uploaded files count should be less than defined value number
files Uploaded files attributes should be valid, based on value config (read more in Files validation section)
files: {
    extensions?: string[];
    types?: string[];
    minSize?: number;
    maxSize?: number;
    names?: string[];
  }

Examples

Let's see some examples using value rule field:

validation.addField('#name', [
  {
    rule: 'minLength',
    value: 3,
  },
]);

validation.addField('#password', [
  {
    rule: 'customRegexp',
    value: /(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@$!%*#?&^_-]).{8,}/,
  },
]);

Field validator used for custom validation. It should return a boolean or a function which returns a Promise.

validation.addField('#text', [
  {
    validator: (value) => value[value.length - 1] === '.',
  },
]);

validation.addField('#email', [
  {
    validator: (value) => () => {
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve(true);
        }, 500);
      });
    },
  },
]);

Also, it's possible to do validation based on other fields:

validation.addField('#repeat-password', [
  {
    validator: (value, fields) => {
      if (fields['#password'] && fields['#password'].elem) {
        const repeatPasswordValue = fields['#password'].elem.value;

        return value === repeatPasswordValue;
      }

      return true;
    },
    errorMessage: 'Passwords should be the same',
  },
]);

Files validation Last updated: 2022-03-16

It's possible to validate min/max count of uploaded files and check for type, extension, min/max size and name of uploaded files.

Uploaded files count

<input type="file" name="file" id="file" multiple />
validation.addField('#file', [
  {
    rule: 'minFilesCount',
    value: 1,
  },
  {
    rule: 'maxFilesCount',
    value: 3,
  },
]);

Files attributes validation

validation.addField('#file', [
  {
    rule: 'files',
    value: {
      files: {
        extensions: ['jpeg', 'png'],
        maxSize: 25000,
        minSize: 1000,
        types: ['image/jpeg', 'image/png'],
        names: ['file1.jpeg', 'file2.png'],
      },
    },
  },
]);

Format of value for files rule:

{
  files: {
    extensions?: string[];
    types?: string[];
    minSize?: number;
    maxSize?: number;
    names?: string[];
  }
}

minSize and maxSize should be defined in bytes.

Date validation Last updated: 2022-03-16

Date validation is performed by a separate plugin JustValidatePluginDate to avoid extending the library size. The plugin uses date-fns for operating with dates.

It is possible to validate for defined format, isAfter, isBefore dates. It works both for text input and for date input.

Configuration

JustValidatePluginDate takes 1 argument: function which returns config object:

JustValidatePluginDate((fields) => ({
  required: boolean,
  format: string,
  isBefore: Date | string,
  isAfter: Date | string,
}));

If it's not required, validation will pass if the value is empty.
It is useful to use fields for accessing other fields and their value to set values in the config.

Validate for the given format

import JustValidatePluginDate from 'just-validate-plugin-date';

validation.addField('#date', [
  {
    plugin: JustValidatePluginDate(() => ({
      format: 'dd MMM yyyy',
    })),
    errorMessage: 'Date should be in dd MMM yyyy format (e.g. 20 Dec 2021)',
  },
]);

Validate for isBefore/isAfter

To be able to validate isBefore/isAfter it's important to define the same format which supposed to use in isBefore/isAfter, otherwise the library will not be able to parse this date string correctly.

import JustValidatePluginDate from 'just-validate-plugin-date';

validation.addField('#date', [
  {
    plugin: JustValidatePluginDate(() => ({
      format: 'dd/MM/yyyy',
      isBefore: '15/12/2021',
      isAfter: '10/12/2021',
    })),
    errorMessage: 'Date should be between 10/12/2021 and 15/12/2021',
  },
]);

Also, you could perform validation depends on other fields:

import JustValidatePluginDate from 'just-validate-plugin-date';

validation
  .addField('#date-start', [
    {
      plugin: JustValidatePluginDate(() => ({
        format: 'dd/MM/yyyy',
      })),
      errorMessage: 'Date should be in dd/MM/yyyy format (e.g. 15/10/2021)',
    },
  ])
  .addField('#date-between', [
    {
      plugin: JustValidatePluginDate(() => ({
        format: 'dd/MM/yyyy',
      })),
      errorMessage: 'Date should be in dd/MM/yyyy format (e.g. 15/10/2021)',
    },
    {
      plugin: JustValidatePluginDate((fields) => ({
        format: 'dd/MM/yyyy',
        isAfter: fields['#date-start'].elem.value,
        isBefore: fields['#date-end'].elem.value,
      })),
      errorMessage: 'Date should be between start and end dates',
    },
  ])
  .addField('#date-between-required', [
    {
      plugin: JustValidatePluginDate(() => ({
        required: true,
        format: 'dd/MM/yyyy',
      })),
      errorMessage: 'Date should be in dd/MM/yyyy format (e.g. 15/10/2021)',
    },
    {
      plugin: JustValidatePluginDate((fields) => ({
        required: true,
        format: 'dd/MM/yyyy',
        isAfter: fields['#date-start'].elem.value,
        isBefore: fields['#date-end'].elem.value,
      })),
      errorMessage: 'Date should be between start and end dates',
    },
  ])
  .addField('#date-end', [
    {
      plugin: JustValidatePluginDate(() => ({
        format: 'dd/MM/yyyy',
      })),
      errorMessage: 'Date should be in dd/MM/yyyy format (e.g. 15/10/2021)',
    },
  ]);

Using with date input

If you use date input, you don't need to define format field, because it's always in yyyy-mm-dd format, you just should define isBefore/isAfter:

validation.addField('#date-between', [
  {
    plugin: JustValidatePluginDate((fields) => ({
      isAfter: document.querySelector('#date-start').value,
      isBefore: document.querySelector('#date-end').value,
    })),
    errorMessage: 'Date should be between start and end dates',
  },
]);

Localization Last updated: 2022-03-16

You could define your own translations for different languages. To do that you should define dictLocale array, like:

  [
    {
      key: 'Name is too short',
      dict: {
        ru: 'Имя слишком короткое',
        es: 'El nombre es muy corto',
      },
    },
    {
      key: 'Field is required',
      dict: {
        ru: 'Обязательное поле',
        es: 'Se requiere campo',
      },
    },
  ]

Field key should be defined as a key string, which also should be defined as errorMessage in a rule object.

dict should be an object with languages keys with their translations. To switch a language you should call validation.setCurrentLocale('ru');. The argument for setCurrentLocale() method you should pass the key, which you defined in dict object, or you could call with empty argument to set the default language (strings defined in key fields).

document.querySelector('#change-lang-btn-en').addEventListener('click', () => {
  validation.setCurrentLocale();
});

document.querySelector('#change-lang-btn-ru').addEventListener('click', () => {
  validation.setCurrentLocale('ru');
});

document.querySelector('#change-lang-btn-es').addEventListener('click', () => {
  validation.setCurrentLocale('es');
});

Plugins Last updated: 2022-03-16

If you need more custom functionality it is possible to write your own plugin. For now there is one official plugin (which you could use as an example):