Form validation (or more correctly, form field validation) forces a user to fill out all required fields in a web form. The validation is typically done where the developer can set up rules. For example: If the name field is blank, take the user back to the form and display an error message.

Template driven validation is a type of form validation where validation rules are set directly in the form elements using directives.

To implement template driven validations in Vue.js, we can use VeeValidate. VeeValidate is a plugin for Vue.js that allows you to validate input fields and display errors.

What We Will build

At the end of this piece, we will build a simple registration form which uses VeeValidate to validate its form input. Here is a pictorial view of what we will build.

Getting Started With VeeValidate

First, we need to grab Vue and the VeeValidate library.

Let us grab the browser build for Vue.js which is available here.

Next, we will grab the browser build for VeeValidate which is available via jsdeliver here.

Create a new file called register.html and add:

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>Vue Template Form Validation</title>
</head>
<body>
</body>
<!-- include the Vue.js library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.9/vue.js"></script>
<!-- include the VeeValidate library -->
<script src="https://cdn.jsdelivr.net/npm/vee-validate@latest/dist/vee-validate.js"></script>
<script>
// Notify vue about the VeeValidate plugin
 Vue.use(VeeValidate);
</script>

The code above shows how easy it is to include VeeValidate into our Vue project. Vue.js is notified that it can use the VeeValidate plugin at the line that says Vue.use(VeeValidate).

VeeValidate Rules

A VeeValidate rule is one that sets limits or conditions on what can be entered in one or more fields. Validation rules are checked when you update a record containing fields requiring validation. If the rule is violated, a trappable error occurs. How do you use a rule? Just apply the v-validate directive on your input and pass a string value which is a list of validations separated by a pipe. For example, we will use the required and the email validators:

<input v-validate="'required|email'" type="text" name="email">

Alternatively you can pass an object for more flexibility:

<input v-validate="{ required: true, email: true, regex: /[0-9]+/ }" type="text" name="email">

Now every time the input changes, the validator will run the list of validations from left to right, populating the errors helper object whenever an input fails validation.

As at the time of writing this tutorial, VeeValidate ships with 30 rules for form validation with an option of creating your own rules. You can take a peep at what rules are available here.

VeeValidate Errors

By default, VeeValidate provides us with a variable errors which is injected into the data section of the Vue component by the plugin. When a form validation fails, VeeValidate populates this errors variable with an array containing objects of failed validations, which can be accessed this way:

//check if an input has errors
this.errors.has(Inputname)
//return the first error of an input
this.errors.first(Inputname)

However, to use this method, some things must be put into consideration.

  • The name attribute must be specified. It serves as an identifier for VeeValidate to refer to. You can use data-vv-name if, for some reason, you can’t use name attribute in your template.

VeeValidate Custom Validation

While VeeValidate ships with around 30 rules, these rules might not do justice to your form as intended. What if I need a custom validation that VeeValidate does not come with? For example, what if I want to validate that a username is unique in my database? VeeValidate allows you to write custom validation rules and messages. Take a look at the code below for example:

//declare an array of some usernames user must not input
 var username = [
    'admin',
    'password',
    'administartor'
]
//create new rule
const newrule = {
// will be added to default English messages.
  getMessage(field, params, data) {
      return (data && data.message) || 'Something went wrong';
  },
  // Returns a Boolean or a Promise.
  validate(value) {
    return new Promise(resolve => {
      resolve({
        valid: username.includes(value.toLowerCase()) ? false : !! value,
        data: { message: `${value} has already been taken` }
      });
    });
  }
};

The important things to notice in the code below are:

  • The getMessage method in the object is used to return a custom error message. The message can either be passed directly or passed via a variable from called data from the validate method
  • The validate method in the object returns a boolean, an object or a promise. If it returns an object, the valid property must be present with a boolean value. This boolean value is what is checked to see if the form is valid or not. In our case, we checked if the value is included in the list of usernames we do not want people selecting from.

Now we have a new rule. Does VeeValidate automatically know about this rule? No. How do we tell VeeValidate that it can use this new rule we just created? By extending the default Validator and passing the new rule into it:

 VeeValidate.Validator.extend('checkuser',newrule);

In this snippet, we just extended the VeeValidate validator with our own custom validator named checkuser. So we can call the Validator for any input this way:

<input v-validate="'checkuser'" type="text" name="username">

Validating A Simple Registration Page

