Vueでセレクトボックス、ラジオボタン、チェックボックスを連動する(DL可)

こんにちは。フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、ここのところUSBなどのガジェットや大好きなLaravelの話題ばかりで、このブログのもう一つのメインといってもいいVue.jsからは少し遠ざかってしまっていました。

もちろん、業務的にVueは日常的に使っているのですがこの辺でまた「より根本的な使い方」を紹介するためにも今回は、

Vueでセレクトボックス、ラジオボタン、チェックボックスを連動する方法

を紹介することにしました。

具体的にいうと、以下のようなものです。

  1. まずいくつかの市の中から1つを選びます
  2. すると、その市に所属する区だけが次の選択肢として表示される

というものです。
つまり、イメージで言うとこうなります。

ぜひ皆さんのお役に立てると嬉しいです😊✨

開発環境: Vue 2.6

テストデータをつくる

今回はセレクトボックス、ラジオボタン、チェックボックスの3パターンでの実装方法を紹介しますので、テストデータはコードの中に直接書き込まず、jsonファイルを作ってaxiosを使ってAjaxで取得します。

データの中身はこうなります。(政令指定都市とその中にある区のデータです)
ファイル名はcity-wards.jsonとしておいてください。

{
    "cities": [
        {"id": 1, "name": "札幌市"},
        {"id": 2, "name": "仙台市"},
        {"id": 3, "name": "さいたま市"},
        {"id": 4, "name": "千葉市"},
        {"id": 5, "name": "横浜市"},
        {"id": 6, "name": "川崎市"},
        {"id": 7, "name": "相模原市"},
        {"id": 8, "name": "新潟市"},
        {"id": 9, "name": "静岡市"},
        {"id": 10, "name": "浜松市"},
        {"id": 11, "name": "名古屋市"},
        {"id": 12, "name": "京都市"},
        {"id": 13, "name": "大阪市"},
        {"id": 14, "name": "堺市"},
        {"id": 15, "name": "神戸市"},
        {"id": 16, "name": "岡山市"},
        {"id": 17, "name": "広島市"},
        {"id": 18, "name": "北九州市"},
        {"id": 19, "name": "福岡市"},
        {"id": 20, "name": "熊本市"}
    ],
    "wards": [

        {"cityId": 1, "id": 1, "name": "中央区"},
        {"cityId": 1, "id": 2, "name": "北区"},
        {"cityId": 1, "id": 3, "name": "東区"},
        {"cityId": 1, "id": 4, "name": "白石区"},
        {"cityId": 1, "id": 5, "name": "豊平区"},
        {"cityId": 1, "id": 6, "name": "南区"},
        {"cityId": 1, "id": 7, "name": "西区"},
        {"cityId": 1, "id": 8, "name": "厚別区"},
        {"cityId": 1, "id": 9, "name": "手稲区"},
        {"cityId": 1, "id": 10, "name": "清田区"},

        {"cityId": 2, "id": 1, "name": "青葉区"},
        {"cityId": 2, "id": 2, "name": "宮城野区"},
        {"cityId": 2, "id": 3, "name": "若林区"},
        {"cityId": 2, "id": 4, "name": "太白区"},
        {"cityId": 2, "id": 5, "name": "泉区"},

        {"cityId": 3, "id": 1, "name": "西区"},
        {"cityId": 3, "id": 2, "name": "北区"},
        {"cityId": 3, "id": 3, "name": "大宮区"},
        {"cityId": 3, "id": 4, "name": "見沼区"},
        {"cityId": 3, "id": 5, "name": "中央区"},
        {"cityId": 3, "id": 6, "name": "桜区"},
        {"cityId": 3, "id": 7, "name": "浦和区"},
        {"cityId": 3, "id": 8, "name": "南区"},
        {"cityId": 3, "id": 9, "name": "緑区"},
        {"cityId": 3, "id": 10, "name": "岩槻区"},

        {"cityId": 4, "id": 1, "name": "中央区"},
        {"cityId": 4, "id": 2, "name": "花見川区"},
        {"cityId": 4, "id": 3, "name": "稲毛区"},
        {"cityId": 4, "id": 4, "name": "若葉区"},
        {"cityId": 4, "id": 5, "name": "緑区"},
        {"cityId": 4, "id": 6, "name": "美浜区"},

        {"cityId": 5, "id": 1, "name": "鶴見区"},
        {"cityId": 5, "id": 2, "name": "神奈川区"},
        {"cityId": 5, "id": 3, "name": "西区"},
        {"cityId": 5, "id": 4, "name": "中区"},
        {"cityId": 5, "id": 5, "name": "南区"},
        {"cityId": 5, "id": 6, "name": "保土ヶ谷区"},
        {"cityId": 5, "id": 7, "name": "磯子区"},
        {"cityId": 5, "id": 8, "name": "金沢区"},
        {"cityId": 5, "id": 9, "name": "港北区"},
        {"cityId": 5, "id": 10, "name": "戸塚区"},
        {"cityId": 5, "id": 11, "name": "港南区"},
        {"cityId": 5, "id": 12, "name": "旭区"},
        {"cityId": 5, "id": 13, "name": "緑区"},
        {"cityId": 5, "id": 14, "name": "瀬谷区"},
        {"cityId": 5, "id": 15, "name": "栄区"},
        {"cityId": 5, "id": 16, "name": "泉区"},
        {"cityId": 5, "id": 17, "name": "青葉区"},
        {"cityId": 5, "id": 18, "name": "都筑区"},

        {"cityId": 6, "id": 1, "name": "川崎区"},
        {"cityId": 6, "id": 2, "name": "幸区"},
        {"cityId": 6, "id": 3, "name": "中原区"},
        {"cityId": 6, "id": 4, "name": "高津区"},
        {"cityId": 6, "id": 5, "name": "多摩区"},
        {"cityId": 6, "id": 6, "name": "宮前区"},
        {"cityId": 6, "id": 7, "name": "麻生区"},

        {"cityId": 7, "id": 1, "name": "緑区"},
        {"cityId": 7, "id": 2, "name": "中央区"},
        {"cityId": 7, "id": 3, "name": "南区"},

        {"cityId": 8, "id": 1, "name": "北区"},
        {"cityId": 8, "id": 2, "name": "東区"},
        {"cityId": 8, "id": 3, "name": "中央区"},
        {"cityId": 8, "id": 4, "name": "江南区"},
        {"cityId": 8, "id": 5, "name": "秋葉区"},
        {"cityId": 8, "id": 6, "name": "南区"},
        {"cityId": 8, "id": 7, "name": "西区"},
        {"cityId": 8, "id": 8, "name": "西蒲区"},

        {"cityId": 9, "id": 1, "name": "葵区"},
        {"cityId": 9, "id": 2, "name": "駿河区"},
        {"cityId": 9, "id": 3, "name": "清水区"},

        {"cityId": 10, "id": 1, "name": "中区"},
        {"cityId": 10, "id": 2, "name": "東区"},
        {"cityId": 10, "id": 3, "name": "西区"},
        {"cityId": 10, "id": 4, "name": "南区"},
        {"cityId": 10, "id": 5, "name": "北区"},
        {"cityId": 10, "id": 6, "name": "浜北区"},
        {"cityId": 10, "id": 7, "name": "天竜区"},

        {"cityId": 11, "id": 1, "name": "千種区"},
        {"cityId": 11, "id": 2, "name": "東区"},
        {"cityId": 11, "id": 3, "name": "北区"},
        {"cityId": 11, "id": 4, "name": "西区"},
        {"cityId": 11, "id": 5, "name": "中村区"},
        {"cityId": 11, "id": 6, "name": "中区"},
        {"cityId": 11, "id": 7, "name": "昭和区"},
        {"cityId": 11, "id": 8, "name": "瑞穂区"},
        {"cityId": 11, "id": 9, "name": "熱田区"},
        {"cityId": 11, "id": 10, "name": "中川区"},
        {"cityId": 11, "id": 11, "name": "港区"},
        {"cityId": 11, "id": 12, "name": "南区"},
        {"cityId": 11, "id": 13, "name": "守山区"},
        {"cityId": 11, "id": 14, "name": "緑区"},
        {"cityId": 11, "id": 15, "name": "名東区"},
        {"cityId": 11, "id": 16, "name": "天白区"},

        {"cityId": 12, "id": 1, "name": "北区"},
        {"cityId": 12, "id": 2, "name": "上京区"},
        {"cityId": 12, "id": 3, "name": "左京区"},
        {"cityId": 12, "id": 4, "name": "中京区"},
        {"cityId": 12, "id": 5, "name": "東山区"},
        {"cityId": 12, "id": 6, "name": "下京区"},
        {"cityId": 12, "id": 7, "name": "南区"},
        {"cityId": 12, "id": 8, "name": "右京区"},
        {"cityId": 12, "id": 9, "name": "伏見区"},
        {"cityId": 12, "id": 10, "name": "山科区"},
        {"cityId": 12, "id": 11, "name": "西京区"},

        {"cityId": 13, "id": 1, "name": "都島区"},
        {"cityId": 13, "id": 2, "name": "福島区"},
        {"cityId": 13, "id": 3, "name": "此花区"},
        {"cityId": 13, "id": 4, "name": "西区"},
        {"cityId": 13, "id": 5, "name": "港区"},
        {"cityId": 13, "id": 6, "name": "大正区"},
        {"cityId": 13, "id": 7, "name": "天王寺区"},
        {"cityId": 13, "id": 8, "name": "浪速区"},
        {"cityId": 13, "id": 9, "name": "西淀川区"},
        {"cityId": 13, "id": 10, "name": "東淀川区"},
        {"cityId": 13, "id": 11, "name": "東成区"},
        {"cityId": 13, "id": 12, "name": "生野区"},
        {"cityId": 13, "id": 13, "name": "旭区"},
        {"cityId": 13, "id": 14, "name": "城東区"},
        {"cityId": 13, "id": 15, "name": "阿倍野区"},
        {"cityId": 13, "id": 16, "name": "住吉区"},
        {"cityId": 13, "id": 17, "name": "東住吉区"},
        {"cityId": 13, "id": 18, "name": "西成区"},
        {"cityId": 13, "id": 19, "name": "淀川区"},
        {"cityId": 13, "id": 20, "name": "鶴見区"},
        {"cityId": 13, "id": 21, "name": "住之江区"},
        {"cityId": 13, "id": 22, "name": "平野区"},
        {"cityId": 13, "id": 23, "name": "北区"},
        {"cityId": 13, "id": 24, "name": "中央区"},

        {"cityId": 14, "id": 1, "name": "堺区"},
        {"cityId": 14, "id": 2, "name": "中区"},
        {"cityId": 14, "id": 3, "name": "東区"},
        {"cityId": 14, "id": 4, "name": "西区"},
        {"cityId": 14, "id": 5, "name": "南区"},
        {"cityId": 14, "id": 6, "name": "北区"},
        {"cityId": 14, "id": 7, "name": "美原区"},

        {"cityId": 15, "id": 1, "name": "東灘区"},
        {"cityId": 15, "id": 2, "name": "灘区"},
        {"cityId": 15, "id": 3, "name": "兵庫区"},
        {"cityId": 15, "id": 4, "name": "長田区"},
        {"cityId": 15, "id": 5, "name": "須磨区"},
        {"cityId": 15, "id": 6, "name": "垂水区"},
        {"cityId": 15, "id": 7, "name": "北区"},
        {"cityId": 15, "id": 8, "name": "中央区"},
        {"cityId": 15, "id": 9, "name": "西区"},

        {"cityId": 16, "id": 1, "name": "北区"},
        {"cityId": 16, "id": 2, "name": "中区"},
        {"cityId": 16, "id": 3, "name": "東区"},
        {"cityId": 16, "id": 4, "name": "南区"},

        {"cityId": 17, "id": 1, "name": "中区"},
        {"cityId": 17, "id": 2, "name": "東区"},
        {"cityId": 17, "id": 3, "name": "南区"},
        {"cityId": 17, "id": 4, "name": "西区"},
        {"cityId": 17, "id": 5, "name": "安佐南区"},
        {"cityId": 17, "id": 6, "name": "安佐北区"},
        {"cityId": 17, "id": 7, "name": "安芸区"},
        {"cityId": 17, "id": 8, "name": "佐伯区"},

        {"cityId": 18, "id": 1, "name": "門司区"},
        {"cityId": 18, "id": 2, "name": "若松区"},
        {"cityId": 18, "id": 3, "name": "戸畑区"},
        {"cityId": 18, "id": 4, "name": "小倉北区"},
        {"cityId": 18, "id": 5, "name": "小倉南区"},
        {"cityId": 18, "id": 6, "name": "八幡東区"},
        {"cityId": 18, "id": 7, "name": "八幡西区"},

        {"cityId": 19, "id": 1, "name": "東区"},
        {"cityId": 19, "id": 2, "name": "博多区"},
        {"cityId": 19, "id": 3, "name": "中央区"},
        {"cityId": 19, "id": 4, "name": "南区"},
        {"cityId": 19, "id": 5, "name": "西区"},
        {"cityId": 19, "id": 6, "name": "城南区"},
        {"cityId": 19, "id": 7, "name": "早良区"},

        {"cityId": 20, "id": 1, "name": "中央区"},
        {"cityId": 20, "id": 2, "name": "東区"},
        {"cityId": 20, "id": 3, "name": "西区"},
        {"cityId": 20, "id": 4, "name": "南区"},
        {"cityId": 20, "id": 5, "name": "北区"}
    ]
}

