Validation

Laraform comes with 54 built-in validators fully compatible with Laravel Validators.

#Rule Definition

Validation rules can be added to elements by defining their rules property:

schema: {
  email: {
    type: 'text',
    label: 'Email',
    rules: // validation rules
  }
}

The rules property accepts string or array. When used as a string, validation rules are separated by |:

schema: {
  email: {
    type: 'text',
    label: 'Email',
    rules: 'required|email'
  }
}

When used as an array each item stands for a different rule:

schema: {
  email: {
    type: 'text',
    label: 'Email',
    rules: ['required', 'email']
  }
}

Nullable

Certain rules should only execute if the input has value otherwise it should be ignored. For example if a user may optionally fill in his birthday, obviously the date format should be validated if the input is filled. This is where nullable rule comes in. If it's present among the validation rules it instructs the validator to only execute validation rules if the field has a value.

schema: {
  birthday: {
    type: 'date',
    label: 'Birthday',
    rules: 'nullable|date_format:YYYY-MM-DD'
  }
}

#Debounce

Debouncing means that an element is not getting validated until certain amount of time has passed since it's last value change.

For example it might be disturbing for a user to get an instant error message when start typing an email address so if we set debounce to 300 the validation will only trigger 300 milliseconds after the last character was typed.

Debounce can be defined as debounce property at the element's schema or as a rule attribute. When defined as a schema property it will be applied to all validation rules the element has. Being defined as a rule attribute in a debounce=t form it will only apply to that specifiy rule (t stands for time in milliseconds).

// Debounce will apply to all rules
schema: {
  email: {
    type: 'text',
    label: 'Email',
    debounce: 300,
    rules: 'required|email'
  }
}

// Debounce will only apply to `email` rule
schema: {
  email: {
    type: 'text',
    label: 'Email',
    rules: 'required|email:debounce=300'
  }
}

#Async

Some rules might involve asynchronous processes, like HTTP requests or image parsing. When these processes are in progress the validator will be in pending status and will decide if the rule can pass once the async process has finished.

Live Example

Now that we know the basics of defining validation rules let's see the examples from above in action:

          
<script>
  export default {
    mixins: [Laraform],
    data: () => ({
      schema: {
        // Date format is only checked if the field is filled
        birthday: {
          type: 'date',
          label: 'Birthday',
          rules: 'nullable|date_format:DD-MM-YYYY'
        },

        // Email rule will not execute for 300ms
        // after the last character was typed
        email: {
          type: 'text',
          label: 'Email',
          rules: 'required|email:debounce=300'
        },

        // Active URL is an async rule which asks
        // the backend to check if the provided
        // URL active A or AAAA type DNS record.
        url: {
          type: 'text',
          label: 'Active URL',
          rules: 'active_url'
        }
      }
    })
}
</script>
        

#Error Messages

When a field has a failing validation rule that rule adds it's error message to the element and to the form. The first error message is shown below the element, while all of them added to the errors above the form unless displayErrors is set to false on the form.

Error messages are defined in the locale's messages property and will have a key for each rule:

// locale file

messages: {
  accepted: 'The :attribute must be accepted.',
  active_url: 'The :attribute is not a valid URL.',
  after: 'The :attribute must be a date after :date.',
  // ...
}

Some rules have different error messages for different kinds of data types, like between rule, which is interpreted differently when using as a number, file, string or array:

// locale file

messages: {
  // ...
  between: {
    numeric: 'The :attribute must be between :min and :max.',
    file: 'The :attribute must be between :min and :max kilobytes.',
    string: 'The :attribute must be between :min and :max characters.',
    array: 'The :attribute must have between :min and :max items.',
  },
  // ...
}

Variables in error messages are started with : and provided by the validation rules.

#Custom Messages

The form accepts a messages object where you can place your custom validation rules in the same format as if they were defined in the locale. This is how we'd overwrite required rule's message:

