Vue Selectを導入して、いろいろカスタマイズしてみる

## はじめに
業務でVue Selectを利用する機会があったので、諸々のカスタマイズについての備忘録。
とても便利なライブラリだが、細かな情報を探すのに苦労したのでここに残しておく。

（間違いなどあれば、コメントにてご指摘いただければ幸いです。）

## Vue Selectとは
Vue.jsで利用可能なセレクトボックスコンポーネント。
[公式サイト](https://vue-select.org/) 曰く
> Everything you wish the HTML <select> element could do, wrapped up into a lightweight, extensible Vue component.
> (HTMLの<select>要素にできることをすべて、軽量で拡張性の高いVueコンポーネントにまとめました。)

この記載通りで、とても手軽にセレクトボックスを実装できる。
さらに、選択肢に対する前方一致の絞り込みなどの機能も利用可能な優秀なライブラリ。


## 筆者環境
Vue.js:  2.6
Vue Select: 3.1
(Laravel: 8.1上で利用)


## How to Use
こちらも[公式サイト](https://vue-select.org/guide/install.html#yarn-npm)が分かりやすい。
ざっくりnpmの場合を記載しておくと、
　①npm installを実行
```powershell
npm install vue-select
```
　②コンポーネントとして登録
```javascript:app.js 
import Vue from 'vue'
import vSelect from 'vue-select' 

Vue.component('v-select', vSelect) 
```
以上で利用準備は完了。あとは自分の使いたいvueファイルで\<v-select>を利用するのみ。

## さまざまなカスタマイズを試してみる
　以下コードはSFCとして記載。
### 基本
選択肢べた書きパターン。
タグの中でv-bind:options="選択肢の配列"を設定する。
```javascript:Sample.vue
<template>
    <v-select
        v-bind:options="['small', 'medium', 'large']"
    >
    </v-select>
</template>
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/771526/64b740ad-ce26-b1e0-c15b-3ce077d63d9e.png)

テキスト入力で、前方一致で選択肢絞り込みもしてくれる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/771526/d0145f68-9e1b-1dd5-7668-44d7e4130326.png)


### 配列をdata()に指定する
Vueを使っていてデータをハードコーディングすることは少ないと思うので、dataの値を反映してみる。


```javascript:Sample.vue
<template>
    <v-select
        v-bind:options="sizes"
    >
    </v-select>
</template>

<script>
export default {
    data() {
        return{
            sizes: ['small', 'medium', 'large'],
        }
    },
}
</script>
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/771526/628a5b92-5e39-7aa6-0469-540b5faba558.png)
選択するとこのような感じ。（中途半端にCSSが付いているのはスルーいただきたい…）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/771526/8cb1ad7d-3355-fd52-1b80-160841b1db2a.png)


### 選択後にボックス内に表示される'×'を消す
clearableをfalseで指定。
```javascript:Sample.vue
<template>
    <v-select
        v-bind:options="sizes"
        v-bind:clearable="false"
    >
    </v-select>
</template>
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/771526/c0b2219f-2950-1f49-abda-ee55a1cf534b.png)


### オブジェクトの配列を選択肢として指定する
画面表示したいプロパティをlabelに設定する。

```javascript:Sample.vue
<template>
    <div>
        <v-select
            v-bind:options="drinks"
            label="name"
        >
        </v-select>
    </div>
</template>

<script>
export default {
    data() {
        return{
            drinks: [
                {
                    name: 'coffee',
                    price: 150,
                },
                {
                    name: 'tea',
                    price: 140,
                },
                {
                    name: 'milk',
                    price: 120,
                },
                {
                    name: 'water',
                    price: 100,
                },

                ]
        }
    },
}
</script>
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/771526/a9676ae4-4ac0-8d07-79a2-f80d34cce02d.png)

### オブジェクトのプロパティから独自の選択肢を作る
\<template>タグを用意し、slotでタグ内のoptionと紐づける。
記載した内容はリスト内の\<li>の中へ反映される。



```javascript:Sample.vue
<template>
    <v-select
        v-bind:options="drinks"
    >
        <template v-slot:option="drink" >
            <span>{{drink.name}} : {{drink.price}} yen</span>
        </template>
    </v-select>
</template>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/771526/752c6721-e7ef-2d4c-af55-a06f04020bfd.png)


### 選択した内容をデータへ反映する
v-modelを利用して、dataへ双方向データバインディングを実現する。
処理の流れは以下の通り。
 ①ユーザーが選択肢を選択する
 ②選択した内容がdataのselectedDrinkの値に反映され、選択した値となる
 ③変更されたselectedDrinkの内容がpタグに反映される
```javascript:Sample.vue
<template>
    <div>
        <v-select
            v-bind:options="drinks"
            label="name"
            v-model="selectedDrink"
        >
        </v-select>
        <p>You selected: {{selectedDrink.name}} : {{selectedDrink.price}}yen</p>
    </div>
