【Vue】filtersで検索キーワードをハイライト！

Vue.jsでこの「検索キーワードのハイライト」がしたかった<img width="754" alt="スクリーンショット 2019-07-04 1.20.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316618/b7904eec-3630-a9e9-7e3f-bf7387fa40fb.png">

Vue.jsのfiltersを使えばカンタンだね

# 1. まず、用意
vue-cliで、プロジェクト作るまでは割愛するよ〜

プロジェクトが出来たら、App.vue, components/Media.vueに、サンプルテキストを準備する

```vuejs:/App.vue
<template>
  <div id="app">
    <div class="container">
      <div class="col">
        <div class="row">
          <input class="form-control col-6"
                 type="text"
                 v-model="search" // 検索キーワード
                 placeholder="検索"
                 aria-label="Search">
        </div>
        <media v-for="(media, index) in medias"
               :key="index"
               :heading="media.heading"
               :body="media.body"
               class="mt-3"/>
      </div>
    </div>
  </div>
</template>

<script>
import Media from './components/Media'
export default {
  name: 'app',
  components: {
    Media
  },
  data () {
    return {
      search: '', // 検索キーワード
      medias: [
        {
          heading: 'その1',
          body: 'それも直接あにその始末心というののところをしたた。\n'
        },
        {
          heading: 'その2',
          body: 'ひょろひょろ始めに建設人はたといこの発展たですまでを行かて行かますをは盲従出さでだから、もともとにはできないたたた。'
        },
        {
          heading: 'その3',
          body: '個人をすむたい事もむしろ偶然に同じくたただ。\n'
        },
        {
          heading: 'その4',
          body: 'やはり槙さんへ想像がたどう安心に繰り返しです嚢この人皆か関係にってご返事ですますなかっですから、そうした今は私か国家学校があって、木下さんのものの学生のあなたにいよいよご演説と勧めてあなた主義で大発展に移れように何しろご講演と得んないて、はなはだもっとも発展が構わないて来るん方にかけるなりない。それならもっともご権力を聞いものはぴたり正直と直さですて、どんな頭にはすれないばといった中からしが来なない。'
        },
        {
          heading: 'その5',
          body: 'その時国の時同じ釣はあなた中をありでしょかと岡田さんに云ったう、新のたくさんですという小立脚なだませので、自己のところが他がほかかもの仲間を今込み入って行かで、少々の今をしのでその時にああ使いこなすでたといだつもりまして、忌まわしいらしいたて少しお作物知らで事んなた。'
        }
      ]
    }
  }
}
</script>
```

```vue:/components/Media.vue
<template>
    <div class="media">
        <img class="mr-3" src="https://via.placeholder.com/150" alt="Generic placeholder image">
        <div class="media-body">
            <h5 class="mt-0">{{ heading }}</h5>
            <p>{{ body }}</p>
        </div>
    </div>
</template>

<script>
export default {
    props: ['heading', 'body']
}
</script>
```

見た目
<img width="1033" alt="スクリーンショット 2019-07-04 1.38.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316618/ee5323a4-ac63-cc26-adb8-18a8a76c33a8.png">

# 2. filtersを追加
[公式のフィルターの項目](https://jp.vuejs.org/v2/guide/filters.html)を参考に、searchHighlightを追加してみよう

```vue:App.vue
<template>
  <div id="app">
    <div class="container">
      <div class="col">
        <div class="row">
          <input v-model="search" class="form-control col-6" type="text" placeholder="検索" aria-label="Search">
        </div>
        <media v-for="(media, index) in medias"
               :key="index"
               :heading="media.heading"
               :body="media.body"
               :search="search" // ここを追加 フィルターに使う検索キーワードを渡す
               class="mt-3"/>
      </div>
    </div>
  </div>
</template>
```

```vue:/components/Media.vue
<template>
    <div class="media">
        <img class="mr-3" src="https://via.placeholder.com/150" alt="Generic placeholder image">
        <div class="media-body">
            <!-- | を使って、フィルターした値を表示できるぜ -->
            <h5 class="mt-0">{{ heading | searchHighlight(search) }}</h5>
            <p>{{ body | searchHighlight(search) }}</p>
        </div>
    </div>
</template>

<script>
export default {
    props: ['heading', 'body', 'search'], // propsにsearchを追加
    filters: {
        searchHighlight (value, search) {
            // 検索キーワードが入力されているとき
            if (search) {
                // 検索キーワードに一致する部分を、spanタグに置換すればいいね
                return value.replace(search, `<span class="bg-warning">${search}</span>`)
            }
            // 検索キーワードが入力されていない場合は、ハイライトしない
            return value
        }
    }
}
</script>
```

すると、、、
<img width="1033" alt="スクリーンショット 2019-07-04 1.49.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316618/8831bbd9-9d2e-ada8-d8e1-9e610a16ab27.png">

おや？エスケープされているぞ。

# 3. v-htmlを使いなさい

[公式の見解](https://jp.vuejs.org/v2/guide/syntax.html#%E7%94%9F%E3%81%AE-HTML)によると、
<img width="966" alt="スクリーンショット 2019-07-04 1.53.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316618/47bb11f6-d310-78df-0caf-5be8c894daa0.png">
v-htmlディレクティブを使えとさ。

v-htmlで、フィルターを使うには、Google先生に聞いたところ

```vue

<h5 class="mt-0" v-html="$options.filters.searchHighlight(heading, search)"></h5>
```
こう書くらしい。(optionsの中身を知りたい方は、mounted()メソッド内で、console.log(this.$options)をしてみよう)

この書き方をすると、

```vue:/components/Media.vue
<template>
    <div class="media">
        <img class="mr-3" src="https://via.placeholder.com/150" alt="Generic placeholder image">
        <div class="media-body">
            <!-- v-htmlを使用 -->
            <h5 class="mt-0" v-html="$options.filters.searchHighlight(heading, search)"></h5>
            <p v-html="$options.filters.searchHighlight(body, search)"></p>
        </div>
    </div>
</template>

<script>
export default {
    props: ['heading', 'body', 'search'],
    filters: {
        searchHighlight (value, search) {
            if (search) {
                return value.replace(search, `<span class="bg-warning">${search}</span>`)
            }
            return value
        }
    }
}
</script>

```

すると、、、
<img width="1165" alt="スクリーンショット 2019-07-04 2.06.33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316618/3c3891f2-d308-8e1c-e5e1-53345caccc8a.png">

####出来、、、、てない！

####各文の最初の「た」 しか、ハイライトされていない！！

### でも正規表現を使えば解決だ

# 4. 正規表現を使う

```vuejs
        searchHighlight (value, search) {
            if (search) {
                // 正規表現で、一致する文字全てをハイライトする
                const searchRegExp = new RegExp(search, 'ig')
                return value.replace(searchRegExp, match => `<span class="bg-warning">${match}</span>`)
            }
            return value
        }
```

すると、
<img width="1195" alt="スクリーンショット 2019-07-04 2.10.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/316618/5dd859fb-97f0-b7f8-e1ec-ee33bef158b1.png">

### 出来た！！
[ソースコードでございます](https://github.com/YoshijiFujiwara/qiita-vue-search-highlight)