export default {
  mixins: [Laraform],
  messages: {
    required: 'Please fill in :attribute field.'
  }
}

The same is true for elements, where messages property can be defined in the schema and also accepts an object in the same format as locale or form would:

export default {
  mixins: [Laraform],
  schema: {
    name: {
      type: 'text',
      label: 'Name',
      rules: 'required',
      messages: {
        required: 'We must know your name, please fill in'
      }
    }
  }
}

#Available Validation Rules

Here's the list of available validator rules:

accepted

Examines if the value is true, 'true', 'yes', 'on', '1' or 1.

active_url

Aims to validate if the value is a URL with active A or AAAA type DNS record. You can change its validation endpoint if you don't want to use the built-in validator that comes with the backend Laraform library at Configuration, under endpoints.validators.active_url.

after:date

The value must be after the given date. The date parameter can be either a concrete date like '2018-01-01', a relative date which is either 'today', 'tomorrow' or 'yesterday' or the path of an other field like 'start_date'.

Examples
// being a concrete date
rules: 'date|after:2018-01-01'

// being a relative date
rules: 'date|after:today'

// being an other field's path
rules: 'date|after:accomodation.checkin_date'

Date must be a valid RFC2822 or ISO format according to moment.js.

after_or_equal:date

It's the same as after rule, except that the value must be after or equal to the given date.

alpha

The value can only contain English alphabetic characters.

alpha_dash

The value can only contain numbers, English alphabetic characters, dash - and underscore _.

alpha_num

The value can only contain numbers and English alphabetic characters.

array

The value must be an array.

before:date

It's the same as after rule, except that the value must be before the given date.

before_or_equal:date

It's the same as after rule, except that the value must be before or equal to the given date.

between:min,max

The value must be between the size of min and max. The size is evaluated the same way as described at size rule.

boolean

The value must be some form of a boolean which is either true, false, 1, 0, '1' or '0'.

confirmed

The value must identical to an other field's value which has the same name ending with _confirmation.

Example
schema: {
  password: {
    type: 'password',
    label: 'Password',
    rules: 'confirmed'
  },
  password_confirmation: {
    type: 'password',
    label: 'Password Again'
  }
}

date

The value must be a valid RFC2822 or ISO format according to moment.js.

date_equals:date

The value must equal the given date and must be in a valid RFC2822 or ISO format according to moment.js.

date_format:format

The value must match the given date format according to moment.js.

This rule is not compatible with Laravel's date_format rule because strotime function is using different date/time tokens.

different:field

The value must be different from a different field's value.

digits:value

The value must be numeric and have an exact length of value.

digits_between:min,max

The value must be numeric and have a length between min and max.

dimensions

The value must be an image with dimension constraints. Available constraints:

  • min_width
  • min_height
  • max_witdth
  • max_height
  • height
  • width
  • ratio

The ratio can be a float, like 0.66 or a statement like 2/3 which represents width / height ratio.

Examples
rules: 'dimensions:min_width=768'
rules: 'dimensions:min_height=1024'
rules: 'dimensions:min_width=768,ratio=2/3'

distinct

The value must be a unique member of an array.

Example
favorite_numbers: {
  type: 'list',
  label: 'Favorite numbers:',
  element: {
    type: 'text',
    placeholder: 'Number',
    rules: 'distinct'
  }
}

email

The value must be a valid email format.

exists:param1,param2,...

Aims to check if a given value exists in a database by calling the endpoint retrieved from Configuration endpoints.validators.exists using axios GET request. The value is submitted as value along with provided params array and expects to have a true or false response. If the response is true the field will be resolved as valid.

This rule is not compatible with Laravel's exists rule because of security reasons but you can use separate rules for backend and frontend.

file

The value must be an instance of File.

filled

The value must be not empty.

gt:field

The value must be greater than the size of the given field. The size is evaluated the same way as described at size rule.

gte:field

The value must be greater than or equal to the size of the given field. The size is evaluated the same way as described at size rule.

