2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ElementUIのFormの自動バリデーション

Last updated at Posted at 2020-05-14

コード

import { clone, equals } from 'ramda'
import Vue, { PropType } from 'vue'

type Answer = Record<any, any>

const isObject = (item: any): item is Record<any, any> =>
  typeof item === 'object' && item !== null && !Array.isArray(item)

export const AutoValidate = Vue.extend({
  name: 'FormAutoValidator',

  inject: ['elForm'],

  props: {
    valid: {
      type: Boolean,
      required: true,
    },
    answer: {
      type: Object as PropType<Answer>,
      required: true,
    },
    form: {
      type: Object as PropType<any>,
      default(): any {
        return this.elForm
      },
    },
  },

  data: () => ({
    prevAns: {} as Answer,
  }),

  computed: {
    isAnsInitial(): boolean {
      const isInitial = (a: Record<any, any>): boolean =>
        Object.values(a)
          .map(v => (isObject(v) ? isInitial(v) : v === undefined))
          .every(v => v)

      return isInitial(this.answer)
    },
  },

  watch: {
    answer: {
      handler(ans: Answer): void {
        this.validate(() => {
          if (this.form && this.form.clearValidate && typeof this.form.clearValidate === 'function') {
            const differentKeys = this.findDifference(ans, this.prevAns)
            this.form.clearValidate(Object.keys(this.answer).filter(k => !differentKeys.includes(k)))
          }
        })
        this.prevAns = clone(ans)
      },
      deep: true,
      immediate: true,
    },
  },

  methods: {
    validate(cb?: (valid: boolean) => void): void {
      const form = this.form
      if (form) {
        if (form.validate && typeof form.validate === 'function') {
          form.validate((valid: boolean) => {
            const v = !this.isAnsInitial && valid
            if (cb) cb(v)
            this.$emit('update:valid', v)
          })
        }
      } else if (process.env.NODE_ENV === 'development') {
        console.error('This component should be used in el-form or MUST pass form prop')
      }
    },

    findDifference(a1: Answer, a2: Answer): string[] {
      try {
        return Object.keys(a1)
          .map(key => (equals(a1[key], a2[key]) ? '' : key))
          .filter(v => Boolean(v))
      } catch {
        return []
      }
    },
  },

  render() {
    return <div />
  },
})

使用例

<template>
  <el-form :model="answer" label-position="top" :rules="rules">
    <auto-validate :valid.sync="valid" :answer="answer" />

    <el-form-item label="Q1. 質問!!" prop="q1">
      <el-select v-model="answer.q1">
        <el-option v-for="({ lavel, value }) in data.q1" :key="value" :label="label" :value="value" />
      </el-select>
    </el-form-item>
  </el-form>
</template>

簡単な解説

ElementUIのFormは、Vuetifyのそれと違ってv-modelでvalidかどうかを取得できません。それの対処としてのこのコンポーネントです。

el-formの子孫ではinjectを使用してelFormが受け取れる(少なくともv2.13.2現在)ので、それを利用します。また、フォームの中身の変更の監視はanswerpropを受け取り、それを監視することで行います。

基本的には、公式docにもあるようにelForm.validate()を行うことでvalidかどうかをcallbackで受け取れるので、それを利用します。ただし、これを行うとエラーメッセージが出てとても煩わしいので、それをclearするようなメソッドも作っています。

そのためにramdaを使用していますが、cloneを使っているのはanswerはold valueをwatchの引数として受け取れない(refなので第2引数と第1引数が同じになる)という理由です。また、equalは、deep equalのようなものの実現のために入れています。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?