</template>

<script>
export default {
    data() {
        return{
            drinks: [], // 省略
            selectedDrink: {
                name: '',
                price: ''
            },
        }
    },
}
</script>
```
選択前
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/771526/daf7e9f4-1859-3355-f785-1a5217c516e9.png)

選択後
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/771526/d8872f86-4e43-f1e6-96e4-dfe3f2a8e358.png)





### 選択肢がない場合のメッセージを指定する
こちらも\<template>とslotで実現する。
デフォルトのメッセージが英語なので、ぜひ設定しておきたい。
```javascript:Sample.vue
<template>
    <v-select
        v-bind:options="sizes"
    >
        <template v-slot:no-options>
            選択肢が存在しません
        </template>
    </v-select>
    </v-select>
</template>
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/771526/bb2b80dd-df99-0ed4-7562-a04bbef8a4a9.png)

### Vue Selectのパーツに任意のCSSを適用する
よく使うだろう属性は[CSS valiables](https://vue-select.org/guide/css.html#css-variables)として提供されているので、そちらを使う。
上記で対応できない場合はクラス指定でCSSを適用する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/771526/1a09acae-b1a2-c4aa-d460-bd788df810eb.png)
上記キャプチャの通り、各パーツに特定のクラスが付与されているので、
対象パーツのクラスを確認してCSSを指定する。

※vue selectに限らないが、SFCでstyleをscopedで記載している場合、子孫コンポーネントへのCSS付与がうまくできないことがある。
その場合はクラス名の前に ::v-deep を記載すると成功する。（>>>や/deep/が使えずに数時間費やした人）

```javascript:Sample.vue
<template>
    <v-select
        v-bind:options="sizes"
        label="name"
    >
    </v-select>
</template>

<script>
    // 省略
</script>

<style lang="scss">
:root {
    // 公式提供のCSS Variablesを利用
    --vs-font-size: 14px;
    --vs-dropdown-max-height: 100px;
}

.v-select {
    background-color: lemonchiffon;

    .vs__selected-options {
        font-size: 18px;
    }
}
</style>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/771526/a9065325-a742-030c-5c3d-20d44b554b16.png)
v-selectクラスに付与した背景色と、変数で指定したmax-heightが効いている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/771526/fd42ae6a-92d1-f766-7395-67f0358e2a64.png)
vs__selected-optionsに指定したfont-sizeも適用されている。


### イベントに応じて処理を行う
Vue Selectが[いくつかのイベントをemitで提供してくれる](https://vue-select.org/api/events.html)ので、それをv-onで拾う。

以下では、この通りの処理を行う。
　①リストを表示したタイミングで、classを付与。background-colorを変更。
　②選択肢が選ばれたタイミングでalert()
　③リストが閉じられたタイミングで、classを外す。background-colorを戻す。

```javascript:Sample.vue
<template>
    <div>
        <v-select
            v-bind:options="drinks"
            label="name"
            v-bind:class="{'selecting': isActive}"
            v-on:input="changeDrink"
            v-on:open="changeListStatus"
            v-on:close="changeListStatus"
        >
        </v-select>
    </div>
</template>

<script>
export default {
    data() {
        return{
            drinks: [],  // 省略
            isActive: false,
        }
    },
    methods: {
        changeDrink: function() {
            alert('drink selected!');
        },
        changeListStatus: function() {
            this.isActive = !this.isActive;
        },
    }
}
</script>

<style lang="scss">
.selecting {
    background-color: lightcoral;
}
</style>
```

①エリアをクリック（リストをopen）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/771526/d25d39b7-15ff-f70e-2a8b-2a707e2493cd.png)

②選択肢をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/771526/7a99f1d5-e41f-5f33-bc01-518c1ce34909.png)

③OKをクリック（リストをclose）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/771526/f2f34824-4e4c-6f70-45bb-583c7e95410e.png)

## 終わりに
ここに書くことのできたカスタマイズはほんの一部で、他にも様々なpropsやeventsが提供されている。
導入・実装もとても簡単なので、Vue.jsでセレクトボックスを利用する場合は、ぜひVue Selectの利用を検討してみてほしい。