#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を入れます。
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で候補を表示します
= 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(){
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の詳細コード
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に入力された値をもとに正規表現を作成
function editElement(element) {
if (element != ""){
let result = "^" + element;
return result;
}else{
let result = "$^";
return result;
}
}
inputで分けられた配列の中身があるときだけ検索ワードの先頭に ^ マークをつける。「test ␣」のようにスペースが空いている場合は「"^test", "^"」というふうに配列が作成されるので、すべての商品名がヒットしてしまう。「""」の時は ^ マークは付けないようにする。 b)に戻る。
e) インスタンス変数の値をeachで取り出して正規表現とマッチするか調べる
$.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する
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に変換する
・検索ワードを正規表現にして該当するものだけヒットするようにする
以上です。
最後までご覧いただきありがとうございました。