Vueで選択入力を連動させる

セレクトボックスを連動する

まずはセレクトボックスボックスの連動です。

<html>
<body>
    <h1>Vueを使って連動するセレクトボックス</h1>
    <div id="app">
        <h5>【市】</h5>
        <select name="city_id" v-model="selectedCityId" @change="onChangeCity">
            <option></option>
            <option v-for="city in cities" :value="city.id" v-text="city.name"></option>
        </select>
        <h5>【区】</h5>
        <select name="ward_id" v-model="selectedWardId">
            <option></option>
            <option v-for="ward in filteredWards" :value="ward.id" v-text="ward.name"></option>
        </select>
        <hr>
        <div>
            <h5>【選択された値】</h5>
            市: <strong v-text="selectedCityId"></strong><br>
            区: <strong v-text="selectedWardId"></strong>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.min.js"></script>
    <script>

        new Vue({
            el: '#app',
            data: {
                cities: [],
                wards: [],
                selectedCityId: -1,
                selectedWardId: -1
            },
            methods: {
                onChangeCity() {

                    this.selectedWardId = -1;

                    if(!this.selectedCityId) {

                        this.selectedCityId = -1;

                    }

                }
            },
            computed: {
                filteredWards() {

                    let filteredWards = [];

                    for(let i = 0 ; i < this.wards.length ; i++) {

                        let ward = this.wards[i];

                        if(ward.cityId == this.selectedCityId) {

                            filteredWards.push(ward);

                        }

                    }

                    return filteredWards;

                }
            },
            mounted() {

                axios.get('/json/city-wards.json')
                    .then((response) => {

                        this.cities = response.data.cities;
                        this.wards = response.data.wards;

                    });

            }
        });

    </script>
