はしばみあきら blog

プログラミングアウトプットするブログ。202010スタート

【RailsでLINEを作る】インクリメンタルサーチ実装

前回のトークを非同期で書き換えする機能と同じタイミングでインクリメンタルサーチも実装したのでまた書いていきます

インクリメンタルサーチは、検索フォームに入力されたワードで瞬時に検索し表示させる...であってるのかな?そういう機能です

どんな感じになるかの動画

内容は

  • 一文字入力する毎に、検索結果を表示
  • 検索フォームが空なら友だち一覧を、文字があれば検索された友達を表示
  • 検索結果がなければ、その旨を表示
  • 検索された友だちをクリックでトーク画面を表示

これを作っていきます

流れとしては

検索フォームを実装 ↓ フォームにワードを入力でjsが動く ↓ 入力されたワードをajax通信でコントローラへ ↓ 受け取ったデータを元に友だちを検索 ↓ データをjsに戻し、そのデータを元にviewを書き換える

それでは作っていきます

参考にした記事

Rails + jQueryでインクリメンタルサーチ(基礎)

インクリメンタルサーチ

まずは検索フォーム。部分テンプレートになっています

talk_rooms/index.html.slim

      .talk-rooms.tab-content.is-show
        .talk-rooms__search
          = render partial: "layouts/search"
layouts/_search.heml.slim

i(name="search-icon" id="search-icon" class="fas fa-search")
=form_tag('/talk_rooms/search', method: :get) do
  =text_field_tag :name, :'', id: 'searching-form', placeholder: "名前で検索", autocomplate: 'off'
br

iの部分はフォントオーサムです

このフォームをターゲットにjsを動かしていきます

javascripts/talk_rooms.js

    // 名前で検索機能を作る
    $(document).ready(function(){
      const inputForm = $('#searching-form');
      const url = location.href;
      const searchResult = $('.result');

      function builtHTML(data){
        if(data.profile_image === null){
          let html = `<a class="user-talk-show" data-remote="true" href="/talk_rooms?follower=${data.id}">
                      <img src="${data.profile_image}">
                      <p>${data.name}</p>
                      `
          searchResult.append(html);
        }else{
          let html = `<a class="user-talk-show" data-remote="true" href="/talk_rooms?follower=${data.id}">
                      <img src="/assets/usagi.jpg">
                      <p>${data.name}</p>
                      `
          searchResult.append(html);
        }
      }
      function NoResult(message){
        let html = `<li>${message}</li>`
        searchResult.append(html);
      }
      // フォームに入力でイベント発火
      inputForm.on('keyup', function(e){
        e.preventDefault();
        let target = $(this).val();
        if(target !== ''){
          search(target);  // ajax通信はsearch()という関数に
          $('.talk-rooms-content').addClass("talk-rooms-content-close");
        }else{
          searchResult.empty();
          $('.talk-rooms-content').removeClass("talk-rooms-content-close");
        }
      });
      // ajax処理
      function search(target){
        $.ajax({
          type: 'GET',
          url: 'talkrooms/search',
          data: {keyword: target},
          dataType: 'json'
        })
        .done(function(data){
          searchResult.empty(); // 再度検索した際に前のデータを消す処理
          if (data.length !== 0){
            data.forEach(function(data){ // dataは配列型に格納されているのでeachぶんで回す
              builtHTML(data)
            });
          }else{
            NoResult('検索結果がありません')
          }
        })
        .fail(function(data){
          alert('通信に失敗しました')
        })
      }
    });

結構長くなってしまいました

検索フォームはこの部分

      // フォームに入力でイベント発火
      inputForm.on('keyup', function(e){
        e.preventDefault();
        let target = $(this).val();
        if(target !== ''){
          search(target);  // ajax通信はsearch()という関数に
          $('.talk-rooms-content').addClass("talk-rooms-content-close");
        }else{
          searchResult.empty();
          $('.talk-rooms-content').removeClass("talk-rooms-content-close");
        }
      });

キーが入力される度に発火させています

targetにはフォームの文字を代入

targetが空でなければ、後のsearch()を起動

かつ、友だち一覧の部分にクラスを付けて、cssで非表示にするようにしてあります

