1
1

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.

検索機能で検索boxの下に候補を出す方法

Posted at

#0 はじめに
 今回は以下の写真のように、ユーザーが検索する時の助けになるように、boxの下に候補を出す方法を記述します。
 もっといい方法があるとは思いますが、今の私の理解力と技術ではこれがベストです。

バージョンです

 version ruby 2.5.1p57
          Rails 5.2.4.1

1 実装の流れ

a) controllerで候補に出すデータをインスタンス変数に入れる

b) viewにinputタグ(type:hidden)を記述。その中に1の変数を記述

c) 検索boxに入力されるたびに発火させ処理させる

d) 検索boxに入力された値をもとに正規表現を作成

e) インスタンス変数の値をeachで取り出して正規表現とマッチするか調べる

f) マッチしたものだけ関数を使って要素を作り、検索boxにappendする

2 コード・解説

a) controllerで候補に出すデータをインスタンス変数に入れる

jsサイドにデータを渡すためのコードです。
今回は「new.html.haml」 「edit.html.haml」以外に検索機能を実装します。productsコントローラーにbeforeアクションを設定しました。@wordに商品名を入れ、@id_boxにproduct.idを入れます。

products_controller.rb
class ProductsController < ApplicationController
 before_action :set_product_name, except: [:new, :edit]
 ---省略---
 def set_product_name
   @product_for_search = Product.all
   @wordbox = []
   @id_box = []
   @product_for_search.each do |product|
     @id_box << product.id
   @word_box << product.name
   end
  end
end

b) viewにinputタグ(type:hidden)を記述。その中に1の変数を記述

 jsが発火した時にここから商品名及び、商品idを取得できるようにします。
 一番下の#result-wordの下にliで候補を表示します

index.html.haml
= form_with(url: products_searches_path, local: true, method: :get, class: "search-form") do |form|
 .search-group
  = form.text_field :keyword, placeholder: "商品を検索する", class: "main-header__search-box", id: "_products_searches_keyword"
  = form.label :keyword, for: "search-btn", class: "search-label" do
   = form.submit "検索", class: "search-btn", id: "search-btn", style: "display: none"
   = image_tag 'icon-search 1.png',size: "30x25",class: "main-header__search-img"
  %input{name: "search-word-list", type: "hidden", value: @wordbox, class: 'search_word_list' }  ←この行を入れる
  %input{name: "search-id-list", type: "hidden", value: @id_box, class: 'search_id_list' }     ←この行を入れる
 %ul#result-word ←この下に候補用の要素を追加する

c) 検索boxに入力されるたびに発火させ処理させる

jsファイル完全版

search.js
$(document).on('turbolinks:load', function(){
  var searchWordList = $('.search_word_list').val();


  function appendList(word, number) {    
    let item = $(`
    <li class="list result-list">
      <a href = "/products/${number}" class="search-word-list">
        <p>${word}</p>
    `);
    $("#result-word").append(item);
  }

  function editElement(element) {
    if (element != ""){
      let result = "^" + element;
      return result;
    }else{
      let result = "$^";
      return result;
    }
  }
  
  $("#_products_searches_keyword").on("keyup", function() {
    let input = $("#_products_searches_keyword").val();
    if (input==""){
      $("#result-word").empty();
    }else {
      let inputs = input.split(" ");
      let newInputs = inputs.map(editElement);
      let reg = RegExp(newInputs.join("|"));


      $.each( JSON.parse(searchWordList), function(i, word) {
        var searchIdList = $('.search_id_list').val();
        searchIdList =JSON.parse(searchIdList)
        if (word.match(reg)) {
          appendList(word,searchIdList[i]);
        }
      });
    };
  });
});

search.jsの詳細コード

search.js
 var searchWordList = $('.search_word_list').val(); ⬅️①

 $("#_products_searches_keyword").on("keyup", function() { ⬅️②
    let input = $("#_products_searches_keyword").val();
    if (input==""){
      $("#result-word").empty();
    }else {
      let inputs = input.split(" ");
      let newInputs = inputs.map(editElement);
      let reg = RegExp(newInputs.join("|"));


      ---省略---
      });
    };
  });

① ページが読み込まれた時に商品の名前、idを取得する
② ↓入力されるたびに候補が出て欲しいので、keyupを使って発火させる。
  ↓発火すると検索boxに入れられた値を取得する
  ↓何も入力されていなければ候補用の要素を削除する
  ↓入力された検索ワードが複数ある場合はスペースで区切り、配列(inputs)にする
  ↓配列(inputs)をmapで新しい配列にする d)に飛ぶ
  ↓正規表現用の変数を用意し、先ほど ^ マークを付けた配列を"|"(「または」を意味する)を間に入れて合体させる。

d) 検索boxに入力された値をもとに正規表現を作成

search.js
 function editElement(element) {
    if (element != ""){
      let result = "^" + element;
      return result;
    }else{
      let result = "$^";
      return result;
    }
  }

inputで分けられた配列の中身があるときだけ検索ワードの先頭に ^ マークをつける。「test ␣」のようにスペースが空いている場合は「"^test", "^"」というふうに配列が作成されるので、すべての商品名がヒットしてしまう。「""」の時は ^ マークは付けないようにする。 b)に戻る。

e) インスタンス変数の値をeachで取り出して正規表現とマッチするか調べる

search.js
 $.each( JSON.parse(searchWordList), function(i, word) {
   var searchIdList = $('.search_id_list').val();
   searchIdList =JSON.parse(searchIdList)
   if (word.match(reg)) {
   appendList(word,searchIdList[i]);
   }
 });

 一行目のJSON.parseはsearchWordListをobjectに変換するためのメソッド
 jsのeachはobjectに対して処理を行うようでこの記述がないとエラーが表示される。(理解仕切れていません)

Uncaught TypeError: Cannot use 'in' operator to search for 'length' in ["aa", "test", "選択してくださいテスト", "test-sample", "服"]

商品のidにも同じように記述する
はじめは商品のidを渡さずに、eachのindexを使ってリンクのURLを作成していたが、本番環境でエラーが出たので、書き換えました。

f) e)のeachでマッチしたものだけ、関数を使って要素を作り、検索boxにappendする

search.js
 function appendList(word, number) {    
   let item = $(`
   <li class="list result-list">
    <a href = "/products/${number}" class="search-word-list">
     <p>${word}</p>
  `);
  $("#result-word").append(item);
  }

候補をクリックするとその商品のページに移動するためリンクを作成しています。その時にproduct.idが必要なめviewから取得しました。

 

3 最後に

今回のポイントは
・商品のデータをビューからjsに渡す
・検索ワードを入力している途中でスペースが入った時にすべての商品名がヒットしてしまうので、スペースには ^ マークを付けない
・JSON.parseでobjectに変換する
・検索ワードを正規表現にして該当するものだけヒットするようにする

以上です。
最後までご覧いただきありがとうございました。

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?