</body>
</html>

まずmounted()でページ読み込みと同時に先ほどのjsonファイルを読み込みます。(これはその他の場合も同じです)

そして、この中で一番重要なのがcomputed内のfilteredWards()です。

Vuecomputedは、「何かデータに変更があったら自動的に再計算する」という機能で、filteredWardsという文字列をあたかも変数であるかのように使うことが出来ます。

なお、実際には以下の部分に使われています。

<option v-for="ward in filteredWards" :value="ward.id" v-text="ward.name"></option>

つまり、市のデータであるselectedCityIdに変化があると、区の選択肢であるfilteredWardsが自動的に変更になってくれるわけですね。

では、今回は3パターンあるということでひとつずつテスト結果をご覧ください。

↓↓↓

【Vueを使って連動するセレクトボックスのサンプル】

ラジオボタンを連動させる

続いてラジオボタンの連動です。

<html>
<body>
    <h1>Vueを使って連動するラジオボタン</h1>
    <div id="app">
        <h5>【市】</h5>
        <label v-for="city in cities">
            <input type="radio" :value="city.id" v-model="selectedCityId" @change="onChangeCity">
            <span v-text="city.name"></span>
            &nbsp;
        </label>
        <h5>【区】</h5>
        <label v-for="ward in filteredWards">
            <input type="radio" :value="ward.id" v-model="selectedWardId">
            <span v-text="ward.name"></span>
            &nbsp;
        </label>
        <hr>
        <div>
            <h5>【選択された値】</h5>
            市: <strong v-text="selectedCityId"></strong><br>
            区: <strong v-text="selectedWardId"></strong>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.min.js"></script>
    <script>

        new Vue({
            el: '#app',
            data: {
                cities: [],
                wards: [],
                selectedCityId: -1,
                selectedWardId: -1
            },
            methods: {
                onChangeCity() {

                    this.selectedWardId = -1;

                    if(!this.selectedCityId) {

                        this.selectedCityId = -1;

                    }

                }
            },
            computed: {
                filteredWards() {

                    let filteredWards = [];

                    for(let i = 0 ; i < this.wards.length ; i++) {

                        let ward = this.wards[i];

                        if(ward.cityId == this.selectedCityId) {

                            filteredWards.push(ward);

                        }

                    }

                    return filteredWards;

                }
            },
            mounted() {

                axios.get('/json/city-wards.json')
                    .then((response) => {

                        this.cities = response.data.cities;
                        this.wards = response.data.wards;

                    });

            }
        });

    </script>