image

The value must a file with jpeg, png, bmp, gif or svg extension.

in:foo,bar,...

The value must be one of the provided values.

Example
role: {
  type: 'select',
  label: 'Roles',
  items: {
    admin: 'Admin',
    editor: 'Editor'
  },
  rules: 'in:admin,editor'
}

in_array:anotherfield.*

The value must be present in the anotherfield array.

integer

The value must be an integer according to Number.isInteger() method.

ip

The value must be a valid IP address.

ipv4

The value must be a valid IPv4 address.

ipv6

The value must be a valid IPv6 address.

json

The value must be a valid JSON string.

lt:field

The value must be lower than the size of the given field. The size is evaluated the same way as described at size rule.

lte:field

The value must be lower than or equal to the size of the given field. The size is evaluated the same way as described at size rule.

max:value

The size of the value must be lower than or equal to max. The size is evaluated the same way as described at size rule.

mimetypes:text/plain,...

The value must be an instance of File and have one of the listed mimetype.

mimes:zip,rar,...

The value must be an instance of File and have one of the listed extensions.

min:value

The size of the value must be at least min. The size is evaluated the same way as described at size rule.

not_in:foo,bar,...

The value must not be one of the provided values.

not_regex:pattern

The value must not match the provided regex pattern.

Example
rules: 'not_regex:/^.+$/i'

When using pipe | in the pattern you must define the rules as array instead of a string.

nullable

The value can be empty even it has other validators. Read more at Nullable.

numeric

The value must be numeric.

regex:pattern

The value must match the provided regex pattern.

Example
rules: 'regex:/^.+$/i'

When using pipe | in the pattern you must define the rules as array instead of a string.

required

The value must not be empty. The value is considered empty as the following:

  • the value is null
  • the value is undefined
  • if the value is a string it's ''
  • if the value is an array it contains no items
  • if the value is a File object it's name is empty

same:field

The value must be the same as the give field's value.

Example
schema: {
  password: {
    type: 'password',
    label: 'Password',
    rules: 'same:password_again'
  },
  password_again: {
    type: 'password',
    label: 'Password Again'
  }
}

size:value

The size of the value must be exactly as the given value. Size is calculated as the following:

  • if the value is string then it's the length of the string
  • if the value is numeric then it's the actual numeric value
  • if the value is array then it's the length of the array
  • if the value is File then it's the size of the file.

string

The value must be a string.

timezone

The value must be a valid timezone, eg. 'America/Los_Angeles'.

unique:param1,param2,...

Aims to check if a given value is unique in a database by calling the endpoint from Configuration endpoints.validators.unique using axios GET request. The value is submitted as value along with provided params array and expects to have a true or false response. If the response is true the field will be resolved as valid.

This rule is not compatible with Laravel's unique rule because of security reasons but you can use separate rules for backend and frontend.

url

The value must be a valid URL format.

uuid

The value must be a valid UUID format.

#Multilingual Rules

When the form is multilingual some rules might need only apply for elements in certain languages.

Luckily, validation rules can be specified for different languages. To do that simply define the rules propert as an object where the keys are the language codes:

rules: {
  en: // rules
  de: // rules
}

Let's take a look at a case where the blog posts can be translated to English and German, while it's only required in English:

          
<script>
  export default {
    mixins: [Laraform],
    data: () => ({
      multilingual: true,
      languages: {
        en: {
          label: 'English'
        },
        de: {
          label: 'German'
        }
      },
      schema: {
        title: {
          type: 'text',
          label: 'Title',
          rules: {
            en: 'required'
          }
        },
        content: {
          type: 'textarea',
          label: 'Content',
          rules: {
            en: 'required'
          }
        }
      }
    })
  }
</script>
        

As you can see validation errors are only shown if the title or content element is empty in English.

#Conditional Rules

It might happen that you want to have certain validation rules applied to an element only under certain conditions.

To do that you need to define the condition as an object where the property key is the rule you want to have under condition and the value is the condition.

