[Vue.js] サジェスト/自動補完(autocomplete)を行うコンポーネント

## はじめに

Vue.jsで、某大手検索エンジンのような、サジェストリストを表示する入力コンポーネントを作成しました。npmで公開していますので、良かったら使ってやってください。

## どんな機能

テキストボックスに文字を途中まで入力すると、残りの候補をサジェスト表示（自動補完）する機能を持つVue.jsのテキストボックスコンポーネントです。検索エンジンのサイトなどでよく見かけるあれです。

以下のGIFアニメを見てもらえれば、一発でどんなコンポーネントかが分かると思います。

![サジェスト入力動画.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421986/5023e23e-0ec0-d8b3-54e9-bb2e32813328.gif)


## シンプルなサンプル

とにもかくにも、サンプルコードです。`data()`で指定した配列をサジェストリストに表示するシンプルなサンプルです。

```html
<template>
  <div style="width:400px">
    <vue-suggest-input v-model="selected" :items="items"/>
  </div>
</template>
<script>
import VueSuggestInput from 'vue-suggest-input'
import 'vue-suggest-input/dist/vue-suggest-input.css'

export default {
  components: {
    VueSuggestInput
  },
  data() {
    return {
      items: ['apple','cocoa','coffee','cola'],
      selected: "",
    }
  }
}
</script>
```

実際の運用ではREST APIなどで、外部からサジェストリストに表示する内容をロードすると思いますが、その方法は後述。

## インストール

npmでインストールできます。

```
npm i vue-suggest-input --save
```

## キーボードショートカット

ショートカットキーで、サジェストリストの表示や、リスト内の移動・選択が行えます。

### サジェストリストが非表示の時

| Key | 説明 |
|:--|:--|
| 上矢印(↑)、下矢印(↓) | サジェストリストを表示します |
| Enter | `search`イベントを発生させます。 |

### サジェストリストが表示されている時

| Key | 説明 |
|:--|:--|
| Escape(ESC) | サジェストリストを閉じます。 |
| 上矢印(↑) | 候補の選択を上に移動します。 |
| 下矢印(↓) | 候補の選択を下に移動します。 |
| Enter | サジェストリストで選択した候補を入力エリアに設定し、`search`イベントを発生させます。 |

## プロパティ (Props)

### items

**型：** Array or Function or Promise
**デフォルト値：** null
#### 概要

サジェストに表示する文字列(String)を配列を指定します。配列、関数、Promiseのいずれかの型を指定します。関数またはPromiseで指定する場合、関数からの戻り値は配列（Array）である必要があります。

- 配列で指定する場合の例

```html
<vue-suggest-input :items="['item1', 'item2']"/>
```

- Promiseを指定する場合の例

```html:template
<vue-suggest-input :items="getItems"/>
```

```js:javascript
export  default {
  methods: {
    async getItems() {
      const { data } = await this.$axios.get("url")    
      return data
    }
  }
}
```

### filter-func

**型：** Function
**デフォルト値：** null
#### 概要

サジェストの内容をフィルタ処理するためのカスタム関数を指定できます。カスタム関数には2つ引数が渡されます。引数の1つ目は、フィルタを行うサジェストの内容、2つ目は入力エリアの内容が渡されます。

カスタム関数は`bool`型の戻り値を返す必要があります。`true`を返した場合、1つ目の引数のサジェストが候補として表示されます。`false`を返した場合は表示されません。

下のサンプルコードは、サジェストに`11`の文字列を含むものだけを、候補のリストに表示するようになっています。

```html:template
<vue-suggest-input 
  :item="['item011', 'item012', 'item111']"
  :filter-func="myFilter"/>
```

```js:javascript
export  default {
  methods: {
    myFilter(suggestItem, inputValue) {
      return suggestItem.indexOf("11") >= 0
    }
  }
}
```

### max-suggest

**型：** Number
**デフォルト値：** 10
#### 概要

表示するサジェストの最大数を指定します。

```html:template
<vue-suggest-input :max-suggest="5"/>
```

### query-interval

**型：** Number
**デフォルト値：** 100
#### 概要

サジェストリストを再検索する間隔をミリ秒単位で設定します。例えば`1000`を指定した場合、1秒感覚で`items`プロパティに指定した配列または関数から、サジェストの検索を行います。

逆に、入力の度にすぐに候補の検索を行いたい場合は`0`を指定します。

```html:template
<vue-suggest-input :query-interval="1000"/>
```

### loading-show-delay

**型：** Number
**デフォルト値：** 500
#### 概要

サジェストの検索中に、「読み込み中…」の表示を行うまで時間をミリ秒単位で指定します。例えば`500`を指定した場合、サジェストの検索に500ミリ秒以上の時間を要している場合、入力欄の下に「読み込み中…」の表示が行われます。

サジェストの検索に時間を要する場合に有用なプロパティです。

## イベント

### search

サジェストリストから候補を選択した時や、Enterキーで入力を確定させた時に発生するイベントです。主にこのコンポーネントに入力された検索条件をもとに、メインの検索処理を行うようような場合に使用します。

```html:template
<vue-suggest-input @search="doSearch()"/>
```

### input

入力の度に発生するイベントです。これは`HTMLElement`の`inpput`イベントと等価です。

```html:template
<vue-suggest-input @input="doInput()"/>
```

## アイコンのカスタマイズ

入力エリア右のアイコンを、独自のアイコン等に置き換えることができます。デフォルトでは、下の画像のような、虫眼鏡のSVGアイコンが表示されます。

![入力エリア右のアイコン](https://lh3.googleusercontent.com/QyK6_3PNSDfFceU0F1QqJ3PcR9zCS3MBkAU40gM5Qleq1PxGIEsplKtNrgOZSVDDlmOXDuFzyrQj)

アイコンのカスタマイズには、slotを使います。以下がサンプルコードです。

```html:slotでアイコンを置き換えるサンプル
<vue-suggest-input>
  <div slot="add-on-icon">
    <i class="fa fa-rocket" aria-hidden="true"></i>
  </div>
</vue-suggest-input>
```

上のコードの場合、次のイメージのようにアイコンがslotで指定したものに置きかわります。

![アイコンカスタマイズ後の画面イメージ](https://lh3.googleusercontent.com/tJcg7aZjed6ESkd4mjplkmgy8fcmOzMVMyS9FVR0MAQDJZq343vGvXnE-McPu4g9MmM7jy_yG60h)


## サジェスト項目のカスタマイズ

defaultスロットに、カスタマイズのHTMLを設定すると、サジェスト項目のデザインをカスタマイズする事ができます。

```html
<vue-suggest-input>
  <template slot-scope="{text}">
    <div>
      <i class="fa fa-code" aria-hidden="true"></i>
      <span>{{text}}</span>
    </div>
  </template>
</vue-suggest-input>
```

【実行結果】
![カステムサジェストの実行結果](https://lh3.googleusercontent.com/PDoCPe7XzkiZfqkJK2fy_sDohv-F8NmddV-aHMDP0ePEIjxwTs-2tXJ_yhfoW_GewL7Xvjq7m3KQ)


## さいごに

バグ等あれば、コメントで教えてもらえたら喜びます。