起動するのはこの部分

      // ajax処理
      function search(target){
        $.ajax({
          type: 'GET',
          url: 'talkrooms/search',
          data: {keyword: target},
          dataType: 'json'
        })

トークルームコントローラのサーチアクションにデータが渡ります

ルーティングを書いておきましょう

routes.rb

  #検索機能
  get 'talkrooms/search', to: 'talk_rooms#search'

ajaxの部分で、'GET'と 'talkrooms/search' があることでコントローラが動きます

次にサーチアクションを記述

talk_rooms_controller.rb

  def search
    @users = current_user.followers.where('name LIKE(?)', "%#{params[:keyword]}%")
    respond_to do |format|
      format.html #htmlがないとエラーが出る
      format.json
    end
  end

ajax部分のdata: {keyword: target}, がパラメータとして使えるようになっています

次に、views/talk_roomsにsearch.json.jbuilderというファイルを作ります

まだ理解が浅いですが、コントローラから渡された値をjson形式で出力するものらしいです

@usersに代入された友だちをjson形式に変えていきます

@usersには複数データが入るのでeachで回します

talk_rooms/search.json.jbuilder

json.array! @users do |user|
  json.user user
  json.name user.name
  json.id user.id
  json.profile_image user.profile_image
end

この左側がkey、右側がvalueになります

通信が行われた場合のajaxの処理がこの部分です

      function search(target){
        $.ajax({
          type: 'GET',
          url: 'talkrooms/search',
          data: {keyword: target},
          dataType: 'json'
        })
        .done(function(data){
          searchResult.empty(); // 再度検索した際に前のデータを消す処理
          if (data.length !== 0){
            data.forEach(function(data){ // dataは配列型に格納されているのでeachぶんで回す
              builtHTML(data)
            });
          }else{
            NoResult('検索結果がありません')
          }
        })
        .fail(function(data){
          alert('通信に失敗しました')
        })
      }

.doneは通信が成功した時、.failが失敗した時です

json形式にしたデータは(data)に入ります

そのdataを元にhtmlを作成し検索結果をjsで書き換えていきます

      function builtHTML(data){
        if(data.profile_image === null){
          let html = `<a class="user-talk-show" data-remote="true" href="/talk_rooms?follower=${data.id}">
                      <img src="${data.profile_image}">
                      <p>${data.name}</p>
                      `
          searchResult.append(html);
        }else{
          let html = `<a class="user-talk-show" data-remote="true" href="/talk_rooms?follower=${data.id}">
                      <img src="/assets/usagi.jpg">
                      <p>${data.name}</p>
                      `
          searchResult.append(html);
        }
      }
      function NoResult(message){
        let html = `<li>${message}</li>`
        searchResult.append(html);
      }

あんまり冗長なのでもっといい方法がないか模索中ですがとりあえず上記の形に

ユーザーのプロフィールイメージがある時とないときで表示の方法を変えています

書き換わるindexのviewはこんな感じで

talk_rooms/index.html.slim

    .talk-room-index__talk-rooms
      .talk-rooms.tab-content.is-show
        .talk-rooms__search
          = render partial: "layouts/search"
        .result
        .talk-rooms-content
          .talk-rooms__my-information
            .talk-rooms__my-information--image
              = render partial: "users/attachment", locals: { user: current_user}
            .talk-rooms__my-information--name
              p = current_user.name
          .talk-rooms__list
            .talk-rooms__list--groups
              span.select-group-list グループ
              ul
            .talk-rooms__list--friends
              span.select-friends-list 友だち
              ul
                - current_user.followings.each do |follower|
                  li class="follower"
                    = link_to talk_rooms_path(follower: follower.id), class: "user-talk-show", remote: true do
                      = render partial: 'users/attachment', locals: { user: follower}
                      p = follower.name

フォームに文字が入力された時点で .result にその内容を付け加え、.talk-rooms-content はjsとcssで非表示にします

stylesheets/talk_rooms.scss

.talk-rooms-content-close{
  display: none;
}

こんな感じで実装できました。

あとはCSSを触ったり、データの受け渡しをしてなんとかLINE風に左右でトークを表示したり、トークをしたから上に表示させたりとやっていこうと思います。

またボチボチ更新していきます。