JavaScript(jQuery)でのフィルタリング機能
検索結果の表示といえばAjax!と思っていた時期が私にもありましたが、ほぼ完全クライアントサイドでの検索機能を実装する機会があったので、ここにその内容をまとめておきたいと思います。
ざっくりと処理の流れを簡単に説明すると、
サーバー側から表示させるデータを予め全てブラウザに持っておく ⇒ テキストフォームなどの入力情報をリアルタイムに取得し、それに応じた検索をかける ⇒ 検索結果に応じてDOMの表示非表示を切り替える
という感じになっています。かなりシンプルな流れです。
環境 - Ruby on Rails 4.2.3
1.データをクライアントに送信
送り手側は 今回は取ってくるデータが多岐に(いろいろな関連先に) 渡っていたため、JSONにしやすいように、一回各要素がハッシュであるような配列を渡しています。
def get_list arr = [] Model.get_record.each do |list| hash = { id: list.id, message: list.message, schedule: list.schedule.date } arr.push(hash) end arr end
@lists = Model.get_list
受け手側はjson文字列をそのまま使うとダブルクオーテーションが文字化けしてしまうので、次のようにrawを付けてグローバル変数に代入します。下はjs.erbファイルに記入します。
g_lists_arr = <%= raw @lists.to_json %>;
2.検索機能
検索というか、全て予めブラウザで持っておいて表示非表示を切り替えるので、どちらかと言うとフィルタリングという呼び名のほうが適しているかもしれません。
例えばテキストフォームで上のmessageの検索を考えます。 viewでは次のようにします。
<input type="text" id="message_for_search" name="message_for_search" value onKeyUp = 'search()' onblur = 'search()'/>
普通のテキストフィールドです。onKeyUpにJSの関数'search()'を指定しています。
function search(){ var s_cond = get_search_cond(); var id_arr = search_hold(g_lists_arr, s_cond); filter_dom(id_arr, g_id_arr_all); } //検索条件の取得 function get_search_cond(){ var s_cond = { message: $("[id ^= 'message_for_search']").val() } return s_cond } //テキストによる絞込(マッチするレコードのID配列を返す) function search_hold(lists_arr, s_cond){ var s_result = lists_arr.filter(function(item, index){ return (item.message).indexOf(s_cond.message) >= 0 }); } //マッチしたID配列に対応したDOM操作をする function filter_dom(id_arr, id_arr_all){ $.each(id_arr_all, function(i, v){ $("#list-" + v).addClass('display-none'); }); $.each(id_arr, function(i, v){ $("#list-" + v).removeClass('display-none'); }); }
あとは、項目を表示したい部分のdivにlist-から始まるIDを指定してやって、CSSに.display-none{display: none;}を指定すれば終わりです。(実際に書いたソースから変更したので変なところがあるかもしれません。)
JSのArray.filterがかなり便利ですね。各配列に対してreturn値がtrueの要素を返します。
任意の文字列strについて、str.indexOf('')は0を返します。 したがって、何も文字列を入力していない状態だと
(item.message).indexOf(s_cond.message) >= 0 //true
が成立し、全てのDOMが表示されている状態となります。
3.プラスα
テキストフィールドが複数で、別々の検索条件をandまたはorで結ぶ場合やスペース区切りでand,orをさせる場合は、より状況は複雑となります。後者の場合は以下のようにします。
var s_result = lists_arr.filter(function(item, index){ var message = $.map(s_cond.message.split(' '), function(v, i){return (item.message).indexOf(v) >= 0}) .reduce(function(x, y){return x || y;}); if(message) return true; });
上記の例は半角スペース区切りのor検索です。mapで['peach','mango','apple']→[true,false,false]にしてreduceで[true,false,false]→trueという流れにしています。
また、ソートなどDOMにクラスを追加するだけではどうしようもない場合はDOMの書き換えが必要になります。あまり詳しくないのですが、もうriot.jsなどのJSフレームワークを使っても良いかもしれません。
クライアントサイド検索の利点
速い!項目が増えたらページネーション使うなど、ajaxと併用を考えても良いですね。