</body>
</html>

ラジオボタンは先ほどのセレクトボックスとほぼ同じコードで実現できますので、詳しい内容は上の項目をご覧ください。

では、こちらもテスト結果です。

↓↓↓

【Vueを使って連動するラジオボタンのサンプル】

チェックボックスを連動する

では最後にチェックボックスを連動する方法です。

<html>
<body>
    <h1>Vueを使って連動するチェックボックス</h1>
    <div id="app">
        <h5>【市】</h5>
        <label v-for="city in cities">
            <input type="checkbox" :value="city.id" v-model="selectedCityIds" @change="onChangeCity">
            <span v-text="city.name"></span>
            &nbsp;
        </label>
        <h5>【区】</h5>
        <label v-for="ward in filteredWards">
            <input type="checkbox" :value="ward.id" v-model="selectedWardIds">
            <span v-text="ward.name"></span>
            &nbsp;&nbsp;
        </label>
        <hr>
        <div>
            <h5>【選択された値】</h5>
            市: <strong v-text="selectedCityIds"></strong><br>
            区: <strong v-text="selectedWardIds"></strong>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.min.js"></script>
    <script>

        new Vue({
            el: '#app',
            data: {
                cities: [],
                wards: [],
                selectedCityIds: [],
                selectedWardIds: []
            },
            methods: {
                onChangeCity() {

                    this.selectedWardIds = [];

                    if(!this.selectedCityIds) {

                        this.selectedCityIds = [];

                    }

                }
            },
            computed: {
                filteredWards() {

                    let filteredWards = [];

                    for(let i = 0 ; i < this.wards.length ; i++) {

                        let ward = this.wards[i];

                        if(this.selectedCityIds.includes(ward.cityId)) {

                            let city = this.cities.find((city) => {

                                return (city.id == ward.cityId);

                            });

                            let id = city.id +'-'+ ward.id;
                            let name = city.name + ward.name;
                            filteredWards.push({
                                id: id,
                                name: name
                            });

                        }

                    }

                    return filteredWards;

                }
            },
            mounted() {

                axios.get('/json/city-wards.json')
                    .then((response) => {

                        this.cities = response.data.cities;
                        this.wards = response.data.wards;

                    });

            }
        });

    </script>
