コード
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現在)ので、それを利用します。また、フォームの中身の変更の監視はanswer
のprop
を受け取り、それを監視することで行います。
基本的には、公式docにもあるようにelForm.validate()
を行うことでvalid
かどうかをcallbackで受け取れるので、それを利用します。ただし、これを行うとエラーメッセージが出てとても煩わしいので、それをclearするようなメソッドも作っています。
そのためにramda
を使用していますが、clone
を使っているのはanswer
はold valueをwatch
の引数として受け取れない(refなので第2引数と第1引数が同じになる)という理由です。また、equal
は、deep equalのようなものの実現のために入れています。