インクリメンタルサーチ

# 概要
- [他の方のコード](http://qiita.com/Kohei_Kishimoto0214/items/2f08bf430a34023ef01d#comment-ab2b5d457ed2207a8b77)を書き直した。
- class構文を使い、大幅にリファクタリングした。

# サンプルコード
- [実行サンプル](http://codepen.io/yama-t/pen/JErGNd)

```html
<div class="form-group">
  <input type='text' id="keyword" class="form-control" placeholder="好きなフルーツを入力してください">
  <button type="button" id="submit" class="btn">検索</button>
</div>
<ul id="list"></ul>
```

```css
/* twitter-bootstrap使用 */

#keyword {
  display: inline-block;
  width: 50%;
}
#submit {
  display: inline-block;
  background-color: #6495ed;
  color: #fff;
  padding: 5.5px 20px;
  vertical-align: top;
}

#list {
  padding: 0;
  margin: 0 auto;
  width: 50%;
  list-style: none;
}

li {
  border-radius: 5px;
  box-shadow: 0.5px 0.5px 1px 1px rgba(0,0,0,0.1) inset;
  background-color: #eee;
  margin: 10px;
  padding: 10px;
}

.form-group {
  width: 75%;
  margin: 20px auto;
  text-align: center;
}

```

```js
const fruits = ['apple', 'apricot', 'avocado', 'blueberry', 'cherry', 'coconut', 'cranberry', 'dragonfruit', 'durian', 'grape', 'grapefruit', 'guava', 'kiwi fruit', 'lemon', 'lime', 'lychee', 'mango', 'melon', 'watermelon', 'miracle fruit', 'orange', 'bloodorange', 'clementine', 'mandarine', 'tangerine', 'papaya', 'passionfruit', 'peach', 'pear', 'persimmon', 'physalis', 'plum/prune', 'pineapple', 'pomegranate', 'raspberry', 'rambutan', 'star fruit', 'strawberry'];

class IncrementalSearch {
  constructor(option) {
    const { input, result, data, notFound } = option;

    this.data = data;
    this.$input = $(input);
    this.$result = $(result);
    this.preVal = '';
    this.notFound = notFound || 'not found';
    this.bindEvent();
  }
  bindEvent() {
    this.$input.on('keyup', () => this.suggestList());
  }
  makeTopMatchStr(str) {
    return '^' + str;
  }
  makeReg(str){
    const regStr = str.split('').map(this.makeTopMatchStr).join('|');
    
    return new RegExp(regStr);
  }
  filterData(str) {
    const reg = this.makeReg(str);
    
    return this.data.filter(v => reg.test(v));
  }
  appendList(data) {
    const $list = data.map(word => $('<li />').addClass('list').text(word));

    this.$result.append($list);
  }
  clearList() {
    this.$result.html('');
  }
  suggestList() {
    const val = this.$input.val().trim().replace(/\s/g, '');

    if (val === this.preVal) {
      return;
    }

    this.clearList();
    this.preVal = val;

    if (val.length === 0) {
      return;
    }

    const matchData = this.filterData(val),
      result = matchData.length > 0 ? matchData : [this.notFound];
    
    this.appendList(result);
  }
};

$(function() {
  new IncrementalSearch({
    input: '#keyword',
    result: '#list',
    data: fruits,
    notFound: '一致する果物はありませんでした'
  });
});
```

# 解説
## 分割代入
- 連想配列から値を効率的に取り出し、キー名と同じ変数にそれぞれ代入。

```js
// 実行コードのオプション設定
new IncrementalSearch({
  input: '#keyword',
  result: '#list',
  data: fruits,
  notFound: '一致する果物はありませんでした'
});

// クラス設定
class IncrementalSearch {
  constructor(option) {
    const { input, result, data, notFound } = option;
```

## 空白の削除
- replace + 正規表現で空白指定（`\s`）。
- これで全部消せるのだが、ネイティブメソッドに前後の空白を消す`trim()`があるので、最初にそれをした上で、入力文字列の間に残った空白を消す感じに。

```js
const val = this.$input.val().trim().replace(/\s/g, '');
```

## 正規表現文字列の作成
- `ab`という文字列 → `/^a|^b/`という正規表現に変換。
 - 先頭一致（`^a` `^b`）を or(`|`) で繋いでいく形。 
- `^a|^b`という文字列まで作って、 その文字列を引数に`new RegExp()`すると 正規表現オブジェクトの`/^a|^b/`になる。

```js
makeTopMatchStr(str) {
  return '^' + str;
}
makeReg(str){
  const regStr = str.split('').map(this.makeTopMatchStr).join('|');

  return new RegExp(regStr);
}
```

## 配列から条件に合致する要素を抽出
- filterの基本的な使い方 ＋ 正規表現によるマッチング。
- 正規表現オブジェクトが持つ`test`メソッドを利用すると、結果がtrue, falseで返ってくる。
- filter内で配列内の要素を引数にtestメソッドを使えば、条件を満たすもの（trueを返す要素）だけを抽出できる（`!`を使えば逆も可能）。

```js
filterData(str) {
  const reg = this.makeReg(str);

  return this.data.filter(v => reg.test(v));
}
```

## アペンド
- 通常の配列を元にDOM配列を作成。
- 出来上がったDOM配列を丸ごとappendする。

```js
appendList(data) {
  const $list = data.map(word => $('<li />').addClass('list').text(word));

  this.$result.append($list);
}
```

## 処理の中断
- if文とreturnを利用して、処理を途中で終わらせる。
- returnの後のコードは、それまでの中断条件を全て満たさなかった場合のみ実行される。

```js
// 無条件で実行する

if (val === this.preVal) {
      return;
}

// val !== this.preVal なら実行

if (val.length === 0) {
   return;
}

// val !== this.preVal かつ val.length > 0 の場合に実行
```

# 外部リンク
- [javaScript(jQuery)のインクリメンタルサーチで、全ての行に解説をつけてみた。](http://qiita.com/Kohei_Kishimoto0214/items/2f08bf430a34023ef01d#comment-ab2b5d457ed2207a8b77)
- [RegExp - MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/RegExp)
- [RegExp.prototype.test()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test)