Vueでform validation
フォームの初期状態
下記のようなファイル構成。
. ├── app.js └── index.html
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Vue form validation</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"> <script src="https://unpkg.com/vue/dist/vue.js"></script> </head> <body> <div id="app"> <div class="container"> <h1>Vue form validation</h1> <form @submit.prevent="submitForm" autocomplete="off"> <div class="form-group"> <label for="name">Name(Only string):</label> <input v-model="form.name" class="form-control" id="name"> <p v-if="!nameIsValid" class="error-message">The name field is required</p> </div> <div class="form-group"> <label for="age">Age(Between 18 and 108):</label> <input v-model="form.age" class="form-control" id="age"> <p v-if="!ageIsValid" class="error-message">The age field is invalid</p> </div> <div class="form-group form-group-last"> <button class="btn btn-primary" :disabled="!formIsValid">Submit</button> <p v-show="form.sent" class="mt-2 text-info">Form has been successfully sent!</p> </div> </form> </div> </div> <script src="app.js"></script> </body> </html>
app.js
/* global Vue */ // eslint-disable-next-line no-unused-vars const app = new Vue({ el: '#app', data: { form: { name: null, age: null, sent: false } }, computed: { nameIsValid () { return !Number(this.form.name) && !!this.form.name }, ageIsValid () { return !!Number(this.form.age) && this.form.age >= 18 && this.form.age <= 108 }, formIsValid () { return this.nameIsValid && this.ageIsValid } }, methods: { submitForm () { if (!this.formIsValid) return this.form.sent = true } } })
内容としては、名前と年齢を入力してフォームを送信するというシンプルなもの。 名前には数字は入力できず、年齢には18-108間の数字のみが入力できる。
できるだけhtmlのinput側では制御せず、vueの機能を用いてリファクタリングしていってみる。
まずはinputへの型指定だが、v-model
に修飾子を付けることで行うことができる。
v-model.number
で数値型に変換してくれるので、数値になってるかどうかのチェックが不要になる。
Before
<input v-model.number="form" class="form-control" id="age">
ageIsValid () { return !!Number(this.form.age) && this.form.age >= 18 && this.form.age <= 108 },
After
<input v-model.number="form.age" class="form-control" id="age">
ageIsValid () { return this.form.age >= 18 && this.form.age <= 108 },
しかしながら、初期状態でエラー文言が掲出されたままであったりして、まだこのままでは使い勝手がよくない。 一つひとつのエラー文言のハンドリングをしようとすると手間がかかってしまう。
そこで、Vueでvalidateのできるライブラリを用いる。
vuelidateを用いてリファクタリング
appVuelidate.vue
<template> <div id="app"> <div class="container"> <h1>Vue form validation</h1> <form @submit.prevent="submitForm" autocomplete="off"> <!-- To validate on input synchronously, remove @blur and add v-model.trim="$v.form.name.$model" --> <!-- $error === $invalid && $dirty --> <div class="form-group"> <label for="name">Name(Only string):</label> <input v-model.trim="form.name" @blur="$v.form.name.$touch()" :class="{ 'input-error': $v.form.name.$error, 'input-valid': !$v.form.name.$invalid }" class="form-control" id="name" /> <template v-if="$v.form.name.$error"> <p v-if="!$v.form.name.alpha" class="text-danger" >The name field should be alphabet characters</p> <p v-else class="text-danger">The name field is required</p> </template> <p class="text-info" >Invalid: {{ $v.form.name.$invalid }} | Dirty: {{ $v.form.name.$dirty }} | Error: {{ $v.form.name.$error }}</p> </div> <div class="form-group"> <label for="age">Age(Between 18 and 108):</label> <input v-model.number.trim="form.age" @blur="$v.form.age.$touch()" :class="{ 'input-error': $v.form.age.$error, 'input-valid': !$v.form.age.$invalid }" class="form-control" id="age" /> <template v-if="$v.form.age.$error"> <p v-if="!$v.form.age.required" class="text-danger">The age field is required</p> <p v-else-if="!$v.form.age.integer" class="text-danger" >The age field should be an integer</p> <p v-else-if="!$v.form.age.between" class="text-danger" >You should be at least 18 and younger than 108 to coutinue</p> </template> <p class="text-info" >Invalid: {{ $v.form.age.$invalid }} | Dirty: {{ $v.form.age.$dirty }} | Error: {{ $v.form.age.$error }}</p> </div> <button :disabled="$v.form.$invalid" class="btn btn-primary">Submit</button> <p v-show="form.sent" class="mt-2 text-success">Form has been successfully sent!</p> </form> </div> </div> </template> <script> import "bootstrap/dist/css/bootstrap.min.css"; import { validationMixin } from "vuelidate"; import * as validators from "vuelidate/lib/validators"; export default { data() { return { form: { name: null, age: null, sent: false } }; }, mixins: [validationMixin], validations: { form: { name: { required: validators.required, alpha: validators.alpha }, age: { required: validators.required, integer: validators.integer, between: validators.between(18, 108) } } }, methods: { submitForm() { this.$v.form.$touch(); if (!this.$v.form.$invalid) { this.form.sent = true; return; } this.form.sent = false; } } }; </script> <style> #app .input-valid { border-color: rgba(126, 239, 104, 0.8); box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 8px rgba(126, 239, 104, 0.6); } #app .input-error { border-color: tomato; box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 8px tomato; } </style>
VeeValidateを用いてリファクタリング
appVeeValidate.vue
<template> <div class="container"> <h1>Vue form validation</h1> <form @submit.prevent="submitForm" autocomplete="off"> <div class="form-group"> <label for="name">Name(Only alphabetic characters):</label> <input v-model.trim="form.name" v-validate="'alpha_spaces'" name="name" class="form-control" id="name" /> <p class="text-danger">{{ errors.first('name') }}</p> </div> <div class="form-group"> <label for="age">Age(Between 18 and 108):</label> <input v-model.number.trim="form.age" v-validate="'between:18,108'" name="age" class="form-control" id="age" /> <p class="text-danger">{{ errors.first('age') }}</p> </div> <button class="btn btn-primary">Submit</button> </form> </div> </template> <script> import { ValidationProvider } from "vee-validate"; export default { data() { return { form: { name: null, age: null, sent: false } }; }, components: { ValidationProvider }, methods: { submitForm() {} } }; </script>