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を入れます。
classProductsController<ApplicationControllerbefore_action:set_product_name,except: [:new,:edit]---省略---defset_product_name@product_for_search=Product.all@wordbox=[]@id_box=[]@product_for_search.eachdo|product|@id_box<<product.id @word_box<<product.nameendendend
b) viewにinputタグ(type:hidden)を記述。その中に1の変数を記述
jsが発火した時にここから商品名及び、商品idを取得できるようにします。
一番下の#result-wordの下にliで候補を表示します
=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ファイル完全版
$(document).on('turbolinks:load',function(){varsearchWordList=$('.search_word_list').val();functionappendList(word,number){letitem=$(`
<li class="list result-list">
<a href = "/products/${number}" class="search-word-list">
<p>${word}</p>
`);$("#result-word").append(item);}functioneditElement(element){if(element!=""){letresult="^"+element;returnresult;}else{letresult="$^";returnresult;}}$("#_products_searches_keyword").on("keyup",function(){letinput=$("#_products_searches_keyword").val();if(input==""){$("#result-word").empty();}else{letinputs=input.split("");letnewInputs=inputs.map(editElement);letreg=RegExp(newInputs.join("|"));$.each(JSON.parse(searchWordList),function(i,word){varsearchIdList=$('.search_id_list').val();searchIdList=JSON.parse(searchIdList)if(word.match(reg)){appendList(word,searchIdList[i]);}});};});});
search.jsの詳細コード
varsearchWordList=$('.search_word_list').val();⬅️①$("#_products_searches_keyword").on("keyup",function(){ ⬅️②letinput=$("#_products_searches_keyword").val();if(input==""){$("#result-word").empty();}else{letinputs=input.split("");letnewInputs=inputs.map(editElement);letreg=RegExp(newInputs.join("|"));---省略---});};});
① ページが読み込まれた時に商品の名前、idを取得する
② ↓入力されるたびに候補が出て欲しいので、keyupを使って発火させる。
↓発火すると検索boxに入れられた値を取得する
↓何も入力されていなければ候補用の要素を削除する
↓入力された検索ワードが複数ある場合はスペースで区切り、配列(inputs)にする
↓配列(inputs)をmapで新しい配列にする d)に飛ぶ
↓正規表現用の変数を用意し、先ほど ^ マークを付けた配列を"|"(「または」を意味する)を間に入れて合体させる。
d) 検索boxに入力された値をもとに正規表現を作成
functioneditElement(element){if(element!=""){letresult="^"+element;returnresult;}else{letresult="$^";returnresult;}}
inputで分けられた配列の中身があるときだけ検索ワードの先頭に ^ マークをつける。「test ␣」のようにスペースが空いている場合は「"^test", "^"」というふうに配列が作成されるので、すべての商品名がヒットしてしまう。「""」の時は ^ マークは付けないようにする。 b)に戻る。
e) インスタンス変数の値をeachで取り出して正規表現とマッチするか調べる
$.each(JSON.parse(searchWordList),function(i,word){varsearchIdList=$('.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する
functionappendList(word,number){letitem=$(`
<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に変換する
・検索ワードを正規表現にして該当するものだけヒットするようにする
以上です。
最後までご覧いただきありがとうございました。