Frontend input validation
- Introduction and Examples: nod introduction
- License: MIT.
- npm:
npm install nod-validate
- Dependencies: None.
- Browser support: Chrome (newest) / FF (newest) / ie9+.
- Please help me test in ie (9 and up). I have very limited access to windows machines.
- Backwards compatibility: ver. 2 is not compatible with previous versions.
Cloning the project, and checking out the examples is the best way to go. But here are some basic stuff to give you and idea and get you going.
<form action=''>
<label>
<span>Name</span>
<input type='text' class='name'>
</label>
<label>
<span>Email</span>
<input type='text' class='email'>
</label>
<label>
<span>Email again</span>
<input type='text' class='email-again'>
</label>
<label>
<input type='checkbox' class='terms'>
<span>I agree to terms & conditions</span>
</label>
<button class='submit-btn' type='submit'>Sign up</button>
</form>
A basic sign up form. Let's add some validation.
var n = nod();
// We disable the submit button if there are errors.
n.configure({
submit: '.submit-btn',
disableSubmit: true
});
n.add([{
selector: '.name',
validate: 'min-length:2',
errorMessage: 'Your name must be at least two characters long.'
}, {
selector: '.email',
validate: 'email',
errorMessage: 'That doesn\'t look like it\'s a valid email address.'
}, {
selector: '.email-again',
validate: 'same-as:.email',
errorMessage: 'The email does not match.'
}, {
selector: '.terms',
validate: 'checked',
errorMessage: 'You must agree to our terms and canditions.'
}]);
nod
is a factory, which spits out instances of nod when called (no parameters).
var myNod = nod();
Elements can then be added to it via objects (or an array of objects) describing each.
myNod.add({
selector: '.foo',
validate: 'presence',
errorMessage: 'Can\'t be empty'
});
The selector
property works much like a jquery selector (in fact, if jquery
is available, it will use jQuery to do the work). You can throw most anything
at it. A simple css type selector (like in the example above), a list of
selectors, raw dom elements, jQuery elements, even NodeLists.
The validate
property can be a string with the name of the function you want
to validate the input of the element with (there should be a list somewhere on
this page), or your own function (see next example), a RegExp, or you can
directly extend nod.checkFunctions
with your own function and reuse it
throughout your website.
The errorMessage
is a string containing the error message (in case the
validation fails) that will be shown behind the field.
myNod.add([{
// Raw dom element
selector: document.getElementById('foo'),
// Custom function. Notice that a call back is used. This means it should
// work just fine with ajax requests (is user name already in use?).
validate: function (callback, value) {
callback(value % 2 === 0);
},
errorMessage: 'Must be divisible by 2'
}, {
// jQuery element
selector: $('.bar'),
// EegExp
validate: /hello/g,
errorMessage: 'Input must say hello'
}]);
There is one more setting you can add; triggeredBy
. This is a selector for
another element (it will only match the first matched element), where changes
will also trigger a check on the current element. Example:
// Feel free to use `nod.getElement` and `nod.getElements` to get the row dom
// element(s).
var someCheckbox = nod.getElement('.some-checkbox');
myNod.add({
selector: '.some-input',
// You could also just add the raw dom, just created above.
triggeredBy: '.some-checkbox',
validate: function (callback, value) {
if (someCheckbox.checked) {
callback(value.length > 0);
} else {
// If checkbox isn't checked, then it doesn't matter if .some-input
// is filled out.
callback(true);
}
},
errorMessage: 'If the checkbox is checked, then we need your name'
});
By default, nod listens for input
, change
, and blur
events; however you
can add your own as well. This can be done with triggerEvents
like so:
myNod.add({
selector: '.foo',
validate: 'presence',
errorMessage: '...',
triggerEvents: 'keyup' // can also be an array of event names
});
This is especially helpful if you manually need to trigger a check, or if
you're validating a normal element with contenteditable='true'
.
If you are using jQuery and need to update the element by other means,
then calling $('.foo').trigger('change');
won't work out of box. You have to
manually tell nod
that jQuery is available and where to find it before it
will listen for jQuery events:
myNod.configure({
jQuery: $
});
After that is configured, elements being added will listen for the jQuery change event.
By default, nod considers an element unchecked
until a user types something
into it (or in some other way triggers a check). You can change this, by adding
a defaultStatus
:
myNod.add({
selector: '.foo',
validate: 'presence',
errorMessage: '...',
defaultStatus: 'valid'
});
This is useful because an "unchecked" element will disable a submit button, whereas a "valid" button won't.
Consider this example. First some html with a page where I can change my name (it's currently "John Doe").
<label>
<span>Your name</span>
<input type='text' value='John Doe' class='name'>
</label>
<button type='submit' class='submit-name'>Change your name</button>
var myNod = nod();
myNod.configure({
submit: 'submit-name',
disableSubmit: true
});
myNod.add({
selector: '.name',
validate: 'presence',
errorMessage: 'You need a name.',
defaultStatus: 'valid' // Without this, the submit button would be disabled
});
Once added, you can also remove an input field from being checked. It's done much like you'd expect (I hope).
myNod.remove('.foo');
If '.foo'
matches more than one element, they will all be removed.
Each instance of nod
can be configured to suit specific needs. This is done
either via
myNod.configure({
delay: 400
})
or when creating the instance
var myNod = nod({
delay: 400
});
Below, I will walk you through each of the options available.
We can change the classes of both the "parent" and the "error span". I'll go through each referring to this html:
<div class='group'>
<label>
<span>What's the meaning of life?</span>
<input type='text' class='foo'>
<!--
error span gets added here:
<span class='nod-error'><%= errorMessage %></span>
-->
</label>
</div>
By default, the label
(the parent of the input) will have the class
'nod-success' or 'nod-error' when the element is checked. These class names can
be changed via nod.configure
like so:
myNod.configure({
successClass: 'myNewSuccessClass',
errorClass: 'myNewErrorClass'
});
If you want, instead, that the "parent" isn't its immediate parent, but something further out, you can tell nod to search for a parent with a class like so:
myNod.configure({
parentClass: 'group'
});
Notice that the error span that the error span always gets appended to the
"parent", in the above example, it would now be added after the </label>
and not inside it.
The class of the error message can also be changed:
myNod.configure({
successMessageClass: 'myNewMessageSuccessClass',
errorMessageClass: 'myNewMessageErrorClass'
});
However, nod doesn't show a message behind the input field when it's valid. You have to set that by the configure method as well:
myNod.configure({
successMessage: 'Well done!'
});
The message will be shown after every valid input field in myNod
.
Changing the position of the error span is a bit more tricky (unless you do it
by changing the parentClass, see above). You can, specifically for each element
tell nod which dom element to use as the error span. See setMessageOptions
below.
If you want to prevent nod from inserting error spans in your dom, but rather
take care of showing/hiding error messages yourself, you can pass in a noDom
property in configure:
myNod.configure({
noDom: true
});
nod
will then instead fire an event named nod.validation
on the actual
element that you can listen for:
function myFunc (event) {
console.log(event.detail);
}
myElement.addEventListener('nod.validation', myFunc, false);
The content of event.detail
should be enough for you to handle the error.
By default nod waits 0.7 seconds before display an error message. This is so
the user isn't being bothered with error messages about something that he or
she is not done typing. This delay can be changed via configure
method:
myNod.configure({delay: 300});
Notice, however, that this delay only deals with the time before showing an error message. Removing it will happen instantly, and there is currently no way to change that.
You can disable the submit button, until all added elements are considered valid. This is done like so:
myNod.configure({
submit: '.submit-button',
disableSubmit: true
});
However, be aware, that forms can often be submitted by hitting enter, and a disabled submit button will not do anything to prevent that (see next section).
If you tell nod
about the form and set preventSubmit: true
in configure,
then it will prevent submits entirely, until all added elements are considered
valid.
If an error is detected (and submition prevented), then we show error messages for all non-valid elements, and focus on the first of those elements.
myNod.configure({
form: '.myForm',
preventSubmit: true
});
I should caution the use of this however, as it is hard to get it right in every case (from me, the designer's perspective). So test it well, and make sure it is working correctly in your use case. The last thing you want (I assume) is to prevent your users from submitting your form entirely.
You can "tap" into the internals, and get updates about the state of your
elements by adding a "tap" function via configure
.
myNod.configure({
tap: function (options) {
console.log(options);
}
});
This will give you a lot of calls to your console (basically one, per key
press). The options
argument contains various properties used internally and
is very likely to change (that's why it's an object and not 5 different
arguments), but the most useful are probably options.element
,
options.result
, and options.validate
.
You can specifically for each element tell nod
which is its parent and which
dom element to use as its error span:
myNod.setMessageOptions({
selector: '.foo',
parent: '.myCustomParent',
errorSpan: '.myCustomErrorSpan'
});
Notice, that the "parent" (despite its name) does not have to strictly be a parent. There is also no need to set both the parent and the message.
These settings are especially useful when working with radio buttons. Have a look at examples/05-radio-inputs.html to see an example of how it is used to avoid showing an error message behind every single radio button, but rather show just one at the end of the list.
nod
currently exposes two functions for for manually checking validity of the
elements. One for checking all elements (this is the same used, to
enable/disable the submit button internally:
myNod.areAll('unchecked'); // Returns a boolean
myNod.areAll('valid');
myNod.areAll('invalid');
// Safer way to call it:
myNod.areAll(nod.constants.UNCHECKED);
And you can also query one element specifically like so:
myNod.getStatus('.foo'); // 'unchecked', 'valid', or 'invalid'
If you want to get the error message as well, you can call it like this:
myNod.getStatus('.foo', true); // {status: 'invalid', errorMessage: 'some error msg'}
If you need to check them up against something, I suggest you use
nod.constants
to do so. Like this:
if (myNod.getStatus('.foo') === nod.constants.VALID) {
// Do something
}
nod
provides you with a method: myNod.performCheck()
which forces nod
to
run through each element and checks its validity. This can be useful when
updating the value manually by JavaScript.
Alternatively, you can also pass in a selector, or the raw dom elements, to indicate that you only wish to force a check on those:
myNod.performCheck(['.foo', '.bar']);
Most should be pretty self explaining.
[String]
should be replaced with whatever string you feel is appropriate. See
examples below (or in the examples folder).
"presence"
"exact:[String]"
"contains:[String]"
"not:[String]"
"min-length:[Number]"
"max-length:[Number]"
"exact-length:[Number]"
"between-length:[Number]:[Number]"
"max-number:[Number]"
"min-number:[Number]"
"between-number:[Number]:[Number]"
"integer"
"float"
"same-as:[String]"
(A css type selector)"one-of"
"only-one-of"
"checked"
(For checkboxes only)"some-radio"
(For a group of radio buttons)"email"
(Uses the RFC822 spec to check validity)
myNod.add({
selector: '.foo',
validate: 'exact:foo'
errorMessage: 'You must write exalctly "foo" in the input field'
});
All are accessed through the validate
property of adding an element.
Some more examples:
// ...
{
selector: '.foo',
validate: 'between-length:2:4',
errorMessage: 'Must be between 2 and 4 characters long'
}
// ...
{
// This will check that at least one of the inputs matched by the selector
// has a value.
selector: '.phone-number-inputs',
validate: 'one-of',
errorMessage: 'You need to type in at least one phone number'
}
// ...
{
// You can add validate functions in a list. Just remember to also have
// errorMessages be a list with corresponding texts.
selector: '.foo',
validate: ['email', 'max-length:8'],
errorMessage: ['Must be a valid email', 'Your email is too long. Sorry.']
}
// ...
{
selector: '.foo',
validate: 'same-as:.bar',
errorMessage: 'Must be the same as in .bar'
}
one-of
, only-one-of
, and some-radio
all match on their selector
.
same-as
is called as same-as:[selector]
.
You can extend the available check functions in nod
like this:
// Note that this is the general `nod` function. Not a particular instance.
nod.checkFunctions['divBy2'] = function () {
return function (callback, value) {
callback(value % 2 === 0);
};
};
This function can then be reused:
myNod.add({
selector: '.foo',
validate: 'divBy2',
errorMessage: 'Must be divisable by 2'
});
We can also use arguments when setting up the function. Like so:
nod.checkFunctions['divByX'] = function (x) {
x = parseInt(x, 10);
return function (callback, value) {
callback(value % x === 0);
};
};
And to define "x", we use it like so:
myNod.add({
selector: '.foo',
validate: 'divByX:3',
errorMessage: 'Must be divisable by 3'
});
If you need more arguments, you just separate them with ":".
validate: 'myFunc:a:b:c:d:e'
A little more comprehensive example. Say we want to make a game where the user has to add up numbers. We have some html:
<div>
<span class='a'>21</span> +
<span class='b'>13</span> =
<input type='number' class='result'>
</div>
And let's see some check function:
nod.checkFunctions['calc'] = function (a, b) {
var strA = nod.getElement(a).innerHTML,
strB = nod.getElement(b).innerHTML,
result = parseInt(strA, 10) + parseInt(strB, 10);
return function (callback, value) {
value = parseInt(value, 10); // values are always strings
callback(value === result);
};
};
// And let's use it
myNod.add({
selector: '.result',
validate: 'calc:.a:.b',
errorMessage: 'Wrong! Don\'t you know math?'
});