9
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ROXXAdvent Calendar 2019

Day 25

フロントエンドでテーブルに検索機能を実装する

Last updated at Posted at 2019-12-25

:christmas_tree: Happy Holiday :santa:
これはROXXアドベントカレンダー25日目の投稿です。(12時過ぎましたがクリスマス気分のうちはまだクリスマス!)

業務ではサーバー (Laravel製) に対して、 フロントエンド (Nuxt.js製) からリクエストを送る構成になっています。
データ内の検索機能などは基本的にはサーバー側で処理することが多いのですが、最近フロントエンドで同機能を実装する機会がありました
フロントエンドだけで処理を行うため、処理速度は速くなり、 API へのアクセスも削減することができます🚀
この記事ではその実装方法について書きます。

実装ページ (CodePen)

実装概要

  • Vue.js
  • 使用するAPI: PokéAPI
    • ポケモン情報が取れる API
  • ページ作成時に API リクエストを行う
  • 検索をフロントエンドで処理する

検索機能詳細

  • テキストに含まれる文字列で検索
  • 検索文字列をスペースで分けることで、 AND 検索 (もしくは OR 検索) が可能

コード説明

Template

template はこんな感じで書きました。
取得したデータのうち、モンスターの名前だけをリストにします。

index.html
<div id="app">
  <input
    v-model="searchText"
    type="text"
    placeholder="Enter text"
  >
  <div
    v-for="(monster, key) in monstersList"
    :key="key"
  >
    <p>{{ monster.name }}</p>
  </div>
</div>

Script - データ取得

axios で API からデータを取得します。

data 内の monsters に取得したデータを代入します。オブジェクトで返ってくるので Object.freeze() で凍結させてもいいかもしれません。

searchText には検索キーワードが入ります。実際の処理はこのあと書きます。

main.js
new Vue({
  el: '#app',
  data:() => ({
    monsters: null,
    searchText: '',
    url: 'https://pokeapi.co/api/v2/berry?limit=200'
  }),
  async created() {
    const res = await axios.get(this.url)
    this.monsters = res.data.results
  }
});

Script - 検索キーワードの処理

このあとモンスター名と照合させるために、検索キーワード内の文字列を先に処理しましょう。

例えば leppa を検索する際、 le ppa というように文字列にスペースが入ったときに、 AND 検索か OR 検索を行えるのは自然かと思います。
そのための方法として、ここでは 1 文字ずつ配列の要素に入れる実装にします。

main.js
computed: {
  splittedSearchText() {
    return this.searchText.split(/[\s]+/)
  }
}

こうすることで先ほどの le ppa という文字列を ["le", "ppa"] という配列に置き換えます。

Script - 検索実行

検索機能とは、入力された文字列と同じ文字列を含むモンスター名を抽出することです。

まずは 入力された文字列と同じ文字列を含む という処理を書きます。

monsters の配列に対して、条件を満たすかどうかというメソッドを加えます。
AND 検索の場合は Array.every を、 OR 検索の場合は Array.some を使うことでこれを実現します。
サンプルコードでは AND 検索を実装します。

モンスター名の文字列中に、検索キーワード (splittedSearchText) が含まれるかを見ていくので、モンスターを 1 つずつ関数に渡し、照合して返すメソッドを書きます。

main.js
methods: {
  filteredMonsters(monster) {
    if (this.splittedSearchText.every(el =>
        monster.name.includes(el)
      )
    ) {
      return true
    }
  }
}

上記のメソッドを呼び出しましょう。
computed に記述し、テキストが入力されたと同時に処理が走るようにします。

main.js
// computed に追加
monstersList() {
  if (!this.monsters) { // モンスターが存在しないときは空配列を返す
    return []
  }
  if (!this.searchText.length) { // 検索文字列が存在しないときは monsters をそのまま返す
    return this.monsters
  }
  return this.monsters.filter(monster => {
    return this.filteredMonsters(monster)
  })
}

完成 + おまけ

いささか簡単なコードですが、フロントエンドで検索機能を実装することができました 🎉

pokemon.gif

喜びたいところなんですが、同じ検索機能を日本語の文字列で行う際には少し注意が必要です。アルファベットを入力中にも検索が走ってしまうので、リストのモンスターが増えたり減ったりとても見づらい挙動を起こします。
そんなときは、 compositionstartcompositionend を使って検索するタイミングを調整しましょう。

index.html
<div id="app">
  <input
    v-model="searchText"
    type="text"
    placeholder="Enter text"
    @compositionstart="isComposing = true"
    @compositionend="isComposing = false"
  >
  <div
    v-for="(monster, key) in monstersList"
    :key="key"
  >
    <p>{{ monster.name }}</p>
  </div>
</div>
main.js
data: () => ({
  isComposing: false
}),
// computed に追加
monstersList() {
  if (!this.monsters) {
    return []
  }
  if (!this.searchText.length) {
    return this.monsters
  }
  /* 追加コード */
  if (this.isComposing) { // isComposing が true (日本語入力変換中) のとき、 monsters を返す
    return this.monsters
  }
  return this.monsters.filter(monster => {
    return this.filteredMonsters(monster)
  })
}

このように記述することで、日本語を変換しているときには isComposing が true となるので、処理を走らないよう制御することができました!

フロントエンドでできることがいっぱいあって嬉しいですね。

Happy Programming! 🌟 Happy Holiday! ☃️

9
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?