[ Vue3 ] Vue-multiselect を用いた、 ドロップダウン の実装と サンプルアプリ

以前、こちらの記事で Vue.js ( Vue3 において Bootstrap 5 の ドロップダウン を実装する方法を紹介しました。ただしその後色々と試したところ、どうやら vue 3.0.0 では実装した ドロップダウン で選択した値を取得することができないようでした。どうしたものかと困っていたところ Vue3 Multiselect を用いることでこれを簡単に解消することができました。

今回は、「 テーブルデータ を Vue-multiselect の ドロップダウン を用いて フィルタリング する サンプルアプリ 」を作成しながら、 Vue-multiselect を実装する方法を整理していきます。
なお、同様のものに Vue-multiselect がありますが、こちらは残念ながら Vue3 ではうまく動作しないようでした。Vue3 Multiselect が Vue2 と Vue3 の両方のサポートを名言している一方で、Vue-multiselect は現状 Vue2 のみの サポート なのかもしれません。

サンプルアプリ の最終形

テーブル の準備

まずは、Vue3 でBootstrap 5 を有効にします。詳細はこちらの記事を参考にしてください。

Bootstrap 5 を有効にしたら、以下のような テーブル を作成します。

ベースとなるテーブル

作成した テーブル の ソースコード

App.vue

<template>
  <TagSearch /> 
</template>
<script>
import TagSearch from './components/TagSearch.vue'
 
export default {
 name: 'App',
 components: {
   TagSearch
 }
}
</script>

components / TagSearch.vue

<template>
   <div>
       <table class="table">
           <thead>
               <tr>
                   <th scope="col">#</th>
                   <th scope="col">First</th>
                   <th scope="col">Last</th>
                   <th scope="col">Age</th>
                   <th scope="col">Handle</th>
               </tr>
           </thead>
 
           <tbody>
               <tr v-for="(row, index) in items" :key="row.Handle">
                   <th scope="row">{{ index+1 }}</th>
                   <td>{{ row.First }}</td>
                   <td>{{ row.Last}}</td>
                   <td>{{ row.Age}}</td>
                   <td>{{ row.Handle}}</td>
               </tr>
           </tbody>
       </table>
   </div>
 
</template>
<script>
   export default {
       data() {
           return {
               items: null
           }
       }
       mounted() {
           this.items = DATA
       },
   }
 
   const DATA = [{
           "First": "Mark",
           "Last": "Otto",
           "Age": 20,
           "Handle": "@mdo"
       },
       {
           "First": "Jacob",
           "Last": "Thornton",
           "Age": 24,
           "Handle": "@fat"
       },
       {
           "First": "Larry",
           "Last": "Bird",
           "Age": 41,
           "Handle": "@twitter"
       },
   ]
</script>

フィルタリング 機能の実装

それでは、作成した テーブル の内容を フィルタリング する機能を実装していきます。まず最初に ドロップダウン を実装するために  Vue3 Multiselect を インストール していきます。

Vue3 Multiselect の インストール

Vue3 Multislect の github README を元に進めていきます。

% npm install @vueform/multiselect

インストール は以上となります。それでは ドロップダウン を実装していきます。


Single Select ドロップダウン

Vue3 Multiselect には 単純な シングルセレクト ドロップダウン 以外にも、マルチセレクト 、タグ といったいくつかの パターン がありますが、今回は最も シンプル な Single Select で進めていきます。

先程作成した  TagSearch.vue に以下を追記していきます。

template (抜粋)

<Multiselect v-model="value" :options="options" />
Selected: {{ value }}

script (抜粋)

<script>
   import Multiselect from '@vueform/multiselect'
 
   export default {
       components: {
           Multiselect,
       },
       data() {
           return {
               value: null,
               options: [
                   'Mark',
                   'Jacob',
                   'Larry',
               ],
           }
       }
   }
</script>

style

<style src="@vueform/multiselect/themes/default.css"></style>

実装した状態

この状態で起動すると、以下のように3つの選択肢が表示される ドロップダウン が実装されます。選択した内容が ドロップダウン の下に表示されるようになっています。


フィルタリング 機能として実装

それでは、続いて ドロップダウン で選択した内容で テーブル を フィルタリング していきます。先程同様、 TagSearch.vue の内容を修正していきます。

template (抜粋)

          <tbody>
               <tr v-for="(row, index) in filteredRow" :key="row.Handle">
                   <th scope="row">{{ index+1 }}</th>
                   <td>{{ row.First }}</td>
                   <td>{{ row.Last}}</td>
                   <td>{{ row.Age}}</td>
                   <td>{{ row.Handle}}</td>
               </tr>
           </tbody>

script (抜粋)

filteredRow メソッド で フィルタリング を実現しています。 ドロップダウン で選択した文字列が テーブル 行のどこかに含まれているもののみを表示するようにしています。

なお、 Age に関しては数値型で データ を保持しているため文字列に変換しています。

      computed: {
           filteredRow() {
               if (this.value === null) {
                   return this.items
               } else {
                   let selected = this.value
                   console.log(selected)
                   return this.items.filter(function (el) {
                       return el.First.includes(selected) ||
                              el.Last.includes(selected) ||
                              el.Handle.includes(selected) ||
                              el.Age.toString().includes(selected)
                   })
               }
           }
       },

実装した状態


まとめ

Vue3 + Bootstrap 5 で問題なく Bootstrap を活用できると思っていましたが、 ドロップダウン のような動的な コンポーネント については今回のような実装をする必要があることがわかりました。

  • Vue3 + Bootstrap 5 だけでは ドロップダウン の値を取得できない
  • Vue3 Multiselect を用いることで、 ドロップダウン の値が取得可能
  • Vue3 Multiselect は単一選択だけではなく、複数選択 も可能
  • 類似の Vue-Multiselect (v2.1.4)  は Vue3 には対応していない模様

関連記事

参照情報

フィルタリング関連

How to filter items in Vue 3?

Mapping an Array to Elements with v-for

Displaying Filtered/Sorted Results


今回使用したソースコード

App.vue

<template>
  <TagSearch />
</template>
<script>
import TagSearch from "./components/TagSearch.vue"

export default {
  name: "App",
  components: {
    TagSearch,
  },
}
</script>
<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

components / TagSearch.vue

<template>
  <div>
    <Multiselect v-model="value" :options="options" />
    Selected: {{ value }}
    <table class="table">
      <thead>
        <tr>
          <th scope="col">#</th>
          <th scope="col">First</th>
          <th scope="col">Last</th>
          <th scope="col">Age</th>
          <th scope="col">Handle</th>
        </tr>
      </thead>

      <tbody>
        <tr v-for="(row, index) in filteredRow" :key="row.Handle">
          <th scope="row">{{ index + 1 }}</th>
          <td>{{ row.First }}</td>
          <td>{{ row.Last }}</td>
          <td>{{ row.Age }}</td>
          <td>{{ row.Handle }}</td>
        </tr>
      </tbody>
    </table>
  </div>
</template>
<script>
import Multiselect from "@vueform/multiselect"

export default {
  components: {
    Multiselect,
  },
  data() {
    return {
      value: null,
      options: ["t", "o", "4"],
      items: null,
    }
  },
  mounted() {
    this.items = DATA
  },
  computed: {
    filteredRow() {
      if (this.value === null) {
        return this.items
      } else {
        let selected = this.value
        console.log(selected)
        return this.items.filter(function (el) {
          return (
            el.First.includes(selected) ||
            el.Last.includes(selected) ||
            el.Handle.includes(selected) ||
            el.Age.toString().includes(selected)
          )
        })
      }
    },
  },
}

const DATA = [
  {
    First: "Mark",
    Last: "Otto",
    Age: 20,
    Handle: "@mdo",
  },
  {
    First: "Jacob",
    Last: "Thornton",
    Age: 24,
    Handle: "@fat",
  },
  {
    First: "Larry",
    Last: "Bird",
    Age: 41,
    Handle: "@twitter",
  },
]
</script>
<style src="@vueform/multiselect/themes/default.css"></style>