テーブルの列情報の中で検索したワードを含む行のみ表示したいと思ったことはありませんか?vue.jsを利用するとテーブルの検索機能も簡単に実装することができます。本文書ではテーブル検索(フィルター処理ともいいます)と検索ワードに一致した文字列をハイライトする方法をシンプルな方法で詳細に説明を行なっています。

テーブルの準備

テーブルに表示されるデータを手動で作成することもできますが、通常はテーブルを作成する際は外部のサーバから取得することが基本になるので、本文書ではテーブルのデータはhttps://jsonplaceholder.typicode.com/から取得します。

jsonplaceholderからのデータ取得はaxiosを利用して行います。またvue.jsとaxiosはcdnから利用します。


<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

vue.jsのデータプロパティにusersを追加し、ライフサイクルフッックのmountedでjsonplaceholderからユーザデータを取得しています。


<script>
new Vue({
  el: '#app',
  data: {
    message: 'Search/Filter In Table',
    users:[],
  },
  mounted(){
    axios.get('https://jsonplaceholder.typicode.com/users')
          .then(response => this.users = response.data)
  }
})
</script> 

取得したusersはv-forを使ってtrタグの中で展開を行います。


<table>
    <thead>
        <tr>
            <th>Name</th>
            <th>Email</th>
            <th>Website</th>
        </tr>
    </thead>
    <tbody>
        <tr v-for="user in users" :key="user.id">
            <td>{{ user.name }}</td>
            <td>{{ user.email }}</td>
            <td>{{ user.website }}</td>
        </tr>
    </tbody>
</table>
jsonplaceholderからuser情報を取得するとname, email, website以外にも様々なデータが取得できますが、ここではこの3つのデータのみ利用します。すべてのデータはブラウザでhttps://jsonplaceholder.typicode.com/usersにアクセスすると確認することができます。
fukidashi

テーブルもHTML内にstyleタグを追加してCSSで装飾を行っています。CSSの設定で悩まないようにシンプルなものしか利用していません。

取得したusersデータをテーブルで表示するまでのコードは下記の通りです。


<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>テーブル検索(フィルター処理)を行う</title>
</head>
<style>
    table{
        border-collapse: collapse;
        width:100%
    }
    td, th {
        border: 1px solid #dddddd;
        text-align: left;
        padding: 8px;
    }
    th {
        color:white;
        background-color: #1E90FF;
    }
</style>
<body>
  <div id="app">
    <h1>{{ message }}</h1>
<table>
    <thead>
        <tr>
            <th>Name</th>
            <th>Email</th>
            <th>Website</th>
        </tr>
    </thead>
    <tbody>
        <tr v-for="user in users" :key="user.id">
            <td>{{ user.name }}</td>
            <td>{{ user.email }}</td>
            <td>{{ user.website }}</td>
        </tr>
    </tbody>
</table>
  </div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
new Vue({
  el: '#app',
  data: {
    message: 'Search/Filter In Table',
    users:[],
  },
  mounted(){
    axios.get('https://jsonplaceholder.typicode.com/users')
          .then(response => this.users = response.data)
  }
})
</script> 
</body>
</html>

ブラウザで確認すると以下の画面が表示されます。

テーブルの表示
テーブルの表示

検索フォームの追加

テーブル検索ができるように入力用のinput要素の追加を検索フォームを作成します。


<h1>{{ message }}</h1>
<div>検索:<input type="text" v-model="search"></div>

v-modelで追加したsearchをvue.jsに追加し、input要素にcssを適用します。


data: {
  message: 'Search/Filter In Table',
  users:[],
  search:'',
}, 

追加した検索入力フォームは下記のように表示されます。

検索用の入力フォーム
検索用の入力フォーム

検索機能の追加

検索の入力フォームに入力した値はcomputedプロパティを利用して検索処理を行います。

computedプロパティの中で検索処理を実装するためにJavaScriptのfilterとincludes関数を利用します。

filter関数を利用することで検索の文字列が入っている行のみ取り出し、includes関数ではテーブル内の各列に入っている値に検索した文字列が入っているかをチェックを行います。2つを組み合わせることで検索フォームに入力した文字列を含む行のみを取り出します。

そのためには、filter関数とincludes関数を理解しておく必要があります。

includes関数ではuser.nameの列に文字列が入っていればtrue, 入っていなければfalseを返します。


user.name.includes('Gra') // user.nameがGrahumであればtrueが戻される

filter関数は用意した配列の要素の中からある条件を満たしたもののみ取り出すといったことができます。下記ではfilter関数を利用して配列の中で400より大きい数字のみ取り出すという処理を行っています。結果500,1000が取り出せれます。


const price = [100, 500 ,1000]

over_400 = price.filter(function(value){
	return value > 400
})

console.log(over_400)
>[500,1000]

filter関数は関数内のreturnがtrueの場合のみ要素を取り出せることが上記の例から理解することできたので入力したsearchの値がuser.nameに含まれている場合その要素のみ取り出すといった処理を行うことができます。