Let us whip up a small demo. We will be validating a simple registration page. Move back into the file created at the beginning of this exercise called register.html and add the following HTML:

      <body>
        <div class="container">
            <div class="row main">
                <div class="main-login main-center">
                <h5>Sign up once and watch any of our free demos.</h5>
                    <form id="signup-form" @submit.prevent="processForm">


                        <div class="form-group">
                            <label for="name" class="cols-sm-2 control-label">Your Name</label>
                            <div class="cols-sm-10">
                                <div class="input-group">
                                    <span class="input-group-addon"><i class="fa fa-user fa" aria-hidden="true"></i></span>
                  <input type="text" name="name" placeholder="Name" :class="{ 'form-control': true, 'is-danger': errors.has('name') }" v-model="name" v-validate="'required|alpha'">

                                </div>
                 <span v-show="errors.has('name')" class="help is-danger">{{ errors.first('name') }}</span>
                            </div>
                        </div>

                        <div class="form-group">
                            <label for="email" class="cols-sm-2 control-label">Your Email</label>
                            <div class="cols-sm-10">
                                <div class="input-group">
                                    <span class="input-group-addon"><i class="fa fa-envelope fa" aria-hidden="true"></i></span>
                                    <input type="email" :class="{ 'form-control': true, 'is-danger': errors.has('email') }" name="email" placeholder="my@email.com" v-model="email" v-validate="'required|email'">

                </div>
                <span v-show="errors.has('email')" class="help is-danger">{{ errors.first('email') }}</span>
                            </div>
                        </div>

                        <div class="form-group">
                            <label for="username" class="cols-sm-2 control-label">Username</label>
                            <div class="cols-sm-10">
                                <div class="input-group">
                                    <span class="input-group-addon"><i class="fa fa-users fa" aria-hidden="true"></i></span>
                                        <input type="text" :class="{ 'form-control': true, 'is-danger': errors.has('username') }" name="username" placeholder="Enter your username" v-model="username" v-validate="'required|checkuser'">
                                </div>
                 <span v-show="errors.has('username')" class="help is-danger">{{ errors.first('username') }}</span>
                            </div>
                        </div>

                        <div class="form-group">
                            <label for="password" class="cols-sm-2 control-label">Password</label>
                            <div class="cols-sm-10">
                                <div class="input-group">
                                    <span class="input-group-addon"><i class="fa fa-lock fa-lg" aria-hidden="true"></i></span>
                                    <input type="password" :class="{ 'form-control': true, 'is-danger': errors.has('password') }" name="password" placeholder="Enter a password" v-model="password" v-validate="'required|min:6'">
                                </div>
                <span v-show="errors.has('password')" class="help is-danger">{{ errors.first('password') }}</span>
                            </div>
                        </div>

                        <div class="form-group">
                            <label for="confirm" class="cols-sm-2 control-label">Confirm Password</label>
                            <div class="cols-sm-10">
                                <div class="input-group">
                                    <span class="input-group-addon"><i class="fa fa-lock fa-lg" aria-hidden="true"></i></span>

                                <input v-validate="'required|confirmed:password'" name="password_confirmation" type="password" :class="{ 'form-control': true, 'is-danger': errors.has('password') }" placeholder="Password, Again" data-vv-as="password">
                </div>
                <span v-show="errors.has('password_confirmation')" class="help is-danger">{{ errors.first('password_confirmation') }}</span>
                            </div>
                        </div>

                        <div class="form-group ">
                            <button id="button" :disabled="errors.any()" class="btn btn-primary btn-lg btn-block login-button">Register</button>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </body>

In the code block above, we have 5 input elements which are:

  • Name: The name of the user registering. Note that the v-validate directive was also added to it, specifying required|alpha which means the input is required and can only contain alphabetic characters. Also notice a span element just after the input which shows up when errors.has('name') is true, courtesy of the v-show directive. It then displays the first error for that input via errors.first('name')
  • Email: The email of the user registering. Note that the v-validate directive was also added to it, specifying required|email which means the input is required and must contain a valid email. Like the name input, this also has a span input attached in similar fashion.
  • Username: The general username for the user registering. Note that we have something a bit different in the v-validate directive here. We passed in a rule called checkuser. Remember this custom validator that we made? Here we see it in action. Like the name and email input, this also has a span input attached in a similar fashion.
  • Password: The password the user registering must set. In the v-validate directive of this input, we have another rule specified min:6. This tells VeeValidate that the password must not be lesser than 6 characters. Like the name, email and username input, this also has a span input attached in a similar fashion.
  • Password_confirmation: Here we want the user to confirm his password. This is helpful, so he doesn't make a mistake in his password unknowingly. Notice that in the v-validate directive, we passed in confirmed:password as one of the rules? This tells VeeValidate that the value of this input must equal the value of the password input. Like the name, email and username input, this also has a span input attached in a similar fashion.

Also, our markup consists of a button, which is used to submit the form. First, I want you to take a look at the Form declaration in the markup and notice the @submit.prevent="processForm". This prevents the form from refreshing or performing any other action when this button is clicked rather than the one defined in the processForm function in our Vue methods. Also, notice that in the button, we have a little condition for the disabled property of the button :disabled="errors.any()". The errors.any() is a method exposed by VeeValidate to verify if all validations were passed or not.

Next, let's add some style to make it look great. Open a style tag and paste:

