Skip to content

Validation triggers

Roberto Prevato edited this page Jan 3, 2018 · 12 revisions

DataEntry has built-in support for validation triggers between fields. This is useful in many cases: for example when validating fields that affect each other (and the validation rules of each other, depending on their value). A trigger is defined in a schema, using an array of property names.

An example is the validation of a new password and password confirmation field:

<form method="post" action="?" action="?" autocomplete="off">
  <fieldset>
    <label>New password</label>
    <input type="password" name="new-password" /><br />
    <label>Password confirmation</label>
    <input type="password" name="password-confirmation" /><br />
  </fieldset>
</form>
const dataentry = new DataEntry({
  schema: {
    "new-password": {
      validation: function() {
        var confirmation = this.getFieldValue("password-confirmation");
        return confirmation ? ["required", { 
           name: "equal", 
           params: confirmation, 
           message: "The passwords don't match"
        }] : ["required"];
      },
      trigger: ["password-confirmation"]
    },
    "password-confirmation": {
      validation: ["required"],
      trigger: ["new-password"]
    }
  }, // {...}

Note that two fields may trigger each other's validation, DataEntry handle triggers to not cause stack overflow. The example above has exactly this case.

Examples in a live demo:

Examples

In the example below, field a triggers the validation of field b, after its validation. b and c trigger validation of a.

    const a = new DataEntry({
      schema: {
        a: {
          validation: function () {
            return ["none"];
          },
          trigger: ["b"]
        },
        b: {
          validation: function () {
            return ["none"];
          },
          trigger: ["a"]
        },
        c: {
          validation: function () {
            return ["none"];
          },
          trigger: ["a"]
        }
      },
      // {...}
    })

In the example below, field a triggers the validation of fields b and c.

    const a = new DataEntry({
      schema: {
        a: {
          validation: function () {
            return ["none"];
          },
          trigger: ["b", "c"]
        },
        b: {
          validation: function () {
            return ["none"];
          }
        },
        c: {
          validation: function () {
            return ["none"];
          }
        }
      },
      // {...}
    })

Firing validation on different events

By default, built-in DomBinder class fires validation for a field edited by user on blur, but it supports firing validation on different events.

Configuring validation event globally:

import DataEntryDom from "dataentry/dom"
const DomBinder = DataEntryDom.DomBinder;

DomBinder.validationEvent = "change"; // change validation event globally

Configuring validation event for a whole instance of DataEntry:

const a = new DataEntry({
  validationEvent: "change", 
  // {...}
}) 

Configuring validation event for a specific property:

const a = new DataEntry({
  schema: {
    foo: ["required"],
    ufo: { 
      validation: ["blabla"], 
      validationEvent: "keyup" // <-- fire validation on keyup for `ufo` field 
    }
  },
})

Configuring multiple validation events:

const a = new DataEntry({
  schema: {
    foo: ["required"],
    ufo: { 
      validation: ["blabla"], 
      validationEvent: "blur, custom" // <-- fire validation on 'blur' and 'custom' event for `ufo` field 
    }
  },
})

Firing validation on keyboard events

While some may think a good idea to validate and format values as the user is typing on a field, in practice this is almost never a good idea. For one thing, formatting a value makes sense if the value passes validation, and formatting on keyboard event is never a good idea. Imagine to implement an input field for amount, and needing to format its value to a culture specific representation; for example the number 2120000.80 (two millions, one hundred twenty thousands and 80 cents) would be formatted to different string representations as in the table below:

Culture Formatted value
PL 2 120 000,80
IT 2.120.000,80
EN-US 2,120,000.80

As a side note, for such scenario, the Intl API can be used to implement formatting.

Imagine to implement formatting as the user is typing, for example: as the user types "2120", the input value is automatically formatted to: "2 120", while the field is still focused and being edited.

This scenario creates great complications in handling the selection and caret position of the input element: because setting an input.value while the user is typing only works if the selection is at the end of the string the user is typing (i.e. while the user is typing), but if the user selects a previous portion of the string to add something, then the selection jumps at the end of the string after setting input.value, unless handled. While selectionStart, selectionEnd and setSelectionRange members of HTMLElement can be used to handle this situation and restore the selection when formatting causes a change in the string length, in practice it's extremely uncomfortable to do so, except for the simplest use cases. If the resulting string of formatting has the same length of the original string (e.g. toLowerCase(); toUpperCase()); then setSelectionRange can be easily used to keep the selection after editing; but for all cases in which the length of a string may vary, like in the case of numbers, then the operation is not trivial and there is not a single one-size-fits-all solution. An handler to restore selection should be specified for each validation rule and depending on how the formatting affects the length of the string being formatted.

Another, minor, complication caused by firing validation on keyboard event, is that validation rules may involve AJAX requests and validation would then require throttling, to avoid shooting the server with several requests.

Therefore, it's recommended to use the default settings and fire validation on blur event.

Clone this wiki locally