In this post, we will learn a step-by-step guide to create a form with VueJs. We are going to cover various steps like UI design, Form Validation, and Data Fetching through Fake API. Make sure you have installed the vue-cli in your system. The vue-cli tool will save a lot of time and effort to set up the dependencies for creating a Vue project.
You may be interested in: Vue + Firebase Authentication
Packages dependencies
Dev Packages dependencies
- json-server – Creating for Fake REST API
- npm-run-all – CLI tool to run multiple npm-scripts
The very first step is to create your Vue project. We are going to use the Vue-CLI for creating the project.
vue create simple-form
The CLI tool will ask you with some default settings once you hit the enter after the above command. Don’t confuse and hit enter with the default settings. The project structure should be similar to the below screen.
Table of content
- Start with basic UI
- Working with Form Validation
- Display Validation Errors
- Fake REST API
- Reset Data After Form Submission
Start with basic UI
We are going to create the UI similar to the above screen. We are using the bootstrap 4 CSS framework. First, we start by creating the basic layout and divides our screen into 2 parts. The first and left side screen going to hold the Vue logo. And the second screen will hold our Form.
Start with App.vue
Let’s open the App.vue
file from the src directory and start creating the basic layout and the left side.
<template>
<div id="app" class="h-100">
<div class="container-fluid h-100">
<div class="row h-100">
<div class="col-md-3 vue-bg h-100 d-flex justify-content-center align-items-center">
<img alt="Vue logo" src="./assets/logo.png" width="100">
</div>
<div class="col-md-9 h-100 d-flex justify-content-center align-items-center">
<div class="col-md-8 rounded px-5 py-4 shadow bg-white text-left"></div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
.vue-bg {
background: #bce5d0;
}
</style>
The basic layout is almost ready. But why we left the right side blank? The main reason is, we are going to make a component that will handle the complete signup process.
Create SignupForm component
Let’s start by creating our component with the name SignupForm.vue
at the src/components
directory. And add the form and fields between the template syntax.
<template>
<form id="signup-form">
<div class="row">
<div class="col-12 form-group">
<label class="col-form-label col-form-label-lg">Full Name <span class="text-danger">*</span></label>
<input type="text" class="form-control form-control-lg">
</div>
<div class="col-12 form-group">
<label class="col-form-label col-form-label-lg">Email <span class="text-danger">*</span></label>
<input type="email" class="form-control form-control-lg">
</div>
<div class="col-12 form-group">
<label class="col-form-label col-form-label-lg">Country <span class="text-danger">*</span></label>
<select class="form-control form-control-lg">
<option value="">Select Country</option>
</select>
</div>
<div class="col-12 form-group">
<label class="col-form-label col-form-label-lg">Password <span class="text-danger">*</span></label>
<input type="password" class="form-control form-control-lg">
</div>
<div class="col-12 form-group text-center">
<button class="btn btn-vue btn-lg col-4">Sign Up</button>
</div>
</div>
</form>
</template>
<script>
export default {
name: 'SignupForm'
}
</script>
<style>
.btn-vue{
background: #53B985;
color: #31485D;
font-weight: bold;
}
</style>
The only thing to notice here is the name of the component that we have defined with name: 'SignupForm'
. It is not mandatory to define the name of the component but yes, it is a good practice.
Now move to the App.vue
file and import and locally register the newly created SignupForm module. The components
options is used for local registration of our module/component.
<template>
<div id="app" class="h-100">
<div class="container-fluid h-100">
<div class="row h-100">
<div class="col-md-3 vue-bg h-100 d-flex justify-content-center align-items-center">
<img alt="Vue logo" src="./assets/logo.png" width="100">
</div>
<div class="col-md-9 h-100 d-flex justify-content-center align-items-center">
<div class="col-md-8 rounded px-5 py-4 shadow bg-white text-left">
<SignupForm />
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import SignupForm from './components/SignupForm.vue'
export default {
name: 'App',
components: {
SignupForm
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
.vue-bg {
background: #bce5d0;
}
</style>
Working with Form Validation
The UI for our form is ready and time to implement the validation to our form. We are going to use the vuelidate package for form validation.
npm i vuelidate
Now open the main.js file and import the vuelidate package. We are going to use the Vue.use()
to install the vuelidate plugin for our application.
import Vue from 'vue'
import App from './App.vue'
import Vuelidate from 'vuelidate'
Vue.config.productionTip = false
Vue.use(Vuelidate);
new Vue({
render: h => h(App),
}).$mount('#app')
Now before going to the validation section, first we need to create and map the state data with the input fields. The v-model directive is used to bind the state to the input fields.
<template>
<form id="signup-form">
<div class="row">
<div class="col-12 form-group">
<label class="col-form-label col-form-label-lg">Full Name <span class="text-danger">*</span></label>
<input type="text" v-model.trim="fullname" class="form-control form-control-lg">
</div>
<div class="col-12 form-group">
<label class="col-form-label col-form-label-lg">Email <span class="text-danger">*</span></label>
<input type="email" v-model.trim="email" class="form-control form-control-lg">
</div>
<div class="col-12 form-group">
<label class="col-form-label col-form-label-lg">Country <span class="text-danger">*</span></label>
<select v-model.trim="country" class="form-control form-control-lg">
<option value="">Select Country</option>
</select>
</div>
<div class="col-12 form-group">
<label class="col-form-label col-form-label-lg">Password <span class="text-danger">*</span></label>
<input type="password" v-model.trim="password" class="form-control form-control-lg">
</div>
<div class="col-12 form-group text-center">
<button class="btn btn-vue btn-lg col-4">Sign Up</button>
</div>
</div>
</form>
</template>
<script>
export default {
name: 'SignupForm',
data: function() {
return {
fullname: '',
email: '',
country: '',
password: '',
countryList: []
}
}
}
</script>
The next step is to import the necessary validation rules from vuelidate package and use the available validations option to validate the input fields. Also, create a submit method to handle the form submission and attach it to the form through v-on directive.
<template>
<form id="signup-form" v-on:submit.prevent="submit">
<div class="row">
....
....
....
</div>
</form>
</template>
<script>
import { required, email, minLength, maxLength } from 'vuelidate/lib/validators'
export default {
name: 'SignupForm',
data: function() {
return {
fullname: '',
email: '',
country: '',
password: ''
}
},
validations: {
fullname: {required},
email: {required, email},
country: {required},
password: {required, minLength: minLength(6), maxLength: maxLength(18)}
},
methods: {
submit: function() {
this.$v.$touch();
if (this.$v.$pendding || this.$v.$error) return;
alert('Data Submit');
}
}
}
</script>
The $v
is the instance of vuelidate plugin, which can be available in your application that could be a template section or the methods. The $v
contains of the various methods and the params to handle the validation.
Display Validation Errors
The next step is to display the validation errors under the input fields. For this, we need to slightly modify our v-model directive. For example, the $v.fullname.$model
is equal to fullname
where $v.fullname.$model
has to pass the validation rules.
<input type="text" v-model.trim="$v.fullname.$model" class="form-control form-control-lg">
This is not enough, we have add an additional is-invalid
class to the invalid field based on the validation rules. Don’t worry, it’s not that hard. We are going to create a method validationStatus()
that takes the validation and returns the boolean value based on that.
validationStatus: function(validation) {
return typeof validation != "undefined" ? validation.$error : false;
}
<template>
<form id="signup-form" v-on:submit.prevent="submit">
<div class="row">
<div class="col-12 form-group">
<label class="col-form-label col-form-label-lg">Full Name <span class="text-danger">*</span></label>
<input type="text" v-model.trim="$v.fullname.$model" :class="{'is-invalid': validationStatus($v.fullname)}" class="form-control form-control-lg">
<div v-if="!$v.fullname.required" class="invalid-feedback">The full name field is required.</div>
</div>
<div class="col-12 form-group">
<label class="col-form-label col-form-label-lg">Email <span class="text-danger">*</span></label>
<input type="email" v-model.trim="$v.email.$model" :class="{'is-invalid': validationStatus($v.email)}" class="form-control form-control-lg">
<div v-if="!$v.email.required" class="invalid-feedback">The email field is required.</div>
<div v-if="!$v.email.email" class="invalid-feedback">The email is not valid.</div>
</div>
<div class="col-12 form-group">
<label class="col-form-label col-form-label-lg">Country <span class="text-danger">*</span></label>
<select v-model.trim="$v.country.$model" :class="{'is-invalid': validationStatus($v.country)}" class="form-control form-control-lg">
<option value="">Select Country</option>
</select>
<div v-if="!$v.country.required" class="invalid-feedback">The country field is required.</div>
</div>
<div class="col-12 form-group">
<label class="col-form-label col-form-label-lg">Password <span class="text-danger">*</span></label>
<input type="password" v-model.trim="$v.password.$model" :class="{'is-invalid': validationStatus($v.password)}" class="form-control form-control-lg">
<div v-if="!$v.password.required" class="invalid-feedback">The password field is required.</div>
<div v-if="!$v.password.minLength" class="invalid-feedback">You must have at least {{ $v.password.$params.minLength.min }} letters.</div>
<div v-if="!$v.password.maxLength" class="invalid-feedback">You must not have greater then {{ $v.password.$params.maxLength.min }} letters.</div>
</div>
<div class="col-12 form-group text-center">
<button class="btn btn-vue btn-lg col-4">Sign Up</button>
</div>
</div>
</form>
</template>
<script>
import { required, email, minLength, maxLength } from 'vuelidate/lib/validators'
export default {
name: 'SignupForm',
data: function() {
return {
fullname: '',
email: '',
country: '',
password: '',
countryList: []
}
},
validations: {
fullname: {required},
email: {required, email},
country: {required},
password: {required, minLength: minLength(6), maxLength: maxLength(18)}
},
methods: {
validationStatus: function(validation) {
return typeof validation != "undefined" ? validation.$error : false;
},
submit: function() {
this.$v.$touch();
if (this.$v.$pendding || this.$v.$error) return;
alert('Data Submit');
}
}
}
</script>
Fake REST API
We are going to use the json-server package to generate the fake REST API’s. We only required it for the countries for our form. We need to provide some data sources to the json-server package. Let’s just install and create a data.js
file that holds the countries list with the original and ISO formatted name.
npm i json-server --save-dev
The data.js
file should look like the below codes.
module.exports = function() {
return {
countries: [
{"iso" : "AF", "country" : "Afghanistan"},
{"iso" : "AL", "country" : "Albania"},
.....
.....
.....
.....
.....
{"iso" : "YE", "country" : "Yemen"},
{"iso" : "ZM", "country" : "Zambia"},
{"iso" : "ZW", "country" : "Zimbabwe"}
]
}
}
Now create an npm script command to run the json-server with the data source and the separate port on package.json file.
{
"name": "simple-form",
"version": "0.1.0",
"private": true,
"scripts": {
"json": "json-server src/data.js -p 4600",
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
....
....
....
....
}
You could run it separately by opening another terminal or by using an additional package i.e. npm-run-all to run more than one npm scripts at once.
npm i npm-run-all --save-dev
Now configure the npm-run-all package to package.json file.
"start": "npm-run-all -p serve json"
{
"name": "simple-form",
"version": "0.1.0",
"private": true,
"scripts": {
"json": "json-server src/data.js -p 4600",
"serve": "vue-cli-service serve",
"start": "npm-run-all -p serve json",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
....
....
....
....
}
Now we have a new command to start our project i.e.
npm run start
Now you will get the
http://localhost:4600/countries
URL once you run the commandnpm run start
to start your project.
We need an HTTP client to access the countries API endpoint provided by json-server.
npm i axios
Import and configure the axios package in the main.js
file.
import Vue from 'vue'
import App from './App.vue'
import Vuelidate from 'vuelidate'
import axios from 'axios'
Vue.config.productionTip = false
Vue.use(Vuelidate);
Vue.prototype.$http = axios;
new Vue({
render: h => h(App),
}).$mount('#app')
Go to the SignupForm component and create a lifecycle hook i.e. mounted. And add an additional state that holds all country’s data.
<script>
import { required, email, minLength, maxLength } from 'vuelidate/lib/validators'
export default {
name: 'SignupForm',
data: function() {
return {
fullname: '',
email: '',
country: '',
password: '',
countryList: []
}
},
validations: {
fullname: {required},
email: {required, email},
country: {required},
password: {required, minLength: minLength(6), maxLength: maxLength(18)}
},
mounted: function() {
var v = this;
v.$http.get(`http://localhost:4600/countries`)
.then(function(resp) {
v.countryList = resp.data;
})
.catch(function(err) {
console.log(err)
});
},
....
....
....
}
As you see in the above codes, now we have all the countries in the countryList state. Next add the countries into the select box through v-for directive.
<div class="col-12 form-group">
<label class="col-form-label col-form-label-lg">Country <span class="text-danger">*</span></label>
<select v-model.trim="$v.country.$model" :class="{'is-invalid': validationStatus($v.country)}" class="form-control form-control-lg">
<option value="">Select Country</option>
<option :value="c.iso" :key="c.iso" v-for="c in countryList">{{ c.country }}</option>
</select>
<div v-if="!$v.country.required" class="invalid-feedback">The country field is required.</div>
</div>
Reset Data After Form Submission
Create another method to reset the state of our SignupForm component.
<script>
import { required, email, minLength, maxLength } from 'vuelidate/lib/validators'
export default {
....
....
....
methods: {
resetData: function() {
this.fullname = '';
this.email = '';
this.country = '';
this.password = '';
},
....
....
....
....
....
submit: function() {
this.$v.$touch();
if (this.$v.$pendding || this.$v.$error) return;
alert('Data Submit');
this.$v.$reset();
this.resetData();
}
}
}
</script>
We hope this article will help you to learn form handling and validation with Vuejs. If you like this article then please follow us on Facebook and Twitter.