#playground-container {
    height: 500px;
    overflow: hidden !important;
    -webkit-overflow-scrolling: touch;
}
body, html{
     height: 100%;
     background-repeat: no-repeat;
     background:url(https://i.ytimg.com/vi/4kfXjatgeEU/maxresdefault.jpg);
     font-family: 'Oxygen', sans-serif;
        background-size: cover;
}

.main{
     margin:50px 15px;
}

h1.title { 
    font-size: 50px;
    font-family: 'Passion One', cursive; 
    font-weight: 400; 
}

hr{
    width: 10%;
    color: #fff;
}

.form-group{
    margin-bottom: 15px;
}

label{
    margin-bottom: 15px;
}

input,
input::-webkit-input-placeholder {
    font-size: 11px;
    padding-top: 3px;
}

.main-login{
     background-color: #fff;
    /* shadows and rounded borders */
    -moz-border-radius: 2px;
    -webkit-border-radius: 2px;
    border-radius: 2px;
    -moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
    -webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
    box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);

}
.form-control {
    height: auto!important;
padding: 8px 12px !important;
}
.input-group {
    -webkit-box-shadow: 0px 2px 5px 0px rgba(0,0,0,0.21)!important;
    -moz-box-shadow: 0px 2px 5px 0px rgba(0,0,0,0.21)!important;
    box-shadow: 0px 2px 5px 0px rgba(0,0,0,0.21)!important;
}
#button {
    border: 1px solid #ccc;
    margin-top: 28px;
    padding: 6px 12px;
    color: #666;
    text-shadow: 0 1px #fff;
    cursor: pointer;
    -moz-border-radius: 3px 3px;
    -webkit-border-radius: 3px 3px;
    border-radius: 3px 3px;
    -moz-box-shadow: 0 1px #fff inset, 0 1px #ddd;
    -webkit-box-shadow: 0 1px #fff inset, 0 1px #ddd;
    box-shadow: 0 1px #fff inset, 0 1px #ddd;
    background: #f5f5f5;
    background: -moz-linear-gradient(top, #f5f5f5 0%, #eeeeee 100%);
    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #f5f5f5), color-stop(100%, #eeeeee));
    background: -webkit-linear-gradient(top, #f5f5f5 0%, #eeeeee 100%);
    background: -o-linear-gradient(top, #f5f5f5 0%, #eeeeee 100%);
    background: -ms-linear-gradient(top, #f5f5f5 0%, #eeeeee 100%);
    background: linear-gradient(top, #f5f5f5 0%, #eeeeee 100%);
    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f5f5f5', endColorstr='#eeeeee', GradientType=0);
}
.main-center{
     margin-top: 30px;
     margin: 0 auto;
     max-width: 400px;
    padding: 10px 40px;
    background:rgb(123, 131, 134);
        color: #FFF;
    text-shadow: none;
    -webkit-box-shadow: 0px 3px 5px 0px rgba(0,0,0,0.31);
-moz-box-shadow: 0px 3px 5px 0px rgba(0,0,0,0.31);
box-shadow: 0px 3px 5px 0px rgba(0,0,0,0.31);

}
span.input-group-addon i {
    color: #009edf;
    font-size: 17px;
}

.login-button{
    margin-top: 5px;
}

.login-register{
    font-size: 11px;
    text-align: center;
}
.is-danger{
  color: red;
  font-weight: 700;
}
.help {
    background: white;
    }

Above are some CSS styles to make our page look great. Don't forget to add the following to your head tag:

<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

Above is the code to link bootstrap to our registration page. To make it much nicer.

Vue Component

Let us take a look at how our Vue Component should look now. Replace the script section where we have Vue.use(VeeValidate) with:

// Notify vue about the VeeValidate plugin
Vue.use(VeeValidate);
//declare an array of some usernames user must not input
 var username = [
    'admin',
    'password',
    'administartor'
]
//create new rule
const newrule = {
// will be added to default English messages.
  getMessage(field, params, data) {
      return (data && data.message) || 'Something went wrong';
  },
    // Returns a Boolean or a Promise.
  validate(value) {
    return new Promise(resolve => {
      resolve({
        valid: username.includes(value.toLowerCase()) ? false : !! value,
        data: { message: `${value} has already been taken` }
      });
    });
  }
};
// Tell the Validator about the new rule
  VeeValidate.Validator.extend('checkuser',newrule);
    const signupForm = new Vue({
        el: '#signup-form',
        data: {
            name: '',
            email: '',
            username: '',
            password: ''
        },
        methods: {
            processForm: function() {
        //attempt validating all
                this.$validator.validateAll().then((result) => {
                    if (result) {
            //validation passed succesfully

                       alert('Form validated succesfully');
                    }
                });
            }
        }
    });

The code block is pretty similar. First we see the custom rule we created earlier on, extended it to the default validator and mounted our Vue instance. Let's move to the methods section and see what we have in our proessForm method. Here we call the $validator.validateAll() function, which is a method exposed by VeeValidate which tries to validate all inputs and then returns a promise. We then check if the validation was succesful and triggered an alert.

If we open our template.html file in a browser, it should work as seen:

Feel free to play with the demo here

Conclusion

In this article, we have seen how to validate form inputs using the template driven approach. VeeValidate has made it very simple to validate form inputs by exposing a v-validate directive as well as giving room to extend / add custom Validators.

When next you need to validate forms in Vue, and you want it template driven, you now know the way to go.