</body>
</html>

チェックボックスの場合もセレクトボックスやラジオボタンと構造的には同じですが、重要なのは「チェックボックスの場合は選択が複数になる」という部分です。

そのため、選択された市と区の変数は以下のように複数形に変更し、配列でデータが入るようになっています。

  • selectedCityIds
  • selectedWardIds

また、これに伴ってfilteredWards()「チェックが入っている市に所属する区すべて」を抽出するように変更になっています。

では、実際のテスト結果です。

↓↓↓

【Vueを使って連動するチェックボックスのサンプル】

ソースコードをダウンロードする

今回実際に開発したコードを以下からダウンロードすることができます。

※ ただし、Ajaxでデータを取得しているのでファイル単体では動きません。必ずウェブサーバー上で実行してください。

Vueでセレクトボックス、ラジオボタン、チェックボックスを連動する
開発のご依頼お待ちしております
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします! by 九保すこひ

おわりに

ということで、久しぶりにVueを使って選択肢を連動させる方法を紹介しました。

今回実装した内容は実際にクライアント様からご要望としてもご依頼いただいたこともありますので、使う側からするとかなりユーザビリティが高くなると思います。特にたくさんの登録をする場合は選択肢にフィルターがかかるだけで作業が早くなりますし、また、間違いも減らることができるじゃないでしょうか。

ぜひ皆さんのウェブサイトには使ってみてくださいね。

ではでは〜😊✨

このエントリーをはてなブックマークに追加       follow us in feedly