お問い合わせ

ブログ

これまでに経験してきたプロジェクトで気になる技術の情報を紹介していきます。

vee-validateをvue3で使ってみる

okuda Okuda 2 years

vee-validate

参考サイト

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>

エラーとバリューとメタの取得

スコープ付きスロットに設定してある valueserrorsmeta を呼び出して使用する

  • values: 入力した値のオブジェクト
  • errors: バリデーションエラーのオブジェクト
  • meta.initialValues: フィールドの初期値、リセットするとこの値に戻る
  • meta.touched: フィールドにふれたらtrue
  • meta.valid: すべてのフィールドが有効ならtrue
  • meta.dirty: フィールドの値に変更があればtrue

Form コンポネントの v-slotinitial-valuesinitial-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 コンポネント内なのでデフォルトではリロードしない
methodsonSubmit を作成
Form コンポネントの submit イベントに onSubmit を登録
meta.validtrue になるまで 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 を使用して増減可能なフィールドの作成
FieldArrayfields アイテムを v-for に使用する
FieldArrayfields の一つ一つを entry とする
v-forkey プロパティには 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>
vee-validateをvue3で使ってみる 2022-01-24 17:11:40

コメントはありません。

4697

お気軽に
お問い合わせください。

お問い合わせ
gomibako@aska-ltd.jp