最近アウトプット出来ていなく久々にjQueryに触れたので議事録がてら書いていきたいと思います。
以前もAjaxに関しての記事を書いたので少し内容が被ってしまいますが今回はインクリメンタルサーチについてです。
##インクリメンタルサーチとは
インクリメンタルサーチ(英語: incremental search)は、アプリケーションにおける検索方法のひとつ。 検索したい単語をすべて入力した上で検索するのではなく、入力のたびごとに即座に候補を表示させる。 逐語検索、逐次検索とも。
↓のような感じで検索ボタンを押さなくてもページ遷移なしで結果を表示してくれます。
##この記事の完成イメージ
見た目は質素なので機能だけを紹介していきます。
##コード
今回実装したコード
.search-container
= form_tag('/products/search', method: :get, placeholder:'') do
= text_field_tag :name, :'', id:'searching-form', placeholder:'検索', autocomplete:'off'
.result
%ul
$(document).on('turbolinks:load', function(){
const inputForm = $('#searching-form');
const url = location.href;
const searchResult = $('.result ul');
function builtHTML(data){
let html = `
<li>${data.name}</li>
`
searchResult.append(html);
}
function NoResult(message){
let html = `
<li>${message}</li>
`
searchResult.append(html);
}
// フォームに入力すると発火する
inputForm.on('keyup', function(){
const target = $(this).val();
search(target);
});
// ajax処理
function search(target){
$.ajax({
type: 'GET',
url: url,
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('非同期通信に失敗しました');
})
}
});
json.array! @products do |product|
json.name product.name
end
def search
@products = Product.where('name LIKE(?)', "%#{params[:keyword]}%")
respond_to do |format|
format.html #htmlを読み込んであげないとエラーが出るのでしっかりと記述
format.json
end
end
##処理の流れ
###1.(search.js)フォームに文字が入力されるとイベントが発生するようにkeyupメソッドを仕掛ける。
// const inputForm = $('#searching-form'); input要素
inputForm.on('keyup', function(e){
e.preventDefault();
let target = $(this).val();
search(target); //ajax通信はsearch()という関数にしています。
});
val()はjQueryメソッドなのでjQuryオブジェクトに対して使用するように注意が必要です。
この時点で変数targetに値が入っているか確認するためにmacの方なら「F11 + fn」でコンソールが開けるのでconsole.log(target)
として確認しておくと確実です。
↓
###2.(search.js)Ajaxでサーバーにリクエストを送信
まずはじめにAjaxリクエストを送信するオプションをキーと値のペアで指定します。
$.ajax({//サーバーに送信するリクエストの設定})
今回必要になる設定のオプションは↓
キー | 値 |
---|---|
type | リクエストのタイプ("GET"または"POST")を指定します。 |
url | Ajaxリクエストを送信するURLを指定します。(rake routesコマンドでリクエストを送信したいURIを確認しましょう。今回であれば'products/search') |
data | サーバへ送信するデータです。キー/値のペアで設定します。 |
dataType | サーバから返ってくるデータの型を指定します(json, htmlなど)。 |
以上を踏まえた上で今回のコード
$.ajax({
type: 'GET',
url: 'products/search',
data: {keyword: target},
dataType: 'json'
})
今回送信したいリクエストはユーザーやクレジットカードなどの情報をサーバーにPOSTするのではなくサーバーにあるproductに関する情報をGETしたいのでtype: 'GET'
となります。
urlに関してはrake routesでリクエストを送りたいURIを確認出来ます。
data: {keyword: target}
に関して
keywordはキーになるのでリクエストを送信した時にController側でparams[:keyword]として受け取れます。
値であるtargetは先ほどイベントが発生した時に定義したtargetです。
Railsのjbuilderという機能を使用するのでdataTypeはjsonです!
データの流れが分かりづらいですが、これでサーバー側にリクエストが送られました。次はRailsのController側での処理になります
※Ajaxでリクエスト送信→Controllerでjson、もしくはhtml形式での処理を場合分け→jsonであればjbuilderへ処理を流す→jbuilderで処理されたデータを再びJavascriptで受け取りHTMLの追加や削除を行う。
###3.(products_controller.rb)コントローラーで値を受け取る
Ajaxで送信された値はコントローラーで受け取る事が出来ます。
$.ajaxではtype, url, data, dataType
を指定しています。
type: 'GET', url: 'products/search'はでした!
これはproducts/searchに対してGETリクエストを送るという事です。
なのでRailsアプリケーションがGET products/search
というHTTPリクエストを受け取った時にprodductsコントローラーのsearchアクションへ飛ぶようにroutes.rb
で設定してあげましょう。
resources :products do
collection do
get 'search'
end
end
# 下記のようにしても出来ますが自分は上のようにしています。
get 'products/search' => 'products#search'
これでコントローラーで受け取る準備が出来ました。(確認されたい方はrake routesコマンドでルーティングを確認出来ます!)
次にコントローラーでの処理を見ます。
def search
# @productsは次に紹介するjbuilderで必要になるインスタンス変数です
@products = Product.where('name LIKE(?)', "%#{params[:keyword]}%")
respond_to do |format|
format.html
format.json
end
end
$.ajaxで設定したdata
がパラメーターとして送られてきます。
画像の3行目にあるのがパラメーターで記述した通りkeyword: value
の形になっています。
(今回は分かりやすくkeywordにしているだけなのでkeyの名前は任意です。)
Railsではparamsメソッドを使えばパラメーターの値が取得できるので、params[:keyword]
としています。
インスタンス変数@productsにはwhereとLIKE句を使いDBから検索した該当するデータを配列として持っています。
フォーマット毎に処理を分けるには、respond_toを使用します。
今回は@productsに入っているデータが配列なのでjbuilderで配列を処理してJavascriptに返します。
※respond_to というメソッドを使うと、リクエストがHTMLを求めているのか、もしくはJSONを求めているのかを拡張子やヘッダー情報を利用して、自動的に条件分岐してくれます。
###4.(search.json.jbuilder)json形式で出力する
jbuilderはrails newコマンドでアプリケーションを作成した際にgemfileにデフォルトで記述されているgemで、入力データをJSON形式で出力するテンプレートエンジンです。
json.array! @products do |product|
json.name product.name
end
@productsは配列でなので.array!を使用します。
jbuilderファイルでは基本的にjson.KEY VALUEという形で書くことができます。
こうすることによってJavaScriptファイル(search.js)に返ってきたデータをjbuilderで定義したキーとバリューの形で呼び出して使うことができます。
今回はjson.nameがkey、product.nameがvalueになります。
###5.(search.js)jbuilderからの出力を受け取り表示する
// データがあった場合に呼ばれる関数
function buildHTML(dataFromSearchFunction){
let html = `
<li>${dataFromSearchFunction.name}</li>
`
searchResult.append(html);
}
// 該当するデータがなかった時に呼ばれる関数
function NoResult(message){
let html = `
<li>${message}</li>
`
searchResult.append(html);
}
function search(target){
$.ajax({
type: 'GET',
url: url,
data: {key: target},
dataType: 'json'
})
.done(function(data){
// 通信が成功した時の処理
searchResult.empty(); //再度検索した際に前のデータを消す処理
if (data.length !== 0) {
data.forEach(function(data) { //dataは配列型に格納されているのでEach文で処理
buildHTML(data)
});
} else {
NoResult('該当する商品はありません')
}
})
.fail(function(data){
// 通信が失敗した時の処理
alert('非同期通信に失敗しました');
})
}
.done(function(data){//処理})
で受け取っているdataはjbuilderから送られてくるデータなのでeach文で一つ一つの値を定義している関数buildに渡している。
関数buildに入っている値はjbuilderで定義したキーとバリューの形で呼び出して使うことができるので${data.name}
といった記述が可能になります!
以上になります。
jQueyでのajax処理は直感的ですごく扱いやすいと改めて感じました。
インフラに関するアウトプットが出来ていないので何か記事をあげたいなと思います。
ご覧いただきありがとうございました。
誰かの参考になれば幸いです。