Web Developer
Simple Form Validation in Vue
Adding some simple validation for forms is important to ensure you're not receiving blank or useless data for your forms. It's also pretty easy to setup! This can be used for static forms or forms that integrate with an API such as Jotform.
1. Form Markup
<template>
<form>
<p>
<label for="fullName">Full Name<sup>*</sup></label>
<input id="fullName" class="required" type="text" name="fullName" v-model="form.fullName" required />
</p>
<p>
<label for="email">Email<sup>*</sup></label>
<input id="email" class="required" type="email" name="email" v-model="form.email" placeholder="name@domain.com" required />
</p>
<p>
<label for="phone">Phone<sup>*</sup></label>
<input id="phone" type="tel" name="phone" v-model="form.phone" placeholder="000-000-0000" />
</p>
<p>
<label for="date">Date</label>
<input id="date" type="date" name="date" v-model="form.date" placeholder="yyyy-mm-dd" />
</p>
<p>
<label for="subject">Subject<sup>*</sup></label>
<select id="subject" class="required" name="subject" v-model="form.subject" required>
<option value="">Choose One</option>
<option v-for="subject in subjects" :value="subject">{{ subject }}</option>
</select>
</p>
<p>
<label for="message">Message<sup>*</sup></label>
<textarea id="message" class="required" name="message" v-model="form.message" required></textarea>
</p>
</form>
</template>
<script>
export default {
data: () => ({
form: {
fullName: '',
email: '',
phone: '',
date: '',
subject: '',
message: ''
},
subjects: [
'General Inquiry',
'Human Resources',
'Technical Support',
]
}),
}
</script>
2. Adding Validation
With our basic form markup done, lets add our @submit
method to the form <form @submit="validateForm">
. Now we can create our validation method and target required fields using the .required
class we've added to required fields.
methods: {
validateForm() {
let valid = true
const reqFields = document.querySelectorAll(`.required`)
reqFields.forEach( field => {
if( !field.value ) {
valid = false
}
});
return valid
}
}
We can take this further and add field specific validations for inputs such as email, tel, and date by adding their own methods.
methods: {
isEmailValid(email) {
const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(email);
},
isPhoneValid(phone) { // (000) 000-0000 or (000)000-0000
const re = /^\(\d{3}\)\s?\d{3}-\d{4}$/
return re.test(phone)
},
isDateValid(dateString) { // yyyy-mm-dd
const regEx = /^\d{4}-\d{2}-\d{2}$/;
if(!dateString.match(regEx)) return false; // Invalid format
const d = new Date(dateString);
const dNum = d.getTime();
if(!dNum && dNum !== 0) return false; // NaN value, Invalid date
return d.toISOString().slice(0,10) === dateString;
},
validateForm() {
let valid = true
const reqFields = document.querySelectorAll(`.required`)
reqFields.forEach( field => {
if( !field.value ) {
valid = false
}
if( field.type === 'email' && !this.isEmailValid(field.value)) {
valid = false
}
if( field.type === 'tel' && !this.isPhoneValid(field.value)) {
valid = false
}
if( field.type === 'date' && !this.isDateValid(field.value)) {
valid = false
}
});
return valid
}
}
3. Adding Error Messaging
While validation itself is great, you probably want to add some messaging to users to indicate there were issues with validation and how they can fix them. To do this, we can add an empty array called errors to our data method.
data: () => ({
errors: []
}),
We can then push error messages to our array if there are problems with the form validation.
methods: {
isEmailValid(email) {
const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(email);
},
isPhoneValid(phone) { // (000) 000-0000 or (000)000-0000
const re = /^\(\d{3}\)\s?\d{3}-\d{4}$/
return re.test(phone)
},
isDateValid(dateString) { // yyyy-mm-dd
const regEx = /^\d{4}-\d{2}-\d{2}$/;
if(!dateString.match(regEx)) return false; // Invalid format
const d = new Date(dateString);
const dNum = d.getTime();
if(!dNum && dNum !== 0) return false; // NaN value, Invalid date
return d.toISOString().slice(0,10) === dateString;
},
validateForm() {
let valid = true
this.errors = [] // reset the errors array on each submit so we don't keep adding messages
const reqFields = document.querySelectorAll(`.required`)
reqFields.forEach( field => {
if( !field.value ) {
valid = false
this.errors.push('Please fill all required fields indicated by *')
}
if( field.type === 'email' && !this.isEmailValid(field.value)) {
valid = false
this.errors.push('The email entered isn\'t valid. Emails are formatted as name@domain.com')
}
if( field.type === 'tel' && !this.isPhoneValid(field.value)) {
valid = false
this.errors.push('The phone number entered isn\'t valid. Phone numbers are formatted as (000) 000-0000')
}
if( field.type === 'date' && !this.isDateValid(field.value)) {
valid = false
this.errors.push('The date entered isn\'t valid. Dates are formatted as yyyy-mm-dd')
}
});
return valid
}
}
Next, we will want to display these errors somewhere in our form.
<p v-if="errors.length">
<strong>Please correct the following error(s):</strong>
<ul>
<li v-for="error,idx in errors" :key="`err-${idx}`">{{ error }}</li>
</ul>
</p>
4. Input Masking
A final piece you could add for a further improved user experience is input masking. For this, you could use an existing library like v-mask or maska. In our case, we are going to add input masking to only the phone field, so adding a whole library doesn't make sense. Let's create our masking method.
methods: {
maskPhone(e) {
const x = e.target.value.replace(/\D/g, '').match(/(\d{0,3})(\d{0,3})(\d{0,4})/);
e.target.value = !x[2] ? x[1] : '(' + x[1] + ') ' + x[2] + (x[3] ? '-' + x[3] : '');
}
}
Then we can call our method on the phone input.
<p>
<label for="phone">Phone<sup>*</sup></label>
<input id="phone" type="tel" name="phone" v-model="form.phone" @input="maskPhone" placeholder="000-000-0000" />
</p>
5. Putting it all together
Now we can put all of this together!
<template>
<form>
<p v-if="errors.length">
<strong>Please correct the following error(s):</strong>
<ul>
<li v-for="error,idx in errors" :key="`err-${idx}`">{{ error }}</li>
</ul>
</p>
<p>
<label for="fullName">Full Name<sup>*</sup></label>
<input id="fullName" class="required" type="text" name="fullName" v-model="form.fullName" required />
</p>
<p>
<label for="email">Email<sup>*</sup></label>
<input id="email" class="required" type="email" name="email" v-model="form.email" placeholder="name@domain.com" required />
</p>
<p>
<label for="phone">Phone<sup>*</sup></label>
<input id="phone" type="tel" name="phone" v-model="form.phone" @input="maskPhone" placeholder="000-000-0000" />
</p>
<p>
<label for="date">Date</label>
<input id="date" type="date" name="date" v-model="form.date" placeholder="yyyy-mm-dd" />
</p>
<p>
<label for="subject">Subject<sup>*</sup></label>
<select id="subject" class="required" name="subject" v-model="form.subject" required>
<option value="">Choose One</option>
<option v-for="subject in subjects" :value="subject">{{ subject }}</option>
</select>
</p>
<p>
<label for="message">Message<sup>*</sup></label>
<textarea id="message" class="required" name="message" v-model="form.message" required></textarea>
</p>
</form>
</template>
<script>
export default {
data: () => ({
form: {
fullName: '',
email: '',
phone: '',
date: '',
subject: '',
message: ''
},
subjects: [
'General Inquiry',
'Human Resources',
'Technical Support',
],
errors: []
}),
methods: {
maskPhone(e) {
const x = e.target.value.replace(/\D/g, '').match(/(\d{0,3})(\d{0,3})(\d{0,4})/);
e.target.value = !x[2] ? x[1] : '(' + x[1] + ') ' + x[2] + (x[3] ? '-' + x[3] : '');
},
isEmailValid(email) {
const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(email);
},
isPhoneValid(phone) { // (000) 000-0000 or (000)000-0000
const re = /^\(\d{3}\)\s?\d{3}-\d{4}$/
return re.test(phone)
},
isDateValid(dateString) { // yyyy-mm-dd
const regEx = /^\d{4}-\d{2}-\d{2}$/;
if(!dateString.match(regEx)) return false; // Invalid format
const d = new Date(dateString);
const dNum = d.getTime();
if(!dNum && dNum !== 0) return false; // NaN value, Invalid date
return d.toISOString().slice(0,10) === dateString;
},
validateForm() {
let valid = true
this.errors = [] // reset the errors array on each submit so we don't keep adding messages
const reqFields = document.querySelectorAll(`.required`)
reqFields.forEach( field => {
if( !field.value ) {
valid = false
this.errors.push('Please fill all required fields indicated by *')
}
if( field.type === 'email' && !this.isEmailValid(field.value)) {
valid = false
this.errors.push('The email entered isn\'t valid. Emails are formatted as name@domain.com')
}
if( field.type === 'tel' && !this.isPhoneValid(field.value)) {
valid = false
this.errors.push('The phone number entered isn\'t valid. Phone numbers are formatted as (000) 000-0000')
}
if( field.type === 'date' && !this.isDateValid(field.value)) {
valid = false
this.errors.push('The date entered isn\'t valid. Dates are formatted as yyyy-mm-dd')
}
});
if( valid ) {
// submit the form to your API
}
return valid
}
}
}
</script>