computed: {
    search_users(){
     return this.users.filter(user => {
        return user.name.includes(this.search)
     })
 },

テーブルのv-forではusersを利用していましたが追加したcomputedプロパティsearch_usersに変更します。


<tr v-for="user in search_users" :key="user.id">
    <td>{{ user.name }}</td>
    <td>{{ user.email }}</td>
    <td>{{ user.website }}</td>
</tr>

search_usersではuser.nameのみ検索を行っているので検索フォームに文字列を入力するとname列の情報のみチェックが行われ、一致した行が表示されます。

Howという文字列を入力するとErvin HowellがHowを含むのでその行のみ表示されます。

検索を実行
検索を実行

その他の列でも検索が行えるように下記のように更新を行います。論理演算子を利用してuser.name.includes, user.emal.inclues, user.website.includesのいづれかがtrueであればその行を残すように変更を行っています。


computed: {
    search_users(){
        return this.users.filter(user => {
            return user.name.includes(this.search) ||
            user.email.includes(this.search) ||
            user.website.includes(this.search)
        })
    },
  },

検索フォームにorgを実行するとどの列でも文字列の検索が行われ、期待した結果が表示されます。

複数の行で検索
複数の行で検索

文字列を検索フォームに入力すると入力した文字列を含む行が表示されることがわかりました。しかし、空白を入力するとすべての行が消えてしまいます。そのためtrim関数を利用して空白を削除する処理を追加します。


computed: {
    search_users(){

    let searchWord = this.search.trim()

    if (searchWord === '') return this.users;

    return this.users.filter(user => {
        return user.name.includes(searchWord) ||
        user.email.includes(searchWord) ||
        user.website.includes(searchWord)
    })
    }
},

その結果、空白が入った場合も検索を行うことができます。orgの前に空白が入っても問題なく検索が行えます。

空白行が入って検索
空白行が入った検索

ここまでの処理で検索機能は実装することができました。次は検索にひっかかった文字列をハイライトする機能を追加します。

大文字、小文字どちらを入れても検索ができるようにするためには、toLowerCase関数を利用することで実現できます。
fukidashi

検索に入力した文字列のハイライト

検索に入力した文字列をハイライトするために新たにhightLightメソッドを追加します。

hightLightメソッドは各行の値に下記のように適用します。ここではv-htmlを利用している理由は後ほど説明します。


<tbody>
    <tr v-for="user in search_users" :key="user.id">
        <td v-html="highLight(user.name)"></td>
        <td v-html="highLight(user.email)"></td>
        <td v-html="highLight(user.website)"></td>
    </tr>
</tbody>

hightLightメソッドでは各値を引数にとり、その値の中に検索に入力したテキストが含まれているかチェックを行い、含まれいる場合はその文字列にstyleを追加し、ハイライトさせます。


methods:{
    highLight(text){

      let searchWord = this.search.trim()

      if (searchWord === '') return text

      if (!text.includes(searchWord)) return text

      const re = new RegExp(searchWord, 'ig');
        
      return text.replace(re,function(search){
          return '<span style="background-color:yellow;font-weight:bold">'+ search + '</span>'
      })
    }
},

下記の3行で入力した文字からの空白の削除と入力した文字列が含まれなかった場合にはハイライトの必要がないため引数で受け取ったtextをそのまま戻しています。


      let searchWord = this.search.trim()

      if (searchWord === '') return text

      if (!text.includes(searchWord)) return text

text.replaceで入力した文字列をstyleのついた文字列に変換を行っています。

下記のようにsearchWordをそのまま入力すると入力した文字列と一致した最初の文字しかハイライトされません。


return text.replace(searchWord,function(search){
    return ''+ search + ''
})

sを入力した時に各列の値の最初の文字sしかハイライトされません。

最初の文字だけハイライト
最初の文字だけハイライト

正規表現を利用するためにRegExpを使っています。正規表現を利用することで入力した文字列が複数含まれていた場合もすべての文字がハイライトされます。


const re = new RegExp(searchWord, 'ig');//i:大文字、小文字区別なし、g:全文

return text.replace(re,function(search){
    return ''+ search + ''
})

正規表現を利用すると検索した文字列が複数含まれる場合もすべてハイライトされます。

すべての検索文字がハイライト
すべての検索文字がハイライト

最後にorgを検索フォームに入れるとorgを含む行だけではなくその文字がハイライトされることも確認できます。

orgで検索するとハイライトされる。
orgで検索するとハイライトされる。

作成したコードは以下の通りです。


<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>テーブル検索(フィルター処理)を行う</title>
</head>
<style>
    table{
        border-collapse: collapse;
        width:100%
    }
    td, th {
        border: 1px solid #dddddd;
        text-align: left;
        padding: 8px;
    }
    th {
        color:white;
        background-color: #1E90FF;
    }
    input {
        width:30%;
        padding:.5em 1em;
        border-radius: 2px;
        margin-bottom: 1em;;
    }
</style>
<body>
  <div id="app">
    <h1>{{ message }}</h1>
    <div>検索:<input type="text" v-model="search"></div>
    <table>
        <thead>
            <tr>
                <th>Name</th>
                <th>Email</th>
                <th>Website</th>
            </tr>
        </thead>
        <tbody>
            <tr v-for="user in search_users" :key="user.id">
                <td v-html="highLight(user.name)"></td>
                <td v-html="highLight(user.email)"></td>
                <td v-html="highLight(user.website)"></td>
            </tr>
        </tbody>
    </table>
  </div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
new Vue({
  el: '#app',
    data: {
        message: 'Search/Filter In Table',
        users:[],
        search:'',
    },
    methods:{
      highLight(text){

        let searchWord = this.search.trim()

        if (searchWord === '') return text

        if (!text.includes(searchWord)) return text

        const re = new RegExp(searchWord, 'ig');
          
return text.replace(re,function(search){
    return '<span style="background-color:yellow;font-weight:bold">'+ search + '</span>'
})
      }
  },
    computed: {
        search_users(){

        let searchWord = this.search.trim()

        if (searchWord === '') return this.users;

        return this.users.filter(user => {
            return user.name.includes(searchWord) ||
            user.email.includes(searchWord) ||
            user.website.includes(searchWord)
        })
        }
    },
  mounted(){
    axios.get('https://jsonplaceholder.typicode.com/users')
          .then(response => this.users = response.data)
  }
})
</script> 
</body>
</html>