# Bruut Validate JS

This plugin can be used for all front-end validation needs. This is the successor of the Bruut-Validator plugin, which is now deprecated.

# Installation

npm install --save bruut-validate@latest

# Usage

Import BruutValidate into your project and apply BruutValidate.

TIP

Since version 2.0.0, the default export is changed. Read about legacy imports below.

By default, the package exports a function that attaches the BruutValidate instance to all form elements in the DOM with [bruut-validate] attributes set. You can still import the BruutValidate instance as well, via a named import.

import validate from 'bruut-validate';
validate;

// Import the BruutValidate instance (only needed if you want to do advanced stuff)
import { BruutValidate } from 'bruut-validate';

# Ready helper

Since you don't know if BruutValidate is attached directly after DOMContentLoaded, you can use the provided BruutValidateReady helper function to wrap your functionality after the BruutValidate instance is attached to a DOM element:

// Attaches BruutValidate to all forms with [bruut-validate] tag
import validate, { BruutValidateReady } from 'bruut-validate';
validate;

// Get a specific form
const form = document.querySelector('form[bruut-validate].my-form');

BruutValidateReady(form, e => {
	const instance = form['bruutValidate']; // or e.detail
	
	instance.$events.on('form:validated', () => {
		...
	});
});

# Legacy import (1.x)

import BruutValidate from 'bruut-expander';

const forms = document.querySelectorAll('form[bruut-validate]') as NodeListOf<HTMLFormElement>;

for (let i = 0, len = forms.length; i < len; ++i) {
	forms[i]['bruutValidate'] = new BruutValidate(forms[i]);
}

# Field setup

# Validate attribute

To add a field or checkbox / radio to the validator, make sure that there's a validate attribute set. It should be parseable by JSON.parse().

validate = '{
	"on"         : ["keyup", "change", "blur", "input"],
	"rules"      : ["email", "min:2"]
	"on_hidden"  : false
}'
key type Remarks
on Array:string List of all events that should trigger the validation for this field.
rules Array:string List of the rules that should be validated
on_hidden boolean *optional If set to true, this field will validate, even if the field is hidden.

# Checkbox / Radio