rules: [
  {
    required: // define when to apply `required` rule
  }
]

Conditions can be defined differently, let's see in the following sections how.

#Array Condition

The simplest way to define rule condition is to provide an array where the first parameter is the path of an other element, while the second is it's expected value. The expected value can be a number, string, boolean or even an array.

schema: {
  delivery: {
    type: 'select',
    label: 'Do you want delivery?',
    items: {
      'yes': 'Yes',
      'no': 'No'
    }
  },
  phone: {
    type: 'phone',
    label: 'Phone number',
    rules: [
      {
        required: ['delivery', 'yes']
      }
    ]
  }
}

Multiple Accepted Values

It's also possible to accept multiple values for the condition by defining an array of values as the second parameter:

schema: {
  delivery: {
    type: 'select',
    label: 'How do you want receive your package?',
    items: {
      1: 'Pick at the store',
      2: 'Delivery with UPS',
      3: 'Delivery with FedEx'
    }
  },
  phone: {
    type: 'phone',
    label: 'Phone number',
    rules: [
      {
        required: ['delivery', [2,3]]
      }
    ]
  }
}

The values of the array can be either numbers or strings.

Custom Operator

You might want to use different operators to examine the condition value, which can be done by simply defining the operator as the second array element, and the comparison value third:

schema: {
  deposit: {
    type: 'input',
    label: 'Deposit amount'
  },
  id_number: {
    type: 'text',
    label: 'ID Card number',
    rules: [
      {
        required: ['deposit', '>' 500]
      }
    ]
  }
}

In this example, ID Card number will only be required if the Deposit amount is higher than 500.

Here's the list of available operators:

  • !=
  • =
  • >
  • >=
  • <
  • <=

#Custom Conditions

Conditions can also be defined as functions which allows you to implement your custom solution. The function must return true or false depending if the rule should be applied or not.

Let's take a look at the same example as above but implementing the deposit amount condition with a custom function:

          
<script>
  export default {
    mixins: [Laraform],
    data: () => ({
      schema: {
        deposit: {
          type: 'text',
          label: 'Deposit amount'
        },
        id_number: {
          type: 'text',
          label: 'ID Card number',
          rules: [
            {
              'required': function(Validator){
                return this.el$('deposit').value > 500
              }
            }
          ]
        }
      },
      buttons: [{
        label: 'Submit',
        class: 'btn-primary',
        disabled() {
          return this.form$.disabled
        }
      }]
    })
  }
</script>
        

First thing to notice is that we are using this as a reference to the root Laraform component, so that we can reach other elements and components.

