
九保すこひです(フリーランスのITコンサルタント、エンジニア)
さてさて、前回の記事「Vue Component で2枚の画像を切り替えるチェックボックスをつくってみよう」では独自のコンポーネント(≒疑似HTMLタグ)を作って利用規約の同意で使ってみました。
そして、この記事を書いている時に、その昔クライアントさんからご要望があった「ちょっとめんどうな」セレクトボックスがあったことを思い出しました。
それが、元号 + 年で生年月日を入力するセレクトボックスです。
もちろん西暦ならセレクトボックスひとつで問題なのですが、これまで長い間お客さんのデータを和暦で管理してきた関係上、入力は和暦にしたいとのご要望でした。
ということで、今回は前回に引き続きVue Componenntの話題で、和暦 + 年で連携するセレクトボックスをつくってみます。
ぜひ学習の役に立ててくださいね。(最後に教材ソースコードをダウンロードできます。)
目次 [非表示]
やりたいこと
まずセレクトボックスは次の2つです。
- 元号
- 年
そして、「元号」を選択すると自動的に「年」の中身が元年〜その元号の最大年に変化するようにします。
では、実際に開発を進めていきましょう!
Vueの基本形をつくる
では、Vueが使えるように基本形を作り、ここにコンポーネントをつくっていきます。
<html>
<body>
<div id="app">
<!-- ここのVueコンポーネント -->
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js"></script>
<script>
new Vue({
el: '#app'
});
</script>
</body>
</html>
※ Vue本体はcdn
から読み込んでいます。
Vueコンポーネントをつくる
今回のコンポーネントは元号と年のセレクトボックスが必要になり、さらに各セレクトボックスに様々なコードが必要になってくるので、構造は次のようにします。
- v-wareki-input ・・・ 親コンポーネント
- v-wareki-gengo-input ・・・ 元号の選択 (子コンポーネント – 1)
- v-wareki-year-input ・・・ 年の選択 (子コンポーネント – 2)
つまり、v-wareki-input
というコンポーネントの中に、v-wareki-gengo-input
とv-wareki-year-input
という2つの子コンポーネントが含まれている形になります。
ではひとつずつ開発していきましょう。
元号を選択するセレクトボックス
まずは次のように元号を選択できるセレクトボックスを自動生成するコンポーネントです。
Vue.component('v-wareki-gengo-input', {
props: {
value: {
type: String,
default: 0
}
},
template:
'<select v-model="value">'+
'<option></option>'+
'<option v-for="(gengo,index) in $parent.gengoData" :value="index" v-text="gengo.text"></option>'+
'</div>',
watch: {
value: function(value) {
this.$emit('input', value);
}
}
});
では重要な部分をひとつずつみていきましょう。
props
value
はv-model
の中身が入ってくる変数で、選択中された元号のインデックス番号です。つまり、後でつくる親コンポーネントから次のように利用します。
<v-wareki-gengo-input v-model="gengoIndex"></v-wareki-gengo-input>
template
重要なのは、v-for
を使って元号の選択肢を作っている部分です。
なお、$parent.gengoData
はこれからつくる親コンポーネントの変数gengoData
からデータをとってくるという意味になります。
v-for="(gengo,index) in $parent.gengoData"
watch
value
が変更されたらinputイベントを送出します。
これによって親コンポーネントに値を伝えることができるようになります。
年を選択するセレクトボックス
続いて、年を選択するセレクトボックスですが、これは元号によって選択肢が自動的に変更になるようにします。(明治: 45年/大正: 15年/昭和: 63年/平成: 30年)
Vue.component('v-wareki-year-input', {
props: {
max: {
type: Number,
default: 0
},
value: {
type: Number,
default: 0
}
},
template:
'<select v-model="value">'+
'<option></option>'+
'<option v-for="i in max" :value="i" v-text="year(i)"></option>'+
'</select>',
methods: {
year: function(i) {
if(i == 1) {
return '元年';
}
return i +'年';
}
},
watch: {
value: function(value) {
this.$emit('input', value);
}
}
});
では、ひとつずつみていきます。
props
このコンポーネントに必要なデータは、
- 選択した年
- 選択した元号に対応する最大年数
の2つです。
そのため、親コンポーネント内では、次のように2つの変数を格納するようにします。
<v-wareki-year-input :max="maxGengoYear" v-model="gengoYear"></v-wareki-year-input>
つまり、maxGengoYear
は先ほどの元号セレクトボックスが選択されたらすぐ自動的に切り替わる必要があります。(これについては親コンポーネントのcomputed
で説明します)
template
まず、v-for
ではmax
、つまり最大年数分の選択肢を作成しています。
v-for="i in max"
また、表示するテキストは「1年」の場合は「元年」とするべきですので、year()
というメソッドをつくり、この中で値が1
の場合のみ「元年」を返すようにしています。
watch
このコンポーネントでも、value
が変更されたらinputイベントを送出し、親コンポーネントへ値を伝えています。
親コンポーネント
では最後に親コンポーネントです。
2つの子コンポーネントを連携させるので複雑な部分もありますが、できるだけわかりやすく説明していきます。
Vue.component('v-wareki-input', {
props: {
value: {
type: Number,
default: 0
}
},
template: '<span>'+
'<v-wareki-gengo-input v-model="gengoIndex"></v-wareki-gengo-input> '+
'<v-wareki-year-input :max="maxGengoYear" v-model="gengoYear"></v-wareki-year-input> '+
'</span>',
data: function() {
return {
gengoIndex: '',
gengoYear: 0,
gengoData: [
{text: '明治', base: 1867, max: 45},
{text: '大正', base: 1911, max: 15},
{text: '昭和', base: 1925, max: 63},
{text: '平成', base: 1988, max: 30},
],
initialized: false
}
},
methods: {
calcYear: function() {
var year = null;
if(this.hasGengo && this.gengoYear > 0) {
var gengo = this.gengoData[this.gengoIndex];
year = this.gengoYear + gengo.base;
}
this.$emit('input', year);
}
},
computed: {
hasGengo: function() {
return (this.gengoData[this.gengoIndex] != undefined);
},
selectingGengo: function() {
return (this.hasGengo)
? this.gengoData[this.gengoIndex]
: null;
},
maxGengoYear: function() {
return (this.hasGengo)
? this.selectingGengo.max
: 0;
}
},
mounted: function() {
if(this.value > 0) {
for(var i in this.gengoData) {
var gengo = this.gengoData[i];
if(this.value > gengo.base) {
this.gengoIndex = i;
this.gengoYear = this.value - gengo.base;
}
}
}
var self = this;
Vue.nextTick(function(){
self.initialized = true;
});
},
watch: {
gengoIndex: function() {
if(this.initialized && this.hasGengo) {
this.gengoYear = 0;
}
this.calcYear();
},
gengoYear: function() {
this.calcYear();
}
}
});
では、ひとつずつ見ていきます。(わかりやすく説明したいので順不同です)
props
valueはv-model
に対応する部分です。
例えば、次のように初期値を西暦で入力すると、自動的に和暦に変換するようにします。
<v-wareki-input v-model="2000"></v-wareki-input>
data
変数は次のとおりです。
- gengoIndex ・・・ 選択された元号のインデックス
- gengoYear ・・・ 選択された年
- gengoData ・・・ 元号のマスターデータ。ここで一元管理したいので、子コンポーネントからは$parentとして呼び出しています。もし新元号になったらここにデータを追加するだけでOKです。
- initialized ・・・ ページ表示されてすぐかどうかかが分かる変数。
true
orfalse
※ なお、Vueコンポーネントは関数の返り値として設定する必要があります。そのため、function
を使っています。
computed
computed
の内容は次のとおりです。
- hasGengo() ・・・ 元号が選択されているかどうか。
true
orfalse
- selectingGengo() ・・・ 現在選択されている元号(だけ)のデータ
- maxGengoYear() ・・・ 現在選択されている元号の最大年数
template
2つのこのコンポーネントをテンプレートとして呼び出し、それぞれ変数gengoIndex
とgengoYear
をバインディングしています。
重要なのは、maxGengoYear
の部分で、これはcomputed
のmaxGengoYear()
の結果が自動的に入るようにしています。
methods
calcYear()
は、元号と年の両方が選択された状態なら、それが西暦何年かを計算し、結果を$emit
で呼び出し元に伝えています。つまり、次の場合、変数year
が自動的に更新されます。
<v-wareki-input v-model="year"></v-wareki-input>
※ なお、もし元号や年が選択されていないときはnull
を返すようにしています。
mounted
value
(西暦)に初期値が入っていたら、計算して元号と年をセットするようにしています。また、最後にinitialized
をtrue
に変更しています。(この部分は詳しくwatch
で説明します)
watchs
変数gengoIndex
とgengoYear
に変更があったらcalcYear()
で西暦を計算するようになっています。
そして、ここで重要なのがgengoIndex
の次の部分です。
if(this.initialized && this.hasGengo) {
this.gengoYear = 0;
}
なぜページが表示されてすぐの場合はgengoYear
を0
にはしないかというと、例えば、初期値を2000
にした場合、mounted
の中で
- 元号 ・・・ 平成
- 年 ・・・ 12
と和暦への変換が行われます。そして、それぞれgengoIndex
とgengoYear
にその値が格納されることになるのですが、もしinitialized
で守っていないとgengoIndex
を変更した時点で、watch
によって12
が0
に上書きされてしまうのです。
mounted
内でnextTick()
を使っているのもそのためです。
var self = this;
Vue.nextTick(function(){
self.initialized = true;
});
nextTick()
は一旦レンダリングが完了してから中身を実行するのですが、こうしないと同じくタイミング的にgengoYear
は0
に上書きされてしまいます。
デモを用意しました
実際のキャプチャ画像は次のとおりですが、画像だけでは分かりにくいかもしれませんので、実際に体験できるデモページを用意しました。ぜひ触ってみてください。
教材ソースコードをダウンロードする
今回開発したソースコード一式(Vue Componentを含む)を以下からダウンロードできます。cdn
でVue
を読み込んでいるので展開してすぐ実行することができます。