Vuetifyを使った開発 5 ~ドロップダウンの連動~

IT

Vuetifyでは、「Select」コンポーネントを使ってドロップダウンを簡単に作成できます。今回は2つの連動するドロップダウンを作成したいと思います。1つ目のドロップダウンで選ばれた値によって2つ目のドロップダウンの内容を変更するものです。

前提

環境・バージョン

oswindows11
nodev20.11.1
npm10.5.0
Vue3.4.21
Vuetify3.5.9
エディターVS Code

データの準備

今回は以下のようなデータを含むJsonファイルを用意します。

[
    {
        "type": "ごはん",
        "items": [
            {
                "name": "カレー",
                "price": 500
            },
         ・・・
        ]
    },
    {・・・}
]

最終系

今回実装する連動するドロップダウンは以下のようなものになります。

今回の内容は、以下の公式サイトを参考にしています。

1つ目のドロップダウンの作成

まずは、分類(ごはん、パン、麺類)用のドロップダウンを作成します。

<script setup lang="ts">
const selected = ref()
</script>

<template>
    <v-select label="分類" :items="sampleData" item-title="type" item-value="type"
         v-model="selected" return-object></v-select>
</template>

ドロップダウンは「v-select」ディレクティブを使用します。「items」プロパティには、「データの準備」で記載したJson配列を指定します。そして、「item-title」にはドロップダウンのメニューに表示する値を、「item-value」には選択されたメニューに対応する値を指定します。

今回のデータでは「item-title」と「item-value」両方に「type」を指定しています。typeはJsonのプロパティです。なお、「item-value」に指定する値は、「String」や「Number」などの「primitive型」である必要があります。「Object」などは指定できません。

続いて、「v-model」を用いて、選択されたメニューを「selected定数」にバインドさせます。この時、「selected」には「item-value」で指定した値が入ります。ただし、「return-object」プロパティを追加することで、「item-value」を含むオブジェクトが入ります

例えば、「ごはん」を選択したときに、return-objectの有無でselectedの値は以下のように変わります。
・指定しないとき:
selected = "ごはん"
・指定するとき :
selected = {"type":"ごはん", "items":[{"name":"カレー","price":500},..}]}

2つ目のドロップダウンの作成に、selectedに格納されるオブジェクトの「items」の内容が必要になります。そのためreturn-objectを付与しています。

ここまで記載すると、以下のようなドロップダウンが作成できます。

2つ目のドロップダウンの作成

続いて、2つ目のドロップダウンを作成しましょう。2つ目は、1つ目のドロップダウンで選択された分類に対応する具体的なメニューを選択するドロップダウンです。

<script setup lang="ts">
const selectedFood = ref()
</script>

<template>
    <v-select label="メニュー" :items="selected?.items" item-title="name" item-value="price"
        v-model="selectedFood" return-object></v-select>
</template>

記載内容は1つ目と同じです。ここでは、「items」プロパティに「selected.items」を渡しています。これは1つ目のドロップダウンで選択した食事分類によって変化します。

分類を変える度にメニューをリセット

今の状態ですと1つ問題があります。分類を変えた直後に、メニューに古いメニューが選択されたままになってしまいます。カレーを選んだ後に、分類を麺類に変えてみましょう。すると、メニューのドロップダウンの内容は更新されますが、カレーが表示されたままとなってしまいます。

そこで、分類を変えたらメニューをリセットする処理を追加してみましょう。

2つ目のドロップダウンで選択した内容は「selectedFood」に格納されています。そのため、「selectedFood」の値をリセットする関数「clearFood」を作成します。そして、1つ目のドロップダウンを変更したときに呼び出されるように、「update:modelValue」イベントにこの関数を指定します。

<script setup lang="ts">
const clearFood = () => {
    selectedFood.value = undefined
}
</script>

<template>
    <v-select label="分類" :items="sampleData" item-title="type" item-value="type"
         v-model="selected" return-object @update:modelValue="clearFood"></v-select>
</template>

「v-select」で発生するイベントは、VSelectのAPIページに記載されています。

2つ目のドロップダウンでは、価格が取得できれば良いので「return-object」は不要です。また、外したとしても、一見動作に変わりはありません。

しかし、「clearFood」関数を無効化する(初期化を行わない)と、「return-object」を外すことで若干挙動が異なります。カレーと選んだ後に分類を麺類に変更すると、以下のようにメニューの値が金額に変わるようになります。

最後に

Jsonファイルの読み込み

Typescriptでは、Jsonファイルを単純にインポートするだけで、型の情報まで取得生成してくれます。

import sampleData from '../assets/json/sample1.json'

Jsonファイルをインポートするためには、「tsconfig.json」に以下の設定が必要です。

{
  "compilerOptions": {
    "moduleResolution": "node",
    "resolveJsonModule": true
  }
}

コード

全体のコードを最後に記載します。

<script setup lang="ts">
import { ref } from 'vue';
import sampleData from '../assets/json/sample1.json'

const selected = ref()
const selectedFood = ref()
const clearFood = () => {
    selectedFood.value = undefined
}
</script>

<template>
    <div>Home</div>
    <v-divider></v-divider>
    <div class="d-flex justify-center">
        <v-sheet class=" ma-2" elevation="4" width="100%" color="purple-lighten-4">
            <v-row class="ma-1">
                <v-col cols="3">
                    <v-select label="分類" :items="sampleData" item-title="type" item-value="type" v-model="selected"
                        return-object @update:modelValue="clearFood"></v-select>
                </v-col>
                <v-col cols="3">
                    <v-select label="メニュー" :items="selected?.items" item-title="name" item-value="price"
                        v-model="selectedFood" return-object></v-select>
                </v-col>
                <v-col cols="3">
                    <v-text-field :model-value="selectedFood?.price" label="値段"></v-text-field>
                </v-col>
            </v-row>
            <v-row>
                <v-col> {{ selectedFood }}</v-col>
            </v-row>
        </v-sheet>
    </div>
</template>

コメント