[Rails, jQ]インクリメンタルサーチ

#インクリメンタルサーチ
本記事では前回作成した[ユーザーの名前検索機能](https://qiita.com/Masanori_N/items/5065bc59d9dff88d5152)を使用して実装していきます。
以下を使用しています。

- ruby 2.5.1
- rails 5.2.4.1
- gem 'jquery-rails'
- gem 'devise'

なお、使用するビューは以下を使用します。

`````erb:index.html.erb
<%= form_with(url: users_searches_path, local: true, method: :get, class: "search_form") do |f| %>
  <%= f.text_field :keyword, placeholder: "Name", class: "search_input" %>
  <%= f.submit "Search", class: "search_btn" %>

<div class="contents">
  <% @users.each do |user| %>
    <div class="user_content">
      <p class="user_name">
        user.name
      </p>
    </div>
  <% end %>
</div>
`````

###準備

以下が記入されていなければ記入します。
``````app/assets/javascripts/application.js
//= require jquery
``````

###フォーマット毎に処理を分ける
フォーマット毎に処理を分けるためコントローラーのindexアクションを編集します。

```````controllers/users/searches_controller.rb

  def index
    @users = User.search(params[:keyword])
    respond_to do |format|
      format.html
      format.json
    end
  end
``````
####respond_to
アクションの中でHTMLとJSONなどのフォーマット毎にhtmlかjsonかを条件分岐することができます。

###jbuilderファイルの作成、編集
index.json.jbuilderを新規作成し内容を編集します。

`````app/views/tweets/searches/index.json.jbuilder
json.array! @users do |user|
  json.id user.id
  json.name name.name
end
`````
jbuilderという拡張子を持つテンプレートでは、JSONという名前のJbuilderオブジェクトが自動的に利用できるようになります。
arrayメソッドはその内の一つでJavaScript側に配列で値を送ることができます。

#search.jsの作成、編集

###検索フォームの値を取得
`````app/assets/javascripts/search.js
$(function() {
  $(".search_input").on("keyup", function() {
    var input = $(".search_input").val();
  });
});
`````
keyupイベントを使用して文字が入力される度に発火するようにします。

###JSON形式で値を返す

`````app/assets/javascripts/search.js
$(function() {
  $(".search_input").on("keyup", function() {
    var input = $(".search_input").val();
//---以下を追記---
    $.ajax({
      type: 'GET',
      url: '/users/searches',
      data: { keyword: input },
      dataType: 'json'
    })
//---以上を追記---
  });
});
`````
Ajax通信を実現するためには、上記のように$.ajaxメソッドを使用します。
また。上記のコードは
HTTPメソッドはGETで、/users/searchのURLに{ keyword: input }を送信。サーバーから値を返す際は、JSON。
という意味を持ちます。JSON形式の場合は、app/views/users/searches/index.json.jbuilderが読まれ,該当する投稿情報はjbuilderによってJSONに変換されてJavaScriptのファイルに返されます。

###レスポンス結果によって処理を分ける
`````app/assets/javascripts/search.js
$(function() {
  $(".search_input").on("keyup", function() {
    var input = $(".search_input").val();
    $.ajax({
      type: 'GET',
      url: '/users/searches',
      data: { keyword: input },
      dataType: 'json'
    })
//---以下を追記---
    .done(function(users) {
      $(".contents").empty();
      if (users.length !== 0) {
        users.forEach(function(user){
          appendUser(user);
        });
      } else {
        appendErrMsgToHTML("一致するユーザーはいません");
      }
    })
    .fail(function() {
      alert('error');
    });
//---以上を追記---
  });
});
`````
レスポンスが成功した場合は、ユーザーが表示される親要素の中身を都度空っぽにします。そしてusersが空ではない場合usersの中身の数だけappendUser関数を呼び出します。
該当ユーザーがいない場合は”一致するツイートがありません”という引数を与え、appendErrMsgToHTML関数を呼び出します。
また、レスポンスに失敗した場合はアラートを表示させます。

####empty()メソッド
指定したDOM要素の子要素のみを削除するメソッドです。
指定したDOM要素自体を削除するremoveメソッドとは異なります。

####forEachメソッド
forEachは、与えられた関数を配列に含まれる各要素に対して一度ずつ呼び出します。

###検索に該当ユーザーいた場合、いない場合の関数を定義

````app/assets/javascripts/search.js
$(function() {
//---以下を追記---
  var search_list = $(".contents");

  function appendUser(user) {
    var html = `
               <div class="user_content">
                 <p class="user_name">
                   #{user.name}
                 </p>
               </div>
               `
    search_list.append(html);
   }

  function appendErrMsgToHTML(msg) {
    var html = `
　　　　　　　　　　<div class="user_content">
                 　<p class="user_name">
                  　　${ msg }
                 　</p>
                 </div>
　　　　　　　　　`
    search_list.append(html);
  }
//---以上を追記---
  $(".search_input").on("keyup", function() {
    var input = $(".search_input").val();
    $.ajax({
      type: 'GET',
      url: '/users/search',
      data: { keyword: input },
      dataType: 'json'
    })
    .done(function(users) {
      search_list.empty();
      if (users.length !== 0) {
        users.forEach(function(user){
          appendUser(user);
        });
      } else {
        appendErrMsgToHTML("一致するユーザーはいません");
      }
    })
    .fail(function() {
      alert('error');
    });
  });
});
````
検索に該当ユーザーがいた場合
変数htmlにユーザー情報を表示する要素を代入し、appendメソッドで親要素の一番下に追加します。
検索に該当ユーザがいない場合
変数htmlに"一致するユーザーはいません"を表示する要素を代入し、appendメソッドで親要素の一番下に追加します。

#おわり
これでインクルメンタルサーチが実装できました。