【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風に左右でトークを表示したり、トークをしたから上に表示させたりとやっていこうと思います。
またボチボチ更新していきます。