If you want to validate a checkbox / radio (or a group of checkboxes / radio's), make sure that the elements have the same name and that one of the elements has a validate attribute set. The plugin will validate the group on each change.

<div class="field">
	<div class="control">
		<input type="checkbox" name="checkboxes" id="checkbox-1" validate='{"on" : ["change"], "rules" : ["min-checked:1"]}'>
		<input type="checkbox" name="checkboxes" id="checkbox-2">
	</div>
</div>

# Text fields

Text fields are required to have a validate property set, with a JSON parseable object string. You can choose one (or multiple) events to validate to and also one or multiple rules.

Make sure that the rule you use is defined as a validator function. BruutValidate will warn you about this.

<div class="field">
	<div class="control">
		<input type = "text" class = "input" validate = '{"on": ["change"], "rules": ["email"]}'>
	</div>
</div>

# Available events

Event name When does it fire
change After radio / checkbox change and after losing focus on textareas and text fields
keyup After keyup in text fields and textareas
input After keyup, paste, edit in text fields and textareas

# 'Lazy' validation on text fields

If you'd like to wait for validation until the user has moved focus from the field, add the class has-lazy-error to the .field wrapper. This class will be removed from the field as soon as the field has lost focus. The field is being validated at each event that's in the validate string, but you can alter your CSS to show only errors and notices when the field doesn't have the class has-lazy-error.

# Available rules

You can also add your own rules. Learn more in the 'Add your own rules' chapter.

Rule What it does Regex pattern
required Checks if any value exists
min:(int) Checks a minimum length
max:(int) Checks a maximum length
characters:alpha Checks if value only contains alpha characters. /^[a-zA-Z]*$/
characters:alphaspace Checks if value only contains alpha characters or spaces. /^[a-zA-Z\s]*$/
characters:alphaspacedash Checks if value only contains alpha characters or spaces or dashes. /^[a-zA-Z\s-]*$/
characters:numeric Checks if value only contains numeric characters.
min-checked:(int) Radio / Checkbox only: Checks a minimum checked amount of items
max-checked:(int) Radio / Checkbox only: Checks a maximun checked amount of items

# Validating process

# Field

As soon as a event (that's stored in the events config) is fired on the form, the information in the Event object is checked. The event.target is checked to see if there's a validate attribute set, or on one of it's same name siblings if it's a checkbox or radio button.

From there, the validate prop will be parsed and validation will begin.

  1. The field parent (default: .field) will get a check class (default: .is-checking) during the process of validation.
  2. Each rule you assign to the field will get its own Promise that will be resolved or rejected via the rule functions.
  3. When all Promises for every rule in the field are resolved or rejected, the field errors are checked.
  4. When the field is ok, its parent will get the success class (default: .is-success). When there are errors its parent will get the error class (default: .has-error).
  5. The check class is removed from the field parent.
  6. A Custom Event will be fired at this stage to the field (DOM element). Read more about events in callbacks.

# Form

When a submit button with validate-check is clicked, the validation process for the form begins:

  1. The default behaviour of the button will be prevented (e.preventDefault()). If you'd like to submit the form on valid, learn more in the chapter 'Submitting the form'.
  2. The form will get a check class (default: .is-checking) during this process.
  3. The plugin will collect all fields in the form with a validate attribute set and validate all these fields (see: Validating process | field for more details).
  4. If all Promises from these individual fields are resolved or rejected, the errors are checked
  5. When the form is ok, it will get the success class (default: .is-success). When there are errors in the form it will get the error class (default: .has-error).
  6. A Custom Event will be fired at this stage to the form (DOM element). Read more about events in callbacks.

# Callbacks

You can listen to the callbacks on the global $events property on each instance.

TIP

Make sure that the BruutValidate instance is attached to the element, before adding callbacks. You can use the Ready helper for this.

const instance = form['bruutValidate'];

instance.$events.on('form:validated', ({valid, errors}) => {
	console.log(valid);
});

instance.$events.on('field:validated', ({field, valid, errors}) => {
	console.log(valid);
});

# Form

Event Fires when Payload <object>
form:validated The form is validated { valid <boolean>, errors: <object> }

# Field

Note : When you are listening to checkbox or radio groups, events are fired to the element that has the validate attribute.

Event Fires when Payload in e.detail
field:validated A field is validated { field: <HTMLInputElement>, valid <boolean>, errors: false or <array>.

# Submitting the form updated

By default the form is submitted when the form submit button is clicked and the form is valid. You can edit this default behaviour by adding a JSON parsable object string in between the [bruut-validate] atrribute:

<form class="my-form" bruut-validate='{"preventSubmit" : true}'>
...
</form>

A little note on submit button value

To work around the problem of missing submit button values in the $_POST data, the clicked submit button is sent as a hidden input field with the same name and value as the button. If there's no button clicked (submit on 'enter') the first submit button that's found in the form is being used for tha name and value.

# Adding error and success messages

You can add success and error messages on a per field basis. You can add default messages for specific rules and override them with custom messages per rule in a field scope.

You can't use the keywords default and form, since these are reserved for the default messages and messages for the form itself.

To use messages, create a <div> and add the attribute validate-messages to it.

<div class="is-hidden" validate-messages>
	<!-- Default RULE message -->
	<span validate-default="email">Use a correct email address.</span>

	<!-- Specific FIELD message -->
	<span validate-error = "fieldname">Something went wrong</span>
	<span validate-success = "fieldname">Yeah! All is well.</span>

	<!-- Specific rule message per field (error only) -->
	<span validate-error = "fieldname:email">Woops, that email address doesn't seem right.</span>
</div>

You can use html in these messages as well.

The content will be injected in any element (inside the form) with the validate-message attribute:

<p validate-message="fieldname"></p>

Good to know : All error messages will be chained per element, followed by a blank space:

[fieldname] [default rule] / [fieldname:rule]

Tip : If you have both a default error message for a rule and a specific rule error message in the field, the latter takes precedence.

# Adding a message for the form

If you would like to add a message for the entire form, you can use the reserved form as validate-error, validate-success and validate-message. Don’t use this keyword for fields.

# Add your own rules

You can add your own validator logic to the plugin. Let’s make a validator for a hex value which will validate if the value in a field is a hex value, starting with a # and 3 or 6 characters:

import BruutValidate from 'bruut-validate';

window['bruut-validate-settings'].addRule({
	name: 	    'hex',
	logic: 	function(rule) {
		var pattern = /^#?([a-f0-9]{6}|[a-f0-9]{3})$/;
		return (pattern.test(fieldValue));
		
		return (pattern.text(this.value)) ? rule.valid() : rule.invalid();
	}
});

...

TIP

Make sure that your rule is added before instantiating the bruutValidate class.

the name is used in the rules attribute of your field. The logic is a function that has the field as this and 1 additional parameter: rule.

The rule parameter is a object with the following information:

  • form (HTMLElement) : The DOM representation of the form.
  • args (Array) : Array that is filled with the extra data from your rule, if set.
  • valid (function) : Call this function when your validation is ready and the result is valid. This resolves the rule Promise.
  • invalid (function) : Call this function when your validation is ready and the result is invalid. This rejects the rule Promise.

Please note: You must return a rule.valid() or rule.invalid() for your custom validator to work properly. If you don't return an valid() or invalid(), the Promisewon't be fullfilled.

You can use this new validator in your markup as follows:

<input type = "text" class = "input" validate = '{"on": ["change"], "rules": ["hex"]}'>

# Working with parameters in the rule

You can add some more information to a custom rule by using ':' in the rule name:

<input type = "text" class = "input" validate = '{"on": ["change"], "rules": ["hex:3:6"]}'>

This extra data is sent as args array in the rule parameter in your rule function. The string is split on each : and each portion after the : is treated as a value in the array. In this case, the args array will be: [3,6].

# Advanced example

Let's say you'd like to create a 'match' with a other input field that is not in the validator:

<input type = "text" class = "input" id="password">
<input type = "text" class = "input" validate = '{"on" : "change", "rules" : ["match:#password"] }'>

You can write a rule function like this:

window['bruut-validate-settings'].addRule({
	name: 'match',
	logic: function(rule) {

		let otherfield = document.querySelector(rule.args[0]);

		if (otherfield !== 'undefined') {
			return (this.value.trim() === otherfield.value.trim()) ? rule.valid() : rule.invalid();
		} else {
			// no element found, abort!
			return rule.invalid();
		}
	}
});

On every change this function checks the value of the field given as parameter in your rule and returns the validation state.

# Changelog

Changelog