[Form作成から学ぶVue(TypeScript)その4] 選択肢を動的に変更するプルダウン

親となるプルダウンを選択することで、子のプルダウンの選択肢を動的に変更するプルダウンの作りかた。

参考としたページ
https://qiita.com/niisan-tokyo/items/7d15716304c492f244c2

ここへ以下の変更を加えた。

* まず記述をTypeScriptへ。
* 親プルダウンを変更すると子プルダウンは自動的に未選択にリセットされる。
* 子プルダウンの選択を親コンポーネントへEmitする。
* 選択肢を後から柔軟に変更したい。 →改良版へ
* 子プルダウンの数は任意の数だけ無限に設置したい。 →改良版へ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406130/71cd41f2-e6c5-e5c9-5db0-300d94aca804.png)

今見ると、何の都道府県なのか謎すぎるがやりたいことは連動したプルダウン作成なのでこの際気にしない

# とりあえず真似てみた

```vue:PullDown.vue(子コンポーネント)
<template>
  <div>
    <p>都道府県区分
      <select v-model="selectedArea">
        <option v-for="area in areas" :key="area.value">{{ area }}</option>
      </select>
    </p>
    <p>都道府県
      <select id="prefecture" @change="changeSelectedPrefecture" v-model="selectedPrefecture">
        <option v-for="prifecture in prifectures" :key="prifecture.value">{{ prifecture }}</option>
      </select>
    </p>
  </div>
</template>

<script lang="ts">
import { Component, Vue, Emit } from "vue-property-decorator";

@Component
export default class Form extends Vue {
	private selectedArea: string = "";
	private selectedPrefecture: string = "";

    private prifecture: object = {
    "北海道" : ["北海道"],
    "東北" : ["青森", "秋田", "岩手", "山形", "宮城", "福島"],
		"関東" : ["茨城", "栃木", "群馬", "埼玉", "千葉", "東京", "神奈川"],
		"中部" : ["新潟", "富山", "石川", "福井", "山梨", "長野", "岐阜", "静岡", "愛知"],
		"近畿" : ["三重", "滋賀", "京都", "大阪", "兵庫", "奈良", "和歌山"],
		"中国" : ["鳥取", "島根", "岡山", "広島", "山口"],
		"四国" : ["徳島", "香川", "愛媛", "高知"],
		"九州・沖縄" : ["福岡", "佐賀", "長崎", "熊本", "大分", "宮崎", "鹿児島", "沖縄"]
	};
	private areas: object = Object.keys(this.prifecture);
	
	private get prifectures(): object {
		return this.prifecture[this.selectedArea];
	}

    private changeSelectedPrefecture(): void {
			console.log(this.selectedPrefecture)
			this.updateSelected(this.selectedPrefecture)
    }

    @Emit('updateSelected')
    private updateSelected(selected: string): void{
	}

}
</script>
```

なお、`changeSelectedPrefecture `はRadioButtonコンポーネントでやったように以下のようにしてもよい。selectタグから取得する値は`HTMLSelectElement`なので注意。

```ts
  private changeSelectedPrefecture(): void {
    if (event!.target instanceof HTMLSelectElement) {
      console.log(event!.target.value)
			this.updateSelected(event!.target.value)
		}
  }
```

プルダウンから選択されると`@change="changeSelectedPrefecture"`をトリガとして親コンポーネントで定義した`updateSelected `をEmitする。ここの記述は前回までのRadioButtonなどと同じなので省略。


ただ、このままでは2連結のプルダウンしか動的に生成できないので、子プルダウンを3つ目、4つ目が欲しいと思っても、都度作成する必要が出てしまう。そこで、子プルダウンは親から与えられた選択肢を表示するに止めたより小さな機能のコンポーネントとして作り直す。

# 改良版

```vue:NewPullDown.vue(子コンポーネント)
<template>
  <p>
    <span>{{ title }}</span>
    <span>
      <select @change="changeSelected" v-model="selected">
        <option v-for="alt in alts" :key="alt.value">{{ alt }}</option>
      </select>
    </span>
  </p>
</template>

<script lang="ts">
import { Component, Vue, Prop, Emit } from "vue-property-decorator";

@Component
export default class Form extends Vue {
	@Prop()
	private title: string;

	private selected: string = "";

	@Prop()
	private alternatives: object;
	
	private get alts(): Array<string> {
		if(this.alternatives == undefined)
			return [];
		return Object.keys(this.alternatives);
	} 
	
	mounted(){
		console.log(this.alternatives);
	}

  private changeSelected(): void {
			console.log(this.selected)
			this.updateSelected(this.selected)
  }

  @Emit('updateSelected')
  private updateSelected(selected: string): void{
	}
}
</script>
```

使い方は以下

```vue
<PullDown :title="表示したいタイトル" :alternatives="選択肢を並べたjson" @updateSelected="Emitされる関数" />
```

実際にこれを利用して先ほどの都道府県区分＋都道府県の親コンポーネントを書くと以下。

```vue:Form.vue(親コンンポーネント)
<template>
  <div>
    <div>
      <PullDown :title="areaTitle" :alternatives="alternatives" @updateSelected="updateSelectedArea" />
      <PullDown :title="prefectureTitle" :alternatives="prefectures" @updateSelected="updateSelectedPrefecture" />
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import PullDown from '@/components/PullDown.vue'
import { alternativesOfPrefectures } from '../components/prefectures'

@Component({
  components:{
    PullDown
  }
})
export default class Form extends Vue {

  private selectedArea: string = ""
  private selectedPrefecture: string = ""

  private areaTitle = "都道府県区分"
  private prefectureTitle = "都道府県"

  private alternatives: object= alternativesOfPrefectures

  private updateSelectedArea(selected: string){
    console.log("selectedArea:" + selected)
    this.selectedArea = selected
  }

  private updateSelectedPrefecture(selected: string){
    console.log("selectedPrefecture:" + selected)
    this.selectedPrefecture = selected
  }

  private get prefectures(): object {
    console.log(this.alternatives[this.selectedArea])
		return this.alternatives[this.selectedArea];
  }
}
</script>
```

なお、選択肢のJSONは別ファイルに外出しして参照している。

```ts:components/predfectures.ts(選択肢のJSONを並べたファイル)
export const alternativesOfPrefectures: Object = {
    "北海道" : {
        "北海道": null
    },
    "東北" : {
      "青森" : null,
      "秋田" : null,
       :
      (略)
       :
      "鹿児島" : null,
      "沖縄" : null
    }
  }
```

jsonにリストを混ぜるのをやめてnullとしているのは、のちに3つ目のプルダウンを増やしたいと思った時の拡張性を考慮している。