というわけで前回 Form Validation について考えたので、それを踏まえて実際に Component を作成します。
input text field
以下、私が作成した components/forms/input-text-field.vue
です。@change
ではなく、@input
を使っているのは @change
だと focus が外れるまで変更を検知できないからです。focus が外れてから form validation のトリガーが実行されると不便なので @input
を使使います。なお、 CSS framework に Bulma を使用しています。
また 子となる component に pristine
という state を持たせています。これはフォームに未入力の場合、xx は入力必須です
といったメッセージはまだ表示させたくないので、あえて子となる component に state をもたせました。
私は原則 component は stateless にしたいと考えています。なぜなら密結合をさせない為にも、子となる component を外側の世界に関与させないためです。なのですが、逆に親 component に関与させたくない細かい事象については子となる component で完結させたいと考えました。
<template>
<div class="field">
<p class="control has-icons-right">
<input @input="handleChange" :class="{'is-danger': hasError}" class="input" type="text" :placeholder="placeholder">
<span v-if="isValid" class="icon is-small is-right">
<fa icon="check" aria-hidden="true" />
</span>
</p>
<p v-if="hasError" class="help is-danger">
{{ errorMessages }}
</p>
</div>
</template>
<script>
export default {
props: ['placeholder', 'errorMessages', 'onChange'],
data () {
return {
pristine: true
}
},
computed: {
isValid () {
return !this.pristine && !this.errorMessages
},
hasError () {
return !this.pristine && this.errorMessages
}
},
methods: {
handleChange (e) {
this.onChange(e.target.value)
this.pristine = false
}
}
}
</script>
Nuxt.js でいうところの Page Component からは以下のように呼び出します。
<template>
<div class="field-body">
<InputTextField
placeholder="姓"
:errorMessages="errors.lastNameKanji"
:onChange="(value) => handleChangeForms({lastNameKanji: value})"
/>
<InputTextField
placeholder="名"
:errorMessages="errors.firstNameKanji"
:onChange="(value) => handleChangeForms({firstNameKanji: value})"
/>
</div>
</template>
<script>
import { validate } from '~/utils/validator'
import InputTextField from '~/components/forms/input-text-field'
export default {
components: {
InputTextField,
},
data () {
return {
fields: {},
errors: {}
}
},
methods: {
async handleChangeForms (fields) {
this.fields = { ...this.fields, ...fields }
this.errors = Object.assign({}, validate(this.fields))
}
}
}
</script>
<template>
の記述量はすこし多いですが、前回作成した validator 関数のおかげで<script>
内の記述量は抑える事ができました。
生年月日の Select Field を考える
よくある生年月日の入力フォームですが、今度は単一ではなく複数の select 要素から構成される場合を考えます。今回は yyyy-mm-dd
形式の文字列を value
として受け取る component を考えます。
<template>
<div class="field is-narrow">
<div class="control">
<div class="select">
<select @change="handleChageYear" :value="year">
<option :value="null" :key="`year:0`"></option>
<option v-for="n in 50" :value="n + 1950" :key="`year:${n}`">{{ n + 1950 }}年</option>
</select>
</div>
<div class="select">
<select @change="handleChageMonth" :value="month">
<option :value="null" :key="`month:0`"></option>
<option v-for="n in 12" :value="zeroPadding(n)" :key="`month:${n}`">{{ n }}月</option>
</select>
</div>
<div class="select">
<select @change="handleChageDay" :value="day">
<option :value="null" :key="`day:0`"></option>
<option v-for="n in 31" :value="zeroPadding(n)" :key="`day:${n}`">{{ n }}日</option>
</select>
</div>
</div>
<p v-if="hasError" class="help is-danger">
{{ errorMessages }}
</p>
</div>
</template>
<script>
export default {
props: ['value', 'errorMessages', 'onChange'],
data () {
return {
pristine: true
}
},
computed: {
year () {
return this.value && this.value.substr(0, 4)
},
month () {
return this.value && this.value.substr(5, 2)
},
day () {
return this.value && this.value.substr(8, 2)
},
isValid () {
return !this.pristine && !this.errorMessages
},
hasError () {
return !this.pristine && this.errorMessages
}
},
methods: {
zeroPadding (value) {
return String(value).padStart(2, '0')
},
handleChageYear (e) {
this.onChange([e.target.value, this.month, this.day].join('-'))
this.pristine = false
},
handleChageMonth (e) {
this.onChange([this.year, e.target.value, this.day].join('-'))
this.pristine = false
},
handleChageDay (e) {
this.onChange([this.year, this.month, e.target.value].join('-'))
this.pristine = false
}
}
}
</script>
Nuxt.js でいう Page Component では以下のように呼び出す事ができます。
<BirthdayField
:value="fields.birthday"
:errorMessages="errors.birthday"
:onChange="(birthday) => handleChangeForms({birthday})"
/>
まとめ
今回の例では Validation 部分は Page Component に記述して、子となる Component の処理は極力簡略化しています。Component の作成は CSS が得意な人に専念してもらいたいので責任をここで分離しています。
次回、Form Validation を Page Component から追い出せないかを考えます。