A sweet input validator designed to be a mixin for Vue components. Simply register the variables and rules with the validator and you get automatic error detection. Supply the validator with the error message to display and they can be easily injected into the DOM as real-time feedback for the user.
Grab it in src/Validator.js
and drop it wherever you keep your mixins. Import at the top of the file with /path/to/Validator.js
.
This repo comes prepared for a karma-jasmine
test suite. If you are creating your own rules or editing the validator inner-workings, there are plenty of test examples to get you started. Run npm run test-build
in the repo root to download dependencies. To start testing, run karma start
. From there, every time you save the file, Karma will re-run the tests.
Firstly, tell the validator which variables should be watched. You have a few options here.
this.name = 'Dan'
this.location.zip = '07720'
this.users = [
{ name: 'Bob', email: '[email protected]' },
{ name: 'Wendy', email: '[email protected]' }
]
this.registerErrorChecking('name', 'required', 'Enter a name');
this.registerErrorChecking('location.zip', 'required|size:5', ['Enter a zip', 'Invalid zip'])
this.registerErrorChecking('users.*.email', 'email', 'Invalid email')
Much like Laravel, you declare the rules you want the inputs to follow as pipe delimited strings. Comma-separated arguments are added after a colon in the rule string.
rules = {
'required': 'The input must not be empty',
'boolean': 'The input must be a boolean',
'string': 'The input must be a string',
'number': 'The input must be a number',
'array': 'The input must be an array (object)',
'alpha_num': 'The input must be an alphanumeric string',
'alpha_dash': 'The input must be an alphanumeric string with dashes or underscores',
'email': 'The input must be a valid email'
'regex': 'The input must match a given regular expression',
'max': 'The input must be less than or equal to a given argument in value or length',
'min': 'The input must be greater than or equal to a given argument in value or length',
'size': 'The input must be exactly a given argument in value or length',
'equals': 'The input must be equal to a given argument',
'in': 'The input must be equal to one of the given arguments',
}
examples = [
'required|alpha_num',
'email',
'required|in:yes,no,maybe',
'regex:^[0-9]{1,2}$',
]
Notes:
The
regex
rule currently does not support pipes within your expression argument. This is due to the fact that the rules string is parsed according to pipe delimiters. If you run into this issue, make a workaround and send a PR or perhaps just add your own specific rule to thevalidRules
array and define it at the bottom of the file. I’ll even make it easy and allow you to use myregex_()
function.Any arguments that return something truthy from
parseFloat(arg)
are saved as numbers and not strings.
The third argument is the error message to display when the variable breaks its rule. If there is just a single rule, the third argument accepts a string. If there are multiple rules, pass in an array of messages whose index correspond to the order of the rule string. This argument is technically optional, as it uses a fallback error message of 'Invalid input’
, however 9 out of 10 users agree they want something more helpful than that.
registerErrorChecking('name', 'required', 'Enter a name')
registerErrorChecking('email', 'required|email', ['Enter a name', 'Invalid Email'])
registerErrorChecking('color', 'in:red,yellow')
A boolean that tells whether or not to automatically run error checking whenever the variable changes.
registerErrorChecking('name', 'required', 'Enter a name', false) // only checked manually
When you've got an array that doesn't get initialized to its full possible size, eliminate pesky Vue error messages about undefined variables by allocating indices in this.errors
.
registerErrorChecking('users.*.name', 'required', 'Enter a name', false, 50)
console.log(this.errors.users.length) // 50
By default, Validator.js
attaches a $watch()
closure and whenever Vue detects a change, the validator will automatically re-run error checking on that variable. You can switch to manual error checking with the optional fourth argument.
this.me = 'Dan'
this.you = 'github'
this.registerErrorChecking('me', 'required', 'Enter something')
this.registerErrorChecking('you', 'required', 'Enter something', false)
this.me = ''
this.you = ''
console.log(this.errors.me) // 'Enter something'
console.log(this.errors.you) // ''
errorCheck()
returns the number of detected errors. There are quite a few ways to use the errorCheck()
function, but it all comes down to your desired scope.
Forgo any arguments and Validator.js
will check all the registered variables.
this.name = ''
this.email = 'cats';
this.registerErrorChecking('name', 'required', 'Enter a name')
this.registerErrorChecking('email', 'email', 'Invalid email')
var errors = this.errorCheck()
console.log(errors) // 2
console.log(this.errors.name) // 'Enter a name'
console.log(this.errors.email) // 'Invalid email'
Use the same string that you used to register the variable to return the number of errors detected on that variable only.
this.name = '';
this.email = 'cats';
this.registerErrorChecking('name', 'required', 'Enter a name')
this.registerErrorChecking('email', 'email', 'Invalid email')
var errors = this.errorCheck('name')
console.log(errors) // 1
console.log(this.errors.name) // 'Enter a name'
console.log(this.errors.email) // ''
If you registered multiple nested variables, passing through the root variable will check all registered keys on that variable.
this.user = {
name: '',
age: 12,
}
this.registerErrorChecking('user.name', 'required', 'Enter a name')
this.registerErrorChecking('user.age', 'required|min:18', ['Enter your age', 'Adults Only'])
var errors = this.errorCheck('user')
console.log(errors) // 2
console.log(this.errors.user.name) // 'Enter a name'
console.log(this.errors.user.age) // 'Adults Only'
Given the examples above, it stands to reason that you can error check a specific key in an object as well.
this.location = {
city: {
name: 'Bradley Beach, NJ',
zip: '984'
}
}
this.registerErrorChecking('location.city.name', 'required', 'Enter your city')
this.registerErrorChecking('location.city.zip', 'required|size:5', ['Enter a zip', 'Invalid zip'])
var errors = this.errorCheck('location.city.zip')
console.log(errors) // 1
console.log(this.errors.location.city.name) // ''
console.log(this.errors.location.city.zip) // 'Invalid zip'
Here's how to error check an array of objects
this.users = [
{ name: 'Bob Example', email: 'bob@' },
{ name: 'Wendy Example', email: 'wendy@example' },
]
this.goodIdeas = [
{ step: 'Validator' },
{ step: 'GitHub' },
{ step: '???' },
{ step: 'Profit!' }
]
this.registerErrorChecking('users.*.email', 'required|email', 'Invalid email')
this.registerErrorChecking('goodIdeas.*.step', 'alpha_num', 'Only A thru Z')
var errors = this.errorCheck('users.1.email')
console.log(errors) // 1
console.log(this.errors.users[0].email) // ''
console.log(this.errors.users[1].email) // 'Invalid email'
errors = this.errorCheck('goodIdeas')
console.log(errors) // 2
console.log(this.errors.goodIdeas[0].step) // ''
console.log(this.errors.goodIdeas[1].step) // ''
console.log(this.errors.goodIdeas[2].step) // 'Only A thru Z'
console.log(this.errors.goodIdeas[3].step) // 'Only A thru Z'
Well now I know how to use it, what is this good for?
Vue is reactive, so you can very easily attach error messages near inputs. Since the error message has a length of zero until the moment this.name
breaks its assigned rules, the <span>
won't be shown on the screen until there's an error. Bonus: You can add further feedback from the input (like a red border) by conditionally binding a CSS class.
<div class="form">
<input type="text" :class="{ 'form-error' : errors.name }" v-model="name">
<span class="form-error">{{ errors.name }}</span>
</div>
Place the following code at the top of your submit()
method and you are sure to have valid inputs before they arrive to the backend. Not that this retracts from the necessity of backend validation in the slightest
/**
* POST the form to the server
*/
submit()
{
if (this.errorCheck() > 0) {
return
}
// you're good to go!
}
Here's how it'll look when all is said and done.
import Vue from 'vue'
import Validator from './mixins/Validator.js'
const vm = new Vue({
template: '<div></div>',
mixins: [ Validator ],
data: function()
{
return [
users: [
{ name: 'Bob', email: '[email protected]' },
{ name: 'Wendy', email: 'bob@example' }, // note the error
],
location: {
city: {
name: 'Bradley Beach',
zip: '070' // ouch, another one
}
}
];
},
/**
* Register error checking in beforeCompiled() to ensure this.errors is
* initialized before loading the DOM
*/
beforeCompiled: function()
{
this.registerErrorChecking('users.*.name', 'required|max:50', ['Enter a name', 'Your name is too long!'])
this.registerErrorChecking('users.*.email', 'required|email', 'Bad email')
this.registerErrorChecking('location.city.zip', 'required|size:5|number', 'Invalid zip')
this.registerErrorChecking('location.city.name', 'required|string', 'Enter your city')
},
ready: function()
{
var errors = this.errorCheck();
console.log(errors) // 2
console.log(this.errors[1].email) // 'Bad email'
console.log(this.errors.location.city.zip) // 'Invalid zip'
}
})