ブログ
これまでに経験してきたプロジェクトで気になる技術の情報を紹介していきます。
vee-validateをvue3で使ってみる
vee-validate
参考サイト
- https://vee-validate.logaretm.com/v4/guide/overview
- https://github.com/logaretm/vuejslive-2021-vee-validate-workshop/tree/main/src/Examples
- https://github.com/jquense/yup
install
npm i vee-validate@next --save
フィールドレベルのバリデーション
検証フィールドごとにバリデーションする
Field, Form, ErrorMessage コンポネントをインポート
- Field:検証するフィールドのコンポネント
- Form: フォームのコンポネント
- ErrorMessage: エラーメッセージを出すコンポネント
name
フィールドを作成
type
属性をテキスト(デフォルト)
isRequired
ルールを methods
で作成
Field
コンポネントの `rules に登録
<template>
<Form>
<div>
<h2 class="header1">name</h2>
<Field name="field" :rules="isRequired" />
<div><ErrorMessage name="field" /></div>
</div>
</Form>
</template>
<script>
import { Field, Form, ErrorMessage } from 'vee-validate';
export default {
components: {
Field,
Form,
ErrorMessage,
},
methods: {
isRequired(value) {
if (value && value.trim()) {
return true;
}
return 'This is required';
},
},
};
</script>
フォームレベルのバリデーション
フォームで各フィールドをまとめてバリデーションする
data
でバリデーションスキーマを作成
Form
コンポネントの validation-schema
に登録
フィールド名をコンテキストから取得
<template>
<Form :validation-schema="validation_schema">
<div>
<h2 class="header1">name</h2>
<Field name="name" type="text" />
<div><ErrorMessage name="name" /></div>
<div>
</Form>
</template>
<script>
// Field, Form, ErrorMessage コンポネントをインポート
import { Field, Form, ErrorMessage } from 'vee-validate';
export default {
components: {
Field,
Form,
ErrorMessage,
},
data() {
const validation_schema = {
name(value) {
// trueの場合
if (value && value.trim()) {
return true;
}
// falseの場合
return "This field is required";
},
email(value) {
// validate email value...
},
password(value) {
// validate password value...
},
};
return {
simpleSchema,
};
},
};
</script>
yup バリデータを使用する
javascriptのバリデーションライブラリとして有名なyupを使用する
npm i yup --save
yupをインポート
'email'フィールドに string
スキーマの「必須」と「メールアドレス型」を検証
'password'フィールドに string
スキーマの「必須」と「8文字以上」を検証
メッセージはファンクションに引数として与える
<template>
<Form :validation-schema="validation_schema">
<div>
<h2 class="header1">email</h2>
<Field name="email" type="email" />
<div><ErrorMessage name="email" /></div>
</div>
<div>
<h2 class="header1">password</h2>
<Field name="password" type="password" />
<div><ErrorMessage name="password" /></div>
</div>
</Form>
</template>
<script>
// Field, Form, ErrorMessage コンポネントをインポート
import { Field, Form, ErrorMessage } from 'vee-validate';
// yup をインポート
import * as yup from "yup";
export default {
components: {
Field,
Form,
ErrorMessage,
},
data() {
const validation_schema = {
email: yup.string().required('email is required!').email('email is required!'),
password: yup
.string()
.required("password is required!")
.min(8, `password must be at least 8 characters!`),
};
},
};
</script>
バリデーションするイベントの変更
全部ONにする
<script>
// configureを追加でインポート
import { Form, Field, ErrorMessage, configure } from "vee-validate";
configure({
validateOnBlur: true, // `blur`イベントで検証をトリガー
validateOnChange: true, // `change`イベントで検証をトリガー
validateOnInput: true, // `input`イベントで検証をトリガー
validateOnModelUpdate: true, // update:modelValueイベントで検証をトリガー
});
</script>
エラーとバリューとメタの取得
スコープ付きスロットに設定してある values
、 errors
、 meta
を呼び出して使用する
- values: 入力した値のオブジェクト
- errors: バリデーションエラーのオブジェクト
- meta.initialValues: フィールドの初期値、リセットするとこの値に戻る
- meta.touched: フィールドにふれたらtrue
- meta.valid: すべてのフィールドが有効ならtrue
- meta.dirty: フィールドの値に変更があればtrue
Form
コンポネントの v-slot
に initial-values
と initial-errors
を追加する
値を data
で設定する
- initial-values: フィールドの初期値を設定する
- initial-errors: エラーの初期値を設定する
<template>
<!-- スロットの呼び出し -->
<Form
v-slot="{
values,
errors,
meta,
}"
:validation-schema="validation_schema"
:initial-values="initial_values"
:initial-errors="initial_errors"
>
<div>
<h2 class="header1">name</h2>
<Field name="name" type="text" />
<div><ErrorMessage name="name" /></div>
<div>
<div>
<h2 class="header1">email</h2>
<Field name="email" type="email" />
<div><ErrorMessage name="email" /></div>
</div>
<div>
<h2 class="header1">password</h2>
<Field name="password" type="password" />
<div><ErrorMessage name="password" /><div>
</div>
<div>
<!-- valuesを表示 -->
<h2>values</h2>
<pre>{{ values }}</pre>
</div>
<div>
<!-- errorsを表示 -->
<h2>errors</h2>
<pre>{{ errors }}</pre>
</div>
<div>
<!-- meta.initialValuesを表示 -->
<h2>meta.initialValues</h2>
<pre>{{ meta.initialValues }}</pre>
</div>
<div>
<!-- meta.touchedを表示 -->
<h2>meta.touched</h2>
<span>{{ meta.touched }}</span>
</div>
<div>
<!-- meta.validを表示 -->
<h2>meta.valid</h2>
<span>{{ meta.valid }}</span>
</div>
<div>
<!-- meta.dirtyを表示 -->
<h2>meta.dirty</h2>
<span>{{ meta.dirty }}</span>
</div>
</Form>
</template>
<script>
// インポート
import { Form, Field, ErrorMessage, configure } from "vee-validate";
// yup をインポート
import * as yup from "yup";
configure({
validateOnBlur: true, // `blur`イベントで検証をトリガー
validateOnChange: true, // `change`イベントで検証をトリガー
validateOnInput: true, // `input`イベントで検証をトリガー
validateOnModelUpdate: true, // update:modelValueイベントで検証をトリガー
});
export default {
components: {
Field,
Form,
ErrorMessage,
},
data() {
const validation_schema = {
name(value) {
// trueの場合
if (value && value.trim()) {
return true;
}
// falseの場合
return "This field is required";
},
email: yup.string().required('email is required!').email('email is required!'),
password: yup
.string()
.required("password is required!")
.min(8, `password must be at least 8 characters!`),
};
return {
validation_schema,
};
},
};
</script>
サブミットとリセット
submit
ボタンを追加する
Form
コンポネント内なのですべて有効にならないとサブミットしない
Form
コンポネント内なのでデフォルトではリロードしない
methods
に onSubmit
を作成
Form
コンポネントの submit
イベントに onSubmit
を登録
meta.valid
が true
になるまで disable
にする
送信中は isSubmitting
を使って disable
にする
submitCount
を使って送信回数を表示する
reset
ボタンを追加する
Form
コンポネント内なので meta.initialValues
に設定してある値にリセットされる
meta.dirty
がtrueになるまで disable
にする-->
<template>
<!-- submitイベントにメソッドを追加 -->
<Form
v-slot="{
values,
errors,
meta,
isSubmitting,
submitCount
}"
:validation-schema="validation_schema"
:initial-values="initial_values"
:initial-errors="initial_errors"
@submit="onSubmit"
>
<div>
<h2 class="header1">name</h2>
<Field name="name" type="text" />
<div><ErrorMessage name="name" /></div>
<div>
<div>
<h2 class="header1">email</h2>
<Field name="email" type="email" />
<div><ErrorMessage name="email" /></div>
</div>
<div>
<h2 class="header1">password</h2>
<Field name="password" type="password" />
<div><ErrorMessage name="password" /><div>
</div>
<!--サブミットボタンを追加-->
<!--meta.validがtrueになるまで`disable`にする-->
<!--送信中は`isSubmitting`を使って`disable`にする-->
<button type="submit" :disabled="!meta.valid || isSubmitting">
send <span>{{ submitCount }}</span>
</button>
<!--サブミットボタンを追加-->
<button type="reset" :disabled="!meta.dirty">Reset</button>
</Form>
</template>
<script>
// インポート
import { Form, Field, ErrorMessage, configure } from "vee-validate";
// yup をインポート
import * as yup from "yup";
configure({
validateOnBlur: true, // `blur`イベントで検証をトリガー
validateOnChange: true, // `change`イベントで検証をトリガー
validateOnInput: true, // `input`イベントで検証をトリガー
validateOnModelUpdate: true, // update:modelValueイベントで検証をトリガー
});
export default {
components: {
Field,
Form,
ErrorMessage,
},
data() {
// validation schema
const validation_schema = {
name(value) {
// trueの場合
if (value && value.trim()) {
return true;
}
// falseの場合
return "This field is required";
},
email: yup.string().required('email is required!').emai ('email is required!'),
password: yup
.string()
.required("password is required!")
.min(8, `password must be at least 8 characters!`),
};
// initial values
const initial_values = {
name: "user",
email: "user@example.com",
password: "useruser",
};
// initial errors
const initial_values = {
name: "This name is already taken",
email: "This email is already taken",
password: "The password is too short",
};
return {
validation_schema,
initial_values,
initial_errors,
};
},
methods: {
// 3秒後にレスポンス
async onSubmit(values, { resetForm }) {
await new Promise((resolve) => setTimeout (resolve, 3000));
console.log(values);
resetForm();
},
},
};
</script>
カスタムバリデーションとフィールド名の変更
defineRule
を追加でインポート
defineRule
で新しいバリデーションを作成
Field
コンポネントの label
でフィールド名を変更
<template>
<Form
v-slot="{
meta,
isSubmitting,
}"
:validation-schema="validation_schema"
@submit="onSubmit"
>
<div>
<h2 class="header1">password</h2>
<Field name="password" type="password" />
<!-- confirmationのフィールドを追加 -->
<Field name="confirmation" type="password" label="☆confirmation☆" />
<div><ErrorMessage name="password" /></div>
<!-- confirmationのエラーメッセージを追加 -->
<div><ErrorMessage name="confirmation" /></div>
</div>
<div>
<h2 class="header1">message</h2>
<!-- messageのフィールドを追加 -->
<Field name="message" type="text" label="☆message☆" />
<!-- messageのエラーメッセージを追加 -->
<div><ErrorMessage name="message" /></div>
</div>
<div>
<h2 class="header1">age</h2>
<!-- ageのフィールドを追加 -->
<Field name="age" type="number" label="☆age☆" />
<!-- ageのエラーメッセージを追加 -->
<div><ErrorMessage name="age" /></div>
</div>
<button type="submit" :disabled="!meta.valid || isSubmitting">
send <span>{{ submitCount }}</span>
</button>
<button type="reset" :disabled="!meta.dirty">Reset</button>
</Form>
</template>
<script>
import { Form, Field, ErrorMessage, defineRule } from "vee-validate";
import * as yup from "yup";
// 「必須」を追加
defineRule("required", (value, [], context) => {
if (!value || !value.length) {
return `${context.field} is required!`;
}
return true;
});
// 「最小文字数」を追加
defineRule("minLength", (value, [limit], context) => {
// The field is empty so it should pass
if (!value || !value.length) {
return true;
}
if (value.length < limit) {
return `${context.field} must be at least ${limit} characters`;
}
return true;
});
// 「最小数、最大数」を追加
defineRule("minmax", (value, [min, max], context) => {
// The field is empty so it should pass
if (!value || !value.length) {
return true;
}
const numericValue = Number(value);
if (numericValue < min) {
return `${context.field} must be greater than ${min}`;
}
if (numericValue > max) {
return `${context.field} must be less than ${max}`;
}
return true;
});
// 「コンファーム」を追加
defineRule("confirmed", (value, [target], context) => {
if (value === target) {
return true;
}
return `${context.field} must match`;
});
// 「コンファーム」を追加
defineRule("confirmed", (value, [target]) => {
if (value === target) {
return true;
}
return "Passwords must match";
});
export default {
components: {
Field,
Form,
ErrorMessage,
},
data() {
const validation_schema = {
password: yup
.string()
.required("password is required!")
.min(8, `password must be at least 8 characters!`),
// confirmationに`required`と`confirmed`を追加
// 参照するフィールドは@をつけて指定
confirmation: "required|confirmed:@password",
// オブジェクトでも書ける
message: {required: true, minLength: 5},
age: {required: true, minmax: [5, 10]},
};
return {
validation_schema,
};
},
methods: {
// 3秒後にレスポンス
async onSubmit(values, { resetForm }) {
await new Promise((resolve) => setTimeout (resolve, 3000));
console.log(values);
resetForm();
},
},
};
</script>
セレクト、ラジオボタン、チェックボックス
<template>
<Form
v-slot="{
meta,
isSubmitting
}"
:validation-schema="validation_schema"
@submit="onSubmit"
>
<div>
<h2 class="header1">セレクト</h2>
<Field name="item" as="select">
<option value="">---</option>
<option value="coffee">Coffee</option>
<option value="tea">Tea</option>
<option value="coke">Coke</option>
</Field>
<div><ErrorMessage name="item" /></div>
</div>
<div>
<h2 class="header1">ラジオボタン</h2>
<label><Field name="gender" type="radio" value="male" />male</label>
<label><Field name="gender" type="radio" value="female" />female</label>
<label><Field name="gender" type="radio" value="other" />other</label>
<div><ErrorMessage name="gender" /></div>
</div>
<div>
<h2 class="header1">チェックボックス</h2>
<label><Field name="categories" type="checkbox" value="111" />category1</label>
<label><Field name="categories" type="checkbox" value="222" />category2</label>
<label><Field name="categories" type="checkbox" value="33333" />category3</label>
<div><ErrorMessage name="categories" /></div>
</div>
<button type="submit" :disabled="!meta.valid || isSubmitting">send</button>
<button type="reset" :disabled="!meta.dirty">Reset</button>
</Form>
</template>
<script>
import { Form, Field, ErrorMessage } from "vee-validate";
import * as yup from "yup";
export default {
components: {
Field,
Form,
ErrorMessage,
},
data() {
const validation_schema = {
item: yup
.mixed()
.label("item")
.oneOf(
["coffee", "tea"],
"${path} must be one of the following values: ${values} !"
),
gender: yup
.mixed()
.label("gender")
.oneOf(
["male", "female", "other"],
"${path} must be one of the following values: ${values} !"
),
categories: yup
.array()
.label("categories")
.length(2, "${path} must have ${length} items!")
.of(yup.string().max(3, "category must be at most ${max} characters!")),
};
return {
validation_schema,
};
},
methods: {
// 3秒後にレスポンス
async onSubmit(values, { resetForm }) {
await new Promise((resolve) => setTimeout (resolve, 3000));
console.log(values);
resetForm();
},
},
};
</script>
配列, オブジェクト
<template>
<Form
v-slot="{
meta,
isSubmitting,
}"
:validation-schema="validation_schema"
@submit="onSubmit"
>
<div>
<h2 class="header1">urls 配列</h2>
<Field name="urls[0]" type="url" />
<Field name="urls[1]" type="url" />
<div><ErrorMessage name="urls" /></div>
</div>
<div>
<h2 class="header1">sites オブジェクト</h2>
<Field name="sites.twitter" type="url" />
<Field name="sites.github" type="url" />
<div><ErrorMessage name="sites" /></div>
</div>
<button type="submit" :disabled="!meta.valid || isSubmitting">send</button>
<button type="reset" :disabled="!meta.dirty">Reset</button>
</Form>
</template>
<script>
import { Form, Field, ErrorMessage } from "vee-validate";
import * as yup from "yup";
export default {
components: {
Field,
Form,
ErrorMessage,
},
data() {
const validation_schema = {
urls: yup
.array()
.label("urls")
.of(yup.string().label("url").url("${path} must be a valid URL")),
sites: yup.object().shape({
twitter: yup.string().label("twitter").url("${path} must be a valid URL"),
github: yup.string().label("github").url("${path} must be a valid URL"),
}),
};
// initial values
const initial_values = {
urls: ["https://github.com/asdf"],
sites: {
github: "https://github.com/asdf",
},
"links.twitter": "https://github.com/asdf",
};
return {
validation_schema,
initial_values,
};
},
methods: {
// 3秒後にレスポンス
async onSubmit(values, { resetForm }) {
await new Promise((resolve) => setTimeout (resolve, 3000));
console.log(values);
resetForm();
},
},
};
</script>
繰り返しのフィールド
FieldArray
を使用して増減可能なフィールドの作成
FieldArray
の fields
アイテムを v-for
に使用する
FieldArray
の fields
の一つ一つを entry
とする
v-for
の key
プロパティには entry.key
を使用、このプロパティはユニークなので反復キーとして使用することができる
push
メソッドで配列のフィールドを追加
remove
メソッドでインデックスを指定して削除
<template>
<Form
v-slot="{
meta,
isSubmitting,
}"
:validation-schema="validation_schema"
:initial-values="initial_values"
@submit="onSubmit"
>
<div>
<h2 class="header1">products 繰り返しフィールド</h2>
<FieldArray name="products" v-slot="{ fields, push, remove }">
<div v-for="(entry, idx) in fields" :key="entry.key">
<Field :name="`products[${idx}]`" type="text" />
<button type="button" @click="remove(idx)">Remove</button>
</div>
<button type="button" @click="push()">Add</button>
</FieldArray>
<div class="validation-notice"><ErrorMessage name="products" /></div>
</div>
<div>
<h2 class="header1">users 繰り返しフィールド array in object</h2>
<FieldArray name="users" v-slot="{ fields, push, remove }">
<div v-for="(entry, idx) in fields" :key="entry.key">
<Field :name="`users[${idx}].name`" type="text" />
<Field :name="`users[${idx}].age`" type="number" />
<label
><Field :name="`users[${idx}].gender`" type="radio" value="male" />male
</label>
<label>
<Field :name="`users[${idx}].gender`" type="radio" value="female" />female
</label>
<label>
<Field :name="`users[${idx}].gender`" type="radio" value="other" />other
</label>
<button type="button" @click="remove(idx)">Remove</button>
</div>
<button type="button" @click="push({ name: '', age: 1, gender: '' })">
Add
</button>
</FieldArray>
<div class="validation-notice"><ErrorMessage name="users" /></div>
</div>
<button type="submit" :disabled="!meta.valid || isSubmitting">send</span>
</button>
<button type="reset" :disabled="!meta.dirty">Reset</button>
</Form>
</template>
<script>
import { Form, Field, ErrorMessage, FieldArray } from "vee-validate";
import * as yup from "yup";
export default {
components: {
Field,
Form,
ErrorMessage,
},
data() {
const validation_schema = {
};
// initial values
const initial_values = {
products: yup
.array()
.label("products")
.max(3, "${path} field must have less than or equal to ${max} items!"),
users: yup
.array()
.label("users")
.max(5, "${path} field must have less than or equal to ${max} items!")
.of(
yup.object().shape({
name: yup.string().label("name").required("${path} is a required field!"),
age: yup
.number()
.label("age")
.typeError("${path} must be a number!")
.required("${path} is a required field!!")
.positive()
.integer()
.max(5, "${path} is Very big!"),
gender: yup.string().label("gender").required("${path} is a required field!"),
})
),
};
// initial values
const initial_values = {
name: "user",
email: "user@example.com",
password: "useruser",
urls: ["https://github.com/asdf"],
sites: {
github: "https://github.com/asdf",
},
products: [null],
users: [{ name: "asdf", age: 1, gender: "male" }],
};
return {
validation_schema,
initial_values,
};
},
methods: {
// 3秒後にレスポンス
async onSubmit(values, { resetForm }) {
await new Promise((resolve) => setTimeout (resolve, 3000));
console.log(values);
resetForm();
},
},
};
</script>
laravelと同じバリデーションルールを使用する
npm i @vee-validate/rules
ひとつづつインポート
import {
defineRule
} from 'vee-validate';
import {
required,
email,
min
} from '@vee-validate/rules';
defineRule('required', required);
defineRule('email', email);
defineRule('min', min);
全部インポート
import {
defineRule
} from 'vee-validate';
import * as rules from "@vee-validate/rules";
Object.keys(rules).forEach((rule) => {
if (rule !== "default") {
defineRule(rule, rules[rule]);
}
});
多言語化する
npm i @vee-validate/i18n
import {
configure
} from 'vee-validate';
import {
localize
} from '@vee-validate/i18n';
import en from '@vee-validate/i18n/dist/locale/en.json';
import ja from '@vee-validate/i18n/dist/locale/ja.json';
configure({
generateMessage: localize({
en,
ja,
}),
});
// change locale
setLocale('ja');
実装する
<template>
<Form
v-slot="{
meta,
isSubmitting,
}"
:validation-schema="validation_schema"
@submit="onSubmit"
>
<div>
<h2 class="header1">description</h2>
<Field name="description" type="text" />
<div class="validation-notice"><ErrorMessage name="description" /></div>
</div>
<div>
<h2 class="header1">image</h2>
<Field name="image" type="file" />
<div class="validation-notice"><ErrorMessage name="image" /></div>
</div>
<div>
<h2 class="header1">photos</h2>
<Field name="photos" type="file" multiple />
<div class="validation-notice"><ErrorMessage name="photos" /></div>
</div>
<button type="submit" :disabled="!meta.valid || isSubmitting">send</span>
</button>
<button type="reset" :disabled="!meta.dirty">Reset</button>
</Form>
</template>
<script>
import { Form, Field, ErrorMessage, defineRule } from "vee-validate";
// import { alpha, alpha_num, one_of, image } from "@vee-validate/rules";
import * as rules from "@vee-validate/rules";
Object.keys(rules).forEach((rule) => {
if (rule !== "default") {
defineRule(rule, rules[rule]);
}
});
export default {
components: {
Field,
Form,
ErrorMessage,
},
data() {
const validation_schema = {
description: {required: true, alpha: true, min: 1, max: 10, },
image: {required: true, ext: ['jpg', 'png'], size: 100 },
photos: {required: true, ext: ['jpg', 'png'], size: 100 }
};
return {
validation_schema,
};
},
methods: {
// 3秒後にレスポンス
async onSubmit(values, { resetForm }) {
await new Promise((resolve) => setTimeout (resolve, 3000));
console.log(values);
resetForm();
},
},
};
</script>
その他
onInvalidSubmit
onInvalidSubmit
イベントを取得してフィールドの値が有効でない場合のメソッドを設定できる
<!-- ... -->
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit">
<!-- ... -->
</form>
<!-- ... -->
// ...
methods: {
async onSubmit(values, {
resetForm
}) {
await new Promise((resolve) => setTimeout(resolve, 3000));
console.log(values);
resetForm();
},
onInvalidSubmit({
values,
errors,
results
}) {
console.log(values); // current form values
console.log(errors); // a map of field names and their first error message
console.log(results); // a detailed map of field names and their validation results
},
},
// ...
setFieldValueとsetValues
setFieldValue
でメソッドでフィールドの値を設定、
setValues
でフィールド全体の値を設定
<!-- ... -->
<Form v-slot="{
setFieldValue,
setValues}">
<!-- ... -->
<button type="button" @click="setFieldValue('name', 'kohei')">
Set Field Value
</button>
<button type="button" @click="
setValues({
name: 'user',
email: 'user@user.user',
password: 'paspaspaspas',
item: 'coffee',
})
">
Set Multiple Values
</button>
</form>
<!-- ... -->
デモ
<template>
<div id="app">
<!-- スロットの呼び出し -->
<Form
v-slot="{
values,
errors,
meta,
isSubmitting,
submitCount,
setFieldValue,
setValues,
}"
@submit="onSubmit"
@invalid-submit="onInvalidSubmit"
:validation-schema="validation_schema"
:initial-values="initial_values"
:initial-errors="initial_errors"
ref="testForm"
>
<button type="button" @click="setFieldValue('name', 'kohei')">
Set Field Value
</button>
<button
type="button"
@click="
setValues({
name: 'kohei',
email: 'kohei@gmail.com',
password: 'paspaspaspas',
item: 'coffee',
})
"
>
Set Multiple Values
</button>
<div>
<h2 class="header1">name</h2>
<Field name="name" type="text" />
<div class="validation-notice"><ErrorMessage name="name" /></div>
</div>
<div>
<h2 class="header1">email</h2>
<Field name="email" type="email" />
<div class="validation-notice"><ErrorMessage name="email" /></div>
</div>
<div>
<h2 class="header1">password</h2>
<Field name="password" type="password" />
<Field name="confirmation" type="password" label="☆confirmation☆" />
<div class="validation-notice"><ErrorMessage name="password" /></div>
<div class="validation-notice"><ErrorMessage name="confirmation" /></div>
</div>
<div>
<h2 class="header1">message</h2>
<Field name="message" type="text" label="☆message☆" />
<div class="validation-notice"><ErrorMessage name="message" /></div>
</div>
<div>
<h2 class="header1">age</h2>
<Field name="age" type="number" label="☆age☆" />
<div class="validation-notice"><ErrorMessage name="age" /></div>
</div>
<div>
<h2 class="header1">item セレクト</h2>
<Field name="item" as="select">
<option value="">---</option>
<option value="coffee">Coffee</option>
<option value="tea">Tea</option>
<option value="coke">Coke</option>
</Field>
<div class="validation-notice"><ErrorMessage name="item" /></div>
</div>
<div>
<h2 class="header1">gender ラジオボタン</h2>
<label><Field name="gender" type="radio" value="male" />male</label>
<label><Field name="gender" type="radio" value="female" />female</label>
<label><Field name="gender" type="radio" value="other" />other</label>
<div class="validation-notice"><ErrorMessage name="gender" /></div>
</div>
<div>
<h2 class="header1">categories チェックボックス</h2>
<label><Field name="categories" type="checkbox" value="111" />category1</label>
<label><Field name="categories" type="checkbox" value="222" />category2</label>
<label><Field name="categories" type="checkbox" value="33333" />category3</label>
<div class="validation-notice"><ErrorMessage name="categories" /></div>
</div>
<div>
<h2 class="header1">urls 配列</h2>
<Field name="urls[0]" type="url" />
<Field name="urls[1]" type="url" />
<div class="validation-notice"><ErrorMessage name="urls" /></div>
</div>
<div>
<h2 class="header1">sites オブジェクト</h2>
<Field name="sites.twitter" type="url" />
<Field name="sites.github" type="url" />
<div class="validation-notice"><ErrorMessage name="sites" /></div>
</div>
<div>
<h2 class="header1">products 繰り返しフィールド</h2>
<FieldArray name="products" v-slot="{ fields, push, remove }">
<div v-for="(entry, idx) in fields" :key="entry.key">
<Field :name="`products[${idx}]`" type="text" />
<button type="button" @click="remove(idx)">Remove</button>
</div>
<button type="button" @click="push()">Add</button>
</FieldArray>
<div class="validation-notice"><ErrorMessage name="products" /></div>
</div>
<div>
<h2 class="header1">users 繰り返しフィールド array in object</h2>
<FieldArray name="users" v-slot="{ fields, push, remove }">
<div v-for="(entry, idx) in fields" :key="entry.key">
<Field :name="`users[${idx}].name`" type="text" />
<Field :name="`users[${idx}].age`" type="number" />
<label
><Field :name="`users[${idx}].gender`" type="radio" value="male" />male
</label>
<label>
<Field :name="`users[${idx}].gender`" type="radio" value="female" />female
</label>
<label>
<Field :name="`users[${idx}].gender`" type="radio" value="other" />other
</label>
<button type="button" @click="remove(idx)">Remove</button>
</div>
<button type="button" @click="push({ name: '', age: 1, gender: '' })">
Add
</button>
</FieldArray>
<div class="validation-notice"><ErrorMessage name="users" /></div>
</div>
<div>
<h2 class="header1">description</h2>
<Field name="description" type="text" />
<div class="validation-notice"><ErrorMessage name="description" /></div>
</div>
<div>
<h2 class="header1">image</h2>
<Field name="image" type="file" />
<div class="validation-notice"><ErrorMessage name="image" /></div>
</div>
<div>
<h2 class="header1">photos</h2>
<Field name="photos" type="file" multiple />
<div class="validation-notice"><ErrorMessage name="photos" /></div>
</div>
<button type="submit" :disabled="!meta.valid || isSubmitting">
send <span>{{ submitCount }}</span>
</button>
<button type="reset" :disabled="!meta.dirty">Reset</button>
<div>
<h2>values</h2>
<pre>{{ values }}</pre>
</div>
<div>
<h2>errors</h2>
<pre>{{ errors }}</pre>
</div>
<div>
<h2>meta.initialValues</h2>
<pre>{{ meta.initialValues }}</pre>
</div>
<div>
<h2>meta.touched</h2>
<span>{{ meta.touched }}</span>
</div>
<div>
<h2>meta.valid</h2>
<span>{{ meta.valid }}</span>
</div>
<div>
<h2>meta.dirty</h2>
<span>{{ meta.dirty }}</span>
</div>
</Form>
</div>
</template>
<script>
import {
Form,
Field,
ErrorMessage,
FieldArray,
configure,
defineRule,
} from "vee-validate";
import * as yup from "yup";
import { localize, setLocale } from "@vee-validate/i18n";
import en from "@vee-validate/i18n/dist/locale/en.json";
import ja from "@vee-validate/i18n/dist/locale/ja.json";
// import { alpha, alpha_num, one_of, image } from "@vee-validate/rules";
import * as rules from "@vee-validate/rules";
Object.keys(rules).forEach((rule) => {
if (rule !== "default") {
defineRule(rule, rules[rule]);
}
});
configure({
validateOnBlur: true, // controls if `blur` events should trigger validation with `handleChange` handler
validateOnChange: true, // controls if `change` events should trigger validation with `handleChange` handler
validateOnInput: true, // controls if `input` events should trigger validation with `handleChange` handler
validateOnModelUpdate: true, // controls if `update:modelValue` events should trigger validation with `handleChange` handler
generateMessage: localize({
// en: {
// messages: {
// alpha: "alpha only!",
// },
// },
// ja: {
// messages: {
// alpha: "アルファベットだけ!",
// },
// },
en,
ja,
}),
});
// 「必須」を追加
// defineRule("required", (value, [], context) => {
// if (!value || !value.length) {
// return `${context.field} is required!`;
// }
// return true;
// });
// 「最小文字数」を追加
defineRule("minLength", (value, [limit], context) => {
// The field is empty so it should pass
if (!value || !value.length) {
return true;
}
if (value.length < limit) {
return `${context.field} must be at least ${limit} characters`;
}
return true;
});
// 「最小数、最大数」を追加
defineRule("minmax", (value, [min, max], context) => {
// The field is empty so it should pass
if (!value || !value.length) {
return true;
}
const numericValue = Number(value);
if (numericValue < min) {
return `${context.field} must be greater than ${min}`;
}
if (numericValue > max) {
return `${context.field} must be less than ${max}`;
}
return true;
});
// 「コンファーム」を追加
defineRule("confirmed", (value, [target], context) => {
if (value === target) {
return true;
}
return `${context.field} must match`;
});
export default {
components: {
Form,
Field,
ErrorMessage,
FieldArray,
},
data() {
// validation schema
const validation_schema = {
name(value, context) {
if (value && value.trim()) {
return true;
}
return "This field is required.";
},
email: yup
.string()
.label("email")
.required("${path} is required!")
.email("${path} must be a valid email!"),
password: yup
.string()
.label("password")
.required("${path} is required!")
.min(8, "${path} must be at least ${min} characters!"),
// confirmationに`required`と`confirmed`を追加
confirmation: "required|confirmed:@password",
// オブジェクトでも書ける
message: { required: true, minLength: 5 },
age: { required: true, minmax: [5, 10] },
// messageは「``」ではなくてダブルコーテーション「""」、
item: yup
.mixed()
.label("item")
.oneOf(
["coffee", "tea"],
"${path} must be one of the following values: ${values} !"
),
gender: yup
.mixed()
.label("gender")
.oneOf(
["male", "female", "other"],
"${path} must be one of the following values: ${values} !"
),
categories: yup
.array()
.label("categories")
.length(2, "${path} must have ${length} items!")
.of(
yup
.string()
.label("category")
.max(3, "${path} must be at most ${max} characters!")
),
urls: yup
.array()
.label("urls")
.of(yup.string().label("url").url("${path} must be a valid URL!")),
sites: yup.object().shape({
twitter: yup.string().label("twitter").url("${path} must be a valid URL!"),
github: yup.string().label("github").url("${path} must be a valid URL!"),
}),
products: yup
.array()
.label("products")
.max(3, "${path} field must have less than or equal to ${max} items!"),
users: yup
.array()
.label("users")
.max(5, "${path} field must have less than or equal to ${max} items!")
.of(
yup.object().shape({
name: yup.string().label("name").required("${path} is a required field!"),
age: yup
.number()
.label("age")
.typeError("${path} must be a number!")
.required("${path} is a required field!!")
.positive()
.integer()
.max(5, "${path} is Very big!"),
gender: yup.string().label("gender").required("${path} is a required field!"),
})
),
description: { required: true, alpha: true, min: 1, max: 10 },
image: { required: true, ext: ["jpg", "png"], size: 100 },
photos: { required: true, ext: ["jpg", "png"], size: 100 },
};
// initial values
const initial_values = {
name: "user",
email: "user@example.com",
password: "useruser",
urls: ["https://github.com/asdf"],
sites: {
github: "https://github.com/asdf",
},
products: [null],
users: [{ name: "asdf", age: 1, gender: "male" }],
};
// initial errors
const initial_errors = {
name: "This name is already taken!!",
email: "This email is already taken!!",
password: "The password is too short!!",
};
return {
validation_schema,
initial_values,
initial_errors,
};
},
methods: {
async onSubmit(values, { resetForm }) {
await new Promise((resolve) => setTimeout(resolve, 3000));
console.log(values);
resetForm();
},
onInvalidSubmit({ values, errors, results }) {
console.log(values); // current form values
console.log(errors); // a map of field names and their first error message
console.log(results); // a detailed map of field names and their validation results
},
},
};
</script>
<style scoped>
.validation-notice {
color: var(--color-caution);
}
</style>
コメントはありません。