Nuxt.jsアプリにおいて、VeeValidate はフォームバリデーションを容易に実現してくれるパッケージです。ここではNuxt.js + TypeScript + VeeValidate (ValidationObserver & ValidationProvider) の導入手順を紹介します。
Demoは こちら
パッケージバージョン情報
nuxt: 2.12.2
vee-validate: 3.3.0
TypeScript: 3.8.3
tailwindcss: 1.4.1
tailwindcss/custom-forms: 0.2.1
- VeeValidateのインストール
- Pluginの作成
- ValidationObserver & ValidationProviderの作成
VeeValidateのインストール
yarn add vee-validate
or
npm i -S vee-validate
Pluginの作成
veeValidate.ts
import Vue from 'vue'
import { ValidationProvider, ValidationObserver, extend } from 'vee-validate'
import * as rules from 'vee-validate/dist/rules'
// 全てのルールを利用
Object.keys(rules).forEach((rule) => {
extend(rule, rules[rule])
})
Vue.component('ValidationProvider', ValidationProvider)
Vue.component('ValidationObserver', ValidationObserver)
nuxt.config.js
module.exports = {
...
plugins: [
{ src: '~/plugins/veeValidate', ssr: false },
],
...
}
ValidationObserver & ValidationProviderの作成
ValidationObserver
ValidationObserverはこの内部で定義されるValidationProviderコンポーネント(バリデーション対象フォーム要素)の状態を監視します。
ValidationProviderでは、バリデーション対象となるinput, select, checkbox, radioなどのフォーム要素を定義し、そこにルール、エラーメッセージ等を適用させます。
今回は、 InputForm.vue
, SelectForm.vue
, CheckForm.vue
, RadioForm.vue
コンポーネントを用意しそれぞれで ValidationProviderを利用します。
ValidationObserverで管理される invalid
dataを利用してボタンの活性化をコントロールします。
pages/validate.vue
<template>
<div class="container">
<validation-observer v-slot="{ invalid }" tag="div">
<section class="mb-20">
<div class="mb-10">
<h2 class="mb-4 text-xl font-bold text-gray-700">
<fa icon="leaf" aria-hidden="true" class="mr-2" />Input forms
</h2>
<div class="w-full mb-6">
<input-form
v-model="text"
:error-messages="{
required: '必須項目です',
alpha_spaces: '半角英字で入力してください',
}"
placeholder="半角英字"
field-name="Input Alpha"
rules="required|alpha_spaces"
type="text"
/>
</div>
<div class="w-full mb-6">
<input-form
v-model="email"
:error-messages="{
required: '必須項目です',
email: '不正なメールアドレスです',
}"
placeholder="メールアドレス"
field-name="Input email"
rules="required|email"
type="email"
/>
</div>
<div class="w-full mb-6">
<input-form
v-model="number"
:error-messages="{
required: '必須項目です',
numeric: '数値で入力してください',
}"
placeholder="数値"
field-name="Input number"
rules="required|numeric"
type="number"
pattern="\d*"
/>
</div>
</div>
<div class="mb-10">
<h2 class="mb-4 text-xl font-bold text-gray-700">
<fa icon="leaf" aria-hidden="true" class="mr-2" />Select form
</h2>
<div class="w-full mb-6">
<select-form
v-model="option1"
:options="options1"
:error-messages="{
required: '必須項目です',
}"
placeholder="選択してください"
field-name="Selectbox"
rules="required"
/>
</div>
</div>
<div class="mb-10">
<h2 class="mb-4 text-xl font-bold text-gray-700">
<fa icon="leaf" aria-hidden="true" class="mr-2" />Radio form
</h2>
<div class="w-full mb-6">
<radio-form
v-model="option2"
:options="options2"
:error-messages="{
required: '必須項目です',
}"
placeholder="選択してください"
field-name="Radio"
rules="required"
/>
</div>
</div>
<div class="mb-10">
<h2 :class="['mb-4 text-xl font-bold text-gray-700']">
<fa icon="leaf" aria-hidden="true" class="mr-2" />Checkbox form
</h2>
<div class="w-full mb-6">
<check-form
v-model="option3"
:options="options3"
:error-messages="{
required: '必須項目です',
}"
placeholder="選択してください"
field-name="Checkbox"
rules="required"
/>
</div>
</div>
<h2 class="mb-4 text-xl font-bold text-gray-700">
<fa icon="leaf" aria-hidden="true" class="mr-2" />Password forms
</h2>
<div class="w-full mb-6">
<input-form
v-model="password"
vid="confirmation"
:error-messages="{
required: '必須項目です',
alpha_num: '半角英数字で入力してください',
min: '8-20文字で入力してください',
max: '8-20文字で入力してください',
}"
placeholder="パスワード"
field-name="Input password"
rules="required|alpha_num|min:8|max:20"
type="password"
/>
</div>
<div class="w-full mb-6">
<input-form
v-model="passwordConfirmation"
:error-messages="{
required: '必須項目です',
confirmed: 'パスワードが一致しません',
}"
placeholder="パスワード(確認)"
field-name="Input password (confirmation)"
rules="required|confirmed:confirmation"
type="password"
/>
</div>
</section>
<button
:class="{ 'cursor-not-allowed opacity-50': invalid }"
class="bg-teal-600 hover:bg-teal-500 text-white font-bold rounded w-full py-3"
@click="onClickSubmit"
>
Submit
</button>
</validation-observer>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'
import InputForm from '~/components/form/InputForm.vue'
import SelectForm from '~/components/form/SelectForm.vue'
import RadioForm from '~/components/form/RadioForm.vue'
import CheckForm from '~/components/form/CheckForm.vue'
type formOption = {
label: string
value: string
}
@Component({
name: 'ValidateIndex',
components: {
InputForm,
SelectForm,
RadioForm,
CheckForm,
},
})
export default class Validate extends Vue {
private text: string = ''
private email: string = ''
private number: string = ''
private option1: string = ''
private option2: string = ''
private option3: string[] = []
private password: string = ''
private passwordConfirmation: string = ''
private options1: formOption[] = [
{ label: 'Option 1', value: '1' },
{ label: 'Option 2', value: '2' },
{ label: 'Option 3', value: '3' },
{ label: 'Option 4', value: '4' },
{ label: 'Option 5', value: '5' },
]
private options2: formOption[] = [
{ label: 'Option 1', value: '1' },
{ label: 'Option 2', value: '2' },
{ label: 'Option 3', value: '3' },
{ label: 'Option 4', value: '4' },
{ label: 'Option 5', value: '5' },
{ label: 'Option 6', value: '6' },
{ label: 'Option 7', value: '7' },
{ label: 'Option 8', value: '8' },
]
private options3: formOption[] = [
{ label: 'Option 1', value: '1' },
{ label: 'Option 2', value: '2' },
{ label: 'Option 3', value: '3' },
{ label: 'Option 4', value: '4' },
{ label: 'Option 5', value: '5' },
{ label: 'Option 6', value: '6' },
{ label: 'Option 7', value: '7' },
{ label: 'Option 8', value: '8' },
]
private onClickSubmit() {
alert('SUBMIT')
}
}
</script>
<style>
.container {
margin: 0 auto;
min-height: 100vh;
max-width: 600px;
padding: 10vh 0;
}
</style>
ValidationProvider
components/InputForm.vue
<template>
<validation-provider
v-slot="{ valid, errors }"
:rules="rules"
:name="fieldName"
:custom-messages="errorMessages"
:vid="vid"
:mode="mode"
>
<label
class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"
for="grid-password"
>
{{ fieldName }}
</label>
<input
v-model="innerValue"
:name="fieldName"
:placeholder="placeholder"
:class="[
'form-input mt-1 block w-full',
{ 'border-red-600': errors.length },
{ 'border-teal-600': valid },
]"
:type="type"
:pattern="pattern"
/>
<p v-show="errors.length" class="absolute text-red-600 text-xs">
{{ errors[0] }}
</p>
</validation-provider>
</template>
<script lang="ts">
import { Component, Vue, Prop } from 'nuxt-property-decorator'
@Component({
name: 'InputForm',
})
export default class InputForm extends Vue {
@Prop({ type: String, required: true })
private rules!: string
@Prop({ type: Object, required: true })
private errorMessages!: object
@Prop({ type: String, required: true })
private value!: string
@Prop({ type: String, required: true })
private fieldName!: string
@Prop({ type: String, default: '' })
private placeholder!: string
@Prop({ type: String, default: 'text' })
private type!: string
@Prop({ type: String, default: '' })
private vid!: string
@Prop({ type: String, default: 'eager' })
private mode!: string
@Prop({ type: String, default: '' })
private pattern!: string
get innerValue(): string {
return this.$props.value
}
set innerValue(val) {
this.$emit('input', val)
}
}
</script>
components/SelectForm.vue
<template>
<validation-provider
v-slot="{ valid, errors }"
:rules="rules"
:name="fieldName"
:custom-messages="errorMessages"
:vid="vid"
:mode="mode"
>
<label
class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"
for="grid-password"
>
{{ fieldName }}
</label>
<select
v-model="innerValue"
:name="fieldName"
:class="[
'form-select mt-1 block w-full',
{ 'border-red-600': errors.length },
{ 'border-teal-600': valid },
{ 'text-gray-700': selected },
{ 'text-gray-500': !selected },
]"
@change="selected = true"
>
<option value selected disabled hidden>
{{ placeholder }}
</option>
<option v-for="(item, i) in options" :key="i" :value="item.value">
{{ item.label }}
</option>
</select>
<p v-show="errors.length" class="absolute text-red-600 text-xs">
{{ errors[0] }}
</p>
</validation-provider>
</template>
<script lang="ts">
import { Component, Vue, Prop } from 'nuxt-property-decorator'
type option = {
label: string
value: string
}
@Component({
name: 'SelectForm',
})
export default class SelectForm extends Vue {
private selected: boolean = false
@Prop({ type: String, required: true })
private rules!: string
@Prop({ type: Object, required: true })
private errorMessages!: object
@Prop({ type: String, required: true })
private value!: string
@Prop({ type: String, required: true })
private fieldName!: string
@Prop({ type: Array, required: true })
private options!: option[]
@Prop({ type: String, default: '' })
private placeholder!: string
@Prop({ type: String, default: '' })
private vid!: string
@Prop({ type: String, default: 'aggressive' })
private mode!: string
@Prop({ type: String, default: '' })
private pattern!: string
get innerValue(): string {
return this.$props.value
}
set innerValue(val) {
this.$emit('input', val)
}
}
</script>
components/CheckForm.vue
<template>
<validation-provider
v-slot="{ valid, errors }"
:rules="rules"
:name="fieldName"
:custom-messages="errorMessages"
:vid="vid"
:mode="mode"
>
<label
class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"
for="grid-password"
>
{{ fieldName }}
</label>
<div class="flex flex-wrap">
<div v-for="(item, i) in options" :key="i" class="py-1 px-2">
<label>
<input
v-model="innerValue"
:value="item.value"
:class="[
'form-checkbox h-6 w-6 text-teal-600',
{ 'border-red-600': errors.length },
]"
type="checkbox"
/>
<span class="ml-2">{{ item.label }}</span>
</label>
</div>
</div>
<p v-show="errors.length" class="absolute text-red-600 text-xs">
{{ errors[0] }}
</p>
</validation-provider>
</template>
<script lang="ts">
import { Component, Vue, Prop } from 'nuxt-property-decorator'
type option = {
label: string
value: string
}
@Component({
name: 'RadioForm',
})
export default class RadioForm extends Vue {
@Prop({ type: String, required: true })
private rules!: string
@Prop({ type: Object, required: true })
private errorMessages!: object
@Prop({ type: Array, required: true })
private value!: string[]
@Prop({ type: String, required: true })
private fieldName!: string
@Prop({ type: Array, required: true })
private options!: option[]
@Prop({ type: String, default: '' })
private placeholder!: string
@Prop({ type: String, default: '' })
private vid!: string
@Prop({ type: String, default: 'aggressive' })
private mode!: string
@Prop({ type: String, default: '' })
private pattern!: string
get innerValue(): string {
return this.$props.value
}
set innerValue(val) {
this.$emit('input', val)
}
}
</script>
components/RadioForm.vue
<template>
<validation-provider
v-slot="{ valid, errors }"
:rules="rules"
:name="fieldName"
:custom-messages="errorMessages"
:vid="vid"
:mode="mode"
>
<label
class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2"
for="grid-password"
>
{{ fieldName }}
</label>
<div class="flex flex-wrap">
<div v-for="(item, i) in options" :key="i" class="py-1 px-2">
<label>
<input
:id="`option-${item.value}`"
v-model="innerValue"
:name="fieldName"
:value="item.value"
type="radio"
:class="[
'form-radio h-6 w-6 text-teal-600',
{ 'border-red-600': errors.length },
]"
/>
<span class="ml-2">{{ item.label }}</span>
</label>
</div>
</div>
<p v-show="errors.length" class="absolute text-red-600 text-xs">
{{ errors[0] }}
</p>
</validation-provider>
</template>
<script lang="ts">
import { Component, Vue, Prop } from 'nuxt-property-decorator'
type option = {
label: string
value: string
}
@Component({
name: 'RadioForm',
})
export default class RadioForm extends Vue {
@Prop({ type: String, required: true })
private rules!: string
@Prop({ type: Object, required: true })
private errorMessages!: object
@Prop({ type: String, required: true })
private value!: string
@Prop({ type: String, required: true })
private fieldName!: string
@Prop({ type: Array, required: true })
private options!: option[]
@Prop({ type: String, default: '' })
private placeholder!: string
@Prop({ type: String, default: '' })
private vid!: string
@Prop({ type: String, default: 'aggressive' })
private mode!: string
@Prop({ type: String, default: '' })
private pattern!: string
get innerValue(): string {
return this.$props.value
}
set innerValue(val) {
this.$emit('input', val)
}
}
</script>
以上