Secondly there's a Validator variable as the first argument of our function which is the instance of Validator class and it's API can be accessed (even we don't use it in this example).

Finally, if you enter more than 500 as Deposit amount and try to submit the form, ID Card number will display an error. However if you then change your mind and modify it to let's say 300 the ID Card number will still display its error. Why? Because ID Card number does not know that the Deposit amount has changed below 500 and it should not be required anymore.

The invalid property of each validator is set upon calling the it's .validate() method so to reevaluate it needs to be called again.

The conclusion is that we have to take care of watching any value changes that might affect our condition and manually trigger the reevaluation of the rule when the other element's value is changed.

Watching For Condition Change

To watch for value changes of an other element we can use the Validator's .watch() method, which only sets our watcher once, instead of Vue's this.$watch() which would execute each time the condition is evaluated.

Here's how we can watch the other field's value and revalidate our field upon the change of deposit:

rules: [
  {
    'required': function(Validator){
      // By using the Validator's .watch() method we
      // make sure that our watcher is only registered
      // once instead of registering it each time the
      // condition is checked. The data we are watching
      // is relative to the root Laraform component
      // so in this case we are watching its data.deposit
      // which is exactly what we need here.
      Validator.watch('data.deposit', () => {

        // By using the Validator's validate() method
        // we can simply revalidate the rule.
        Validator.validate()
      })

      return this.element('deposit').value > 500
    }
  }
]

Now let's see it all together by extending our example with the watcher:

          
<script>
  export default {
    mixins: [Laraform],
    data: () => ({
      schema: {
        deposit: {
          type: 'text',
          label: 'Deposit amount'
        },
        id_number: {
          type: 'text',
          label: 'ID Card number',
          rules: [
            {
              'required': function(Validator){
                Validator.watch('data.deposit', () => {
                  Validator.validate()
                })

                return this.element('deposit').value > 500
              }
            }
          ]
        }
      },
      buttons: [{
        label: 'Submit',
        class: 'btn-primary',
        disabled() {
          return this.form$.disabled
        }
      }]
    })
  }
</script>
        

Now if you decrease Deposit amount below 500 when ID Card number is already displaying an error, you can see that it disappears as the element gets revalidated.

#Custom Rule

#Creating Rule

You may create your own validation rule by extending Rule class:

import Rule from '@laraform/laraform/src/utils/validation/rules/rule'

export default class extends Rule {
  // ...
}

The class should have a .validate(value) function, which does the validation and returns true or false and also a get message() getter which returns the error message to be used when the field is invalid.

Let's assume we are creating a rule which checks if the user has entered only uppercase letters:

// uppercase.js

import Rule from '@laraform/laraform/src/utils/validation/rules/rule'

export default class Uppercase extends Rule {
  get message() {
    return 'The :attribute should only contain uppercase letters'
  }

  validate(value) {
    return value.toUpperCase(value) === value
  }
}

Great so now we have our validation rule ready to use. Let's see how we can put it into action.

The simplest way to use a validation rule class is to pass it over to the rules array:

import Uppercase from './path/to/uppercase.js'

export default {
  mixins: [Laraform],
  data: () => ({
    schema: {
      shout: {
        type: 'text',
        label: 'Shout here',
        rules: [
          'required',
          Uppercase
        ]
      }
    }
  })
}

Now when the user types a letter which is not uppercase it will throw it's error.

#Registering Rule

If you want to reuse your rule maybe it's more convenient to register it globally.

To do so simply register it using .rule() method when registering Laraform.

import Vue from 'vue'
import Laraform from '@laraform/laraform'
import Uppercase from './path/to/uppercase'

Laraform.rule('uppercase', Uppercase)

Vue.use(Laraform)

const app = new Vue({
  el: '#app'
})
Laraform plugin registers it's validator rules before getting installed via .use() method, so make sure you add them beforehand.

From this on, uppercase rule is available globally and you can access it like a regular validator:

rules: [
  'required',
  'uppercase'
]

The same technique can be used to override existing validators.

Registering Rule Message

If you are registering a rule globally you might decide to remove it's get message() getter and rely on the locale to provide it's message. In this case you have to take care of adding the message tag to the locale:

import Vue from 'vue'
import Laraform from '@laraform/laraform'
import Uppercase from './path/to/uppercase'

// Registering rule
Laraform.rule('uppercase', Uppercase)

// Registering rule message to `en` locale
Laraform.tag('en_US.validators.uppercase', 'The :attribute should only contain uppercase letters')

Vue.use(Laraform)

const app = new Vue({
  el: '#app'
})
// uppercase.js

import Rule from '@laraform/laraform/src/utils/validation/rules/rule'

export default class Uppercase extends Rule {
  validate(value) {
    return value.toUpperCase(value) === value
  }
}

#Async Rule

Rules might have asyncronous processes like submitting a HTTP request. In order to implement that you must define a get async() getter which returns true. Then you can use async/await promise for the validate() method to do the validation asyncronously.

import Rule from '@laraform/laraform/src/utils/validation/rules/rule'

export default class Remote extends Rule {
  get async() {
    return true
  }

  async validate(value) {
    var response = await axios.get('/endpoint-to-check', {value: value})

    return response.data.valid
  }
}