Skip to content

Implementing validation rules

Roberto Prevato edited this page Dec 27, 2017 · 18 revisions

In DataEntry, validation rules describe how a value must be validated. Validation rules can be defined globally or in specific instances of DataEntry:

Global validation rules

Rules defined in Validator.Rules are common across all instances of DataEntry. Below example demonstrates the definition of new global validation rules.

import DataEntry from "dataentry"

// get reference to Validator class and getError utility
const Validator= DataEntry.Validator;
const getError = Validator.getError;

Validator.Rules.mustBeFoo = function (field, value) {
  if (value !== "foo") {
    // the value is invalid, return an error object using the getError function from Validator
    return getError("mustBeFoo", arguments);
  }
  // the value is valid, return true
  return true;
}

Instance specific validation rules

Instance specific validation rules can also be defined when creating the instance of DataEntry:

import DataEntry from "dataentry"

// get reference to Validator class and getError utility
const Validator = DataEntry.Validator;
const getError = Validator.getError;

var a = new DataEntry({
  // specify one additional validation rule for the instance of dataentry:
  rules: {
    "mustBeFoo": function (field, value) {
      if (value !== "foo") {
        // the value is invalid, return an error object using the getError function from Validator
        return getError("mustBeFoo", arguments);
      }
      // the value is valid, return true
      return true;
    }
  },
  schema: {
    // the new rule can then be used with multiple fields defined in the dataentry schema:
    "field_a": ["mustBeFoo"],
    "field_b": { validation: ["mustBeFoo"], format: ["trim"] },
    "field_c": ["mustBeFoo"],
  }
})

ES6 class definition

Validation rules can also be defined using the ES6 class syntax, defining new types of DataEntry.

import DataEntry from "dataentry";

// example:
class PaymentsAreaDataEntry extends DataEntry {
  // {...} implement here constructor to extend the rules for this kind of DataEntry
  // alter `this.validator.rules` to add new rules for a class of DataEntry
}

Using validation rules

Validation rules are then used in validation schemas (like in example above), to declare the rules for fields by name, or an object with name property:

const dataentry = new DataEntry({
  element: wrapper,
  marker: DomDecorator,
  harvester: DomHarvester,
  schema: {
    name: {
      validation: ["required"],
      format: ["cleanSpaces"]
    },
    year: {
      // this example shows a validation rule used by name and one that requires parameters, used by name
      validation: ["required", { name: "integer", params: [{ min: 1900, max: 2015 }] }]
    },
    "only-letters": ["letters"],
    "policy-read": ["mustCheck"],
    "favored-food": ["required"],
    "force-side": ["required"]
  }
})

Dynamic validation schema

DataEntry also support schemas with dynamic validation rules per field, implemented using functions:

  /* dataentry schema */
  schema: {
    propertyOne: {
      // This validation is dynamic
      validation: function () {
        // the function is called in the context of the dataentry
        if (someCondition)
          return ["required"];
        //otherwise:
        return ["none"];
      }
    },
    propertyTwo: {
      // This validation is static
      validation: ["required"]
    }
  }

Examples of validation rules

Defining asynchronous validation rules

Asynchronous validation rules can be defined using a flag deferred: true, and handling a Promise explicitly:

  // custom rule definition
  Validator.Rules.myAjaxRule = {

    deferred: true, // validation rule is deferred: it returns a Promise explicitly

    fn: function (field, value, forced) {
      return new Promise(function (resolve, reject) {
        var args = arguments;
        // for example, an ajax request created using jQuery:
        //
        $.ajax({
          url: "Validation/MyValidationMethod",
          type: "POST",
          data: {
            value: value
          }
        }).then(function (data) {
          // in this example, the server returns JSON: { valid: true / false }
          if (data.valid)
            return resolve(true);
          // else... (read note below in wiki, to know why here we are resolving with error: true!)
          resolve({
            error: true,
            message: "yourErrorKey",
            field: field,
            value: value,
            params: _.toArray(args).splice(2)
          });
        }).catch(function () {
          // ajax request rejection, reject the validation rule promise
          // error message for this case is handled by the dataentry
          reject();
        });
      });
    };
  }

Please read the note below about why, by design, validation rules using promise should be resolved also with negative results (validation errors).

Note about resolution and rejection of promises

Since the official WC3 specification of the ES6 Promise specifies that the rejection should be used only for exceptional situations (ref. Rejections should be exceptional), the DataEntry library always resolve the promises utilized during the validation of fields and forms: returning a value indicating whether a form is valid or not. Rejection should only happen due to bugs in source code or rejection of a validation rule promise (for example, in case a validation rule requires an AJAX request and a web request completes with status 500). Therefore any rejection must be caused by an unhandled exception happened while applying validation logic, and is ultimately related to a bug in the code. In such situations the DataEntry library is designed to decorate the field for which the validation caused exception and consider the whole form invalid.

Defining synchronous validation rules

Validation rules can also be defined using synchronous functions (like the built-in required validation rule), they are automatically wrapped inside a Promise. This is intentional, so programmers can write simpler code for methods that can be resolved synchronously.

Example: email validation rule

This example shows how to implement an email address validation rule:

    var getError = Validator.getError;

    // add email validation rule:
    Validator.Rules.email = {
      fn: function (field, value, forced) {
        if (!value) return true;
        var limit = 40;
        var rx = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
        if (!value.match(rx) || value.length > limit) {
          return getError("invalidEmail", arguments);
        }
        return true;
      }
    };

Example: phone validation rule

This example shows how to implement a phone number validation rule:

    var getError = Validator.getError;

    Validator.Rules.phone = {
      fn: function (field, value) {
        if (!value) return true;
        if (!/^\s*\+?[0-9\s]+$/.test(value))
          return getError("invalidPhone", arguments);
        return true;
      }
    };

Clone this wiki locally