javascriptのみで複数カテゴリの絞り込み検索を実現するコード
javascript, jQuery 2018年7月28日
はじめに
こんにちは、北陸のwebコーダーdaimaです。
本日はjavascript(jQuery)のみで 項目の絞り込み検索を実現する サンプルコードをご紹介します。
今回ご紹介するコード(スクリプト)には 主に次のようなメリット、デメリットがあります。
このコードのメリット
- コピペで簡単&すぐに利用可能
- 検索件数表示&検索条件リセット機能付き
- PHPやajax、CMSなどが不要
- HTMLを触るだけで検索条件の変更が可能(jsに触れなくてもOK!)
- polyfill無しでChrome、Firefox、safari、IE、スマホ全ての環境で動作可能(記事投稿時点最新版)
このコードのデメリット
- 全ての検索項目を予め HTML内に書かれておく必要があるため 件数が膨大になると保守が煩雑になる可能性がある。
- 検索結果表示時のアニメーションなどはなし
デモページとコード
<div class="bl_3daysSearchBlock">
<div class="bl_3daysSearchBlock_inner">
<div class="bl_3daysSearchBlock_ttl">
<div class="bl_3daysSearchBlock_ttl_main">LUNCH INFO</div>
<div class="bl_3daysSearchBlock_ttl_sub">県内のランチスポット情報</div>
</div>
<div id="select" class="bl_selectBlock">
<div class="el_searchResult">
<span class="el_searchResult_nume js_numerator"></span>件/全<span class="el_searchResult_deno js_denominator"></span>件
</div>
<div class="bl_selectBlock_wrapper">
<div class="bl_selectBlock_wrapper_wrapper">
<div class="bl_selectBlock_ttl">ジャンル</div>
<div class="bl_selectBlock_content js_conditions" data-type="type">
<span class="bl_selectBlock_check"><input id="type-japan" type="checkbox" name="type" value="japan">
<label for="type-japan">
<span class="el_checkbox"></span>
和食
</label>
</span>
<span class="bl_selectBlock_check"><input id="type-china" type="checkbox" name="type" value="china">
<label for="type-china">
<span class="el_checkbox"></span>
中華
</label>
</span>
<span class="bl_selectBlock_check"><input id="type-italia" type="checkbox" name="type" value="italia">
<label for="type-italia">
<span class="el_checkbox"></span>
イタリアン
</label>
</span>
</div>
</div>
<div class="bl_selectBlock_wrapper_wrapper">
<div class="bl_selectBlock_ttl">価格帯</div>
<div class="bl_selectBlock_content js_conditions" data-type="price">
<span class="bl_selectBlock_check"><input id="price-u500" type="checkbox" name="price" value="u500">
<label for="price-u500">
<span class="el_checkbox"></span>
〜500円
</label>
</span>
<span class="bl_selectBlock_check"><input id="price-o500u1000" type="checkbox" name="price" value="o500u1000">
<label for="price-o500u1000">
<span class="el_checkbox"></span>
501円〜1000円
</label>
</span>
<span class="bl_selectBlock_check"><input id="price-o1000" type="checkbox" name="price" value="o1000">
<label for="price-o1000">
<span class="el_checkbox"></span>
1000円〜
</label>
</span>
</div>
</div>
<div class="bl_selectBlock_wrapper_wrapper">
<div class="bl_selectBlock_ttl">場所</div>
<div class="bl_selectBlock_content js_conditions" data-type="location">
<span class="bl_selectBlock_check"><input id="location-kanazawa" type="checkbox" name="location" value="kanazawa">
<label for="location-kanazawa">
<span class="el_checkbox"></span>
金沢
</label>
</span>
<span class="bl_selectBlock_check"><input id="location-kaga" type="checkbox" name="location" value="kaga">
<label for="location-kaga">
<span class="el_checkbox"></span>
加賀
</label>
</span>
<span class="bl_selectBlock_check"><input id="location-komatsu" type="checkbox" name="location" value="komatsu">
<label for="location-komatsu">
<span class="el_checkbox"></span>
小松
</label>
</span>
</div>
</div>
</div>
<div class="bl_selectBlock_release js_release">すべての選択を解除</div>
</div>
<div class="bl_searchResultBlock">
<div class="bl_searchResultBlock_item js_target" data-type="japan" data-price="u500" data-location="kanazawa">和食/〜500円/金沢</div>
<div class="bl_searchResultBlock_item js_target" data-type="china" data-price="u500" data-location="kanazawa">中華/〜500円/金沢</div>
<div class="bl_searchResultBlock_item js_target" data-type="italia" data-price="u500" data-location="kanazawa">イタリアン/〜500円/金沢</div>
<div class="bl_searchResultBlock_item js_target" data-type="japan" data-price="o500u1000" data-location="kanazawa">和食/501円〜1000円/金沢</div>
<div class="bl_searchResultBlock_item js_target" data-type="china" data-price="o500u1000" data-location="kanazawa">中華/501円〜1000円/金沢</div>
<div class="bl_searchResultBlock_item js_target" data-type="italia" data-price="o500u1000" data-location="kanazawa">イタリアン/501円〜1000円/金沢</div>
<div class="bl_searchResultBlock_item js_target" data-type="japan" data-price="u500" data-location="kaga" >和食/〜500円/加賀</div>
<div class="bl_searchResultBlock_item js_target" data-type="china" data-price="u500" data-location="komatsu">中華/〜500円/小松</div>
<div class="bl_searchResultBlock_item js_target" data-type="italia" data-price="u500" data-location="komatsu">イタリアン/〜500円/小松</div>
<div class="bl_searchResultBlock_item js_target" data-type="japan" data-price="o1000" data-location="kanazawa">和食/1000円〜/加賀</div>
</div>
</div>
</div>
input{
display: none;
}
.bl_3daysSearchBlock{
margin-bottom: 60px;
}
.bl_3daysSearchBlock_inner{
width: 82.9%;
max-width: 1160px;
margin-left: auto;
margin-right: auto;
}
.bl_3daysSearchBlock_ttl{
text-align: center;
margin: 55px 0;
}
.bl_3daysSearchBlock_ttl_main{
font-size: 27px;
color: #0073aa;
margin-bottom: 15px;
font-weight: bold;
letter-spacing: 0.15em;
font-family: "Josefin Sans" , "NotoSansCJKjp-Jxck", "Noto Sans CJK JP" , "Noto Sans" , "Noto Sans Japanese" ,"Helvetica Neue", "Meiryo" , "メイリオ", "YuGothic", "游ゴシック",sans-serif;
}
.bl_3daysSearchBlock_ttl_sub{
font-size: 16px;
color: #0073aa;
letter-spacing: 0.2em;
}
.el_searchResult{
text-align: center;
color: #0073aa;
}
.js_target{
display: none;
}
.js_target.js_selected{
display: block;
}
.bl_selectBlock label{
display: inline;
}
.bl_selectBlock input{
}
.bl_selectBlock_check label .el_checkbox{
position: relative;
top: 3px;
width: 14px;
height: 14px;
display: inline-block;
margin-right: 5px;
border: 1px solid #0073aa;
}
.bl_selectBlock_check label .el_checkbox:after{
content: "";
position: absolute;
top: 4px;
left: 4px;
bottom: 4px;
right: 4px;
}
.bl_selectBlock_check input[type=checkbox]:checked + label .el_checkbox:after{
background-color: #0073aa;
}
.el_searchResult{
margin-bottom: 20px;
}
.bl_selectBlock{
border: 1px solid #0073aa;
padding: 30px;
box-sizing: border-box;
margin-bottom: 30px;
font-size: 14px;
letter-spacing: 0.1em;
}
.bl_selectBlock_wrapper_wrapper{
display: -webkit-flex;
display: -moz-flex;
display: -ms-flex;
display: -o-flex;
display: flex;
flex-wrap: wrap;
align-items: center;
margin-bottom: 30px;
}
.bl_selectBlock_check label{
cursor: pointer;
}
.bl_selectBlock_ttl{
width: 185px;
color: #0073aa;
letter-spacing: 0.1em;
margin-bottom: 20px;
}
@media screen and (max-width: 765px){
.bl_selectBlock_ttl{
width: 100%;
margin-bottom: 20px;
}
}
.bl_selectBlock_content{
width: calc(100% - 185px);
}
@media screen and (max-width: 765px){
.bl_selectBlock_content{
width: 100%;
}
}
.bl_searchResultBlock{
font-size: 14px;
border-top: 1px solid #bdbdbd;
}
.bl_searchResultBlock_item{
padding: 20px 0;
box-sizing: border-box;
border-bottom: 1px solid #bdbdbd;
}
.bl_selectBlock_check{
display: inline-block;
margin-right: 25px;
display: inline-block;
margin-bottom: 20px;
}
.bl_selectBlock_check:last-child{
margin-right: 0;
}
.bl_selectBlock_release{
text-align: center;
color: #fff;
background-color: #bdbdbd;
padding: 15px 0;
}
.js_release{
cursor: pointer;
}
$(function(){
var box = $('.js_target');//検索対象のDOMを格納する
var conditions = $('.js_conditions');//現在の条件の選択状況を保持するオブジェクト
var findConditions;//各data-typeの子要素(input)を格納する
var currentType;//現在のdata-typeを示す
var count = 0;//検索ヒット数
var checkcount = 0;//各data-typeのチェックボックス選択数
var data_check = 0;//対象項目のデータがどれだけチェック状態と一致しているか
var condition ={};//チェックボックスの入力状態を保持するオブジェクト
$('.js_denominator').text(box.length);//件数表示の分母をセット
for(var i = 0; i < conditions.length; i++){//ターゲットのdata-typeを参照し、メソッドとしてconditionに個別に代入する
currentType = conditions[i].getAttribute('data-type');
condition[currentType] = [];
}
function setConditions(){//条件設定
count = 0;
box.removeClass('js_selected');
for(var i = 0; i < conditions.length; i++){//data-typeごとの処理
currentType = conditions[i].getAttribute('data-type');
findConditions = conditions[i].querySelectorAll('input');
for(var n = 0; n< findConditions.length; n++){//inputごとの処理
if(findConditions[n].checked){//現在選択中のインプットが選択されている場合
condition[currentType][findConditions[n].value] = true;
checkcount++
} else {
condition[currentType][findConditions[n].value] = false;
}
if(findConditions.length === n+1){//ループが最後の場合
if(checkcount === 0){
for(var t = 0; t < findConditions.length; t++){
condition[currentType][findConditions[t].value] = true;
}
}
checkcount = 0;
}
}
}
for(var m = 0, len = box.length; m< len; ++m){//最初に取得したターゲットの情報と、現在のinputの選択状態を比較して処理を行う
for(var i = 0; i < conditions.length; i++){//ターゲットのdata-typeを参照し、メソッドとしてconditionに個別に代入する
currentType = conditions[i].getAttribute('data-type');
if(condition[currentType][$(box[m]).data(currentType)]){
data_check++;
} else {
}
}
if(data_check === conditions.length){
count++;
$(box[m]).addClass('js_selected');
}else{
}
data_check = 0;
}
$('.js_numerator').text(count);//件数表示の分子をセット
}
setConditions();
$(document).on('click','input',function(){
setConditions();
});
$(document).on('click','.js_release',function(){
$('.bl_selectBlock_check input').attr("checked", false);
setConditions();
});
});
コードの解説
HTMLは「セレクトボックスエリア」と 「検索結果の表示エリア」の二箇所に分かれています。
セレクトボックスエリア
絞り込みの条件を指定する箇所です。
今回は検索条件が複数必要になる状況を想定して、 検索条件のチェックボックスを囲む 親カテゴリも複数設定できるようにしました。
具体的には、js_conditionsクラスを持つ要素が カテゴリの親となり、その親に指定した data-type属性の値が選択条件のカテゴリ名になります。 (例:職務内容 = job,勤務地 = location)
そして、子要素のinputは 全て親のカテゴリ名がnameになり、 valueがそれぞれの選択肢になります。
ここで設定したカテゴリ名や inputのvalue値は後の 検索結果エリアのHTML内でも使用します。
検索結果エリア
検索結果を表示するエリアです。
この箇所のHTMLを見てみると、 絞り込みを行いたい項目が、 一個ずつjs_targetクラスを指定した タグで囲まれていることが分かります。
そして、そのタグに data-[選択条件のカテゴリ名]の命名規則で 検索ステータスを持たせて 一種の目印としているわけですね。
今回のスクリプトは、 inputボタン(とリセットボタン)のクリック時に 現在のinputの選択状況と 検索対象それぞれの検索ステータスの指定を比較し、 条件に当てはまるものだけに専用のクラスを付与して それ以外のものはdisplay:none;で 表示を隠すという処理をしています。
応用編:チェックボックスがひとつも選択されていない場合は検索結果をすべて非表示にするバージョン
$(function(){
var box = $('.js_target');//検索対象のDOMを格納する
var conditions = $('.js_conditions');//現在の条件の選択状況を保持するオブジェクト
var findConditions;//各data-typeの子要素(input)を格納する
var findConditionsAll;//全てのinputを格納する
var currentType;//現在のdata-typeを示す
var count = 0;//検索ヒット数
var checkcount = 0;//各data-typeのチェックボックス選択数
var checktotal = 0;//全体のチェックボックス選択合計数
var data_check = 0;//対象項目のデータがどれだけチェック状態と一致しているか
var condition ={};//チェックボックスの入力状態を保持するオブジェクト
$('.js_denominator').text(box.length);//件数表示の分母をセット
for(var i = 0; i < conditions.length; i++){//ターゲットのdata-typeを参照し、メソッドとしてconditionに個別に代入する
currentType = conditions[i].getAttribute('data-type');
condition[currentType] = [];
}
function setConditions(){//条件設定
count = 0;
box.removeClass('js_selected');
//チェックボックス全体の選択合計数を記憶
findConditionsAll = document.querySelectorAll('#select input');
for(var i = 0; i < findConditionsAll.length; i++){
if(findConditionsAll[i].checked){
checktotal++;
}
}
for(var i = 0; i < conditions.length; i++){//data-typeごとの処理
currentType = conditions[i].getAttribute('data-type');
findConditions = conditions[i].querySelectorAll('input');
for(var n = 0; n< findConditions.length; n++){//inputごとの処理
//inputの選択合計数が0でなかったら処理を続行
if(checktotal !== 0){
if(findConditions[n].checked){//現在選択中のインプットが選択されている場合
condition[currentType][findConditions[n].value] = true;
checkcount++
} else {
condition[currentType][findConditions[n].value] = false;
}
if(findConditions.length === n+1){//ループが最後の場合
if(checkcount === 0){
for(var t = 0; t < findConditions.length; t++){
condition[currentType][findConditions[t].value] = true;
}
}
checkcount = 0;
}
} else {
condition[currentType][findConditions[n].value] = false;
}
}
}
checktotal = 0;
for(var m = 0, len = box.length; m< len; ++m){//最初に取得したターゲットの情報と、現在のinputの選択状態を比較して処理を行う
for(var i = 0; i < conditions.length; i++){//ターゲットのdata-typeを参照し、メソッドとしてconditionに個別に代入する
currentType = conditions[i].getAttribute('data-type');
if(condition[currentType][$(box[m]).data(currentType)]){
data_check++;
} else {
}
}
if(data_check === conditions.length){
count++;
$(box[m]).addClass('js_selected');
}else{
}
data_check = 0;
}
$('.js_numerator').text(count);//件数表示の分子をセット
}
setConditions();
$(document).on('click','input',function(){
setConditions();
});
$(document).on('click','.js_release',function(){
$('.bl_selectBlock_check input').attr("checked", false);
setConditions();
});
});
チェックボックスがひとつも選択されていない場合は 検索結果を全て非表示にするバージョンのコードです。 通常版のjsコードと差し替えてご使用ください。
※HTML、CSSは同一です。
ご利用の際には…
実際にご自身のサイト上で 当コードをご利用される場合は 上記の解説を参考にして 選択項目の差し替えや増減を行ってください。
基本的にはスクリプト部分を弄らなくても 検索項目及び検索条件の変更が可能な設計になっています。
その他
https://isotope.metafizzy.co/より本格的な 絞り込み検索をお求めであれば、 商用利用に条件はありますが、 上記のisotope.jsがおすすめです。
コメント
初めまして、こんばんは
絞り込み検索を探してほしい機能が揃ったJSでしたので、参考にさせていただいております。
一点お伺いしたいのですが、セレクトボックスエリアで未選択時に検索結果エリアでは全て表示されておりますが、セレクトボックスエリアで未選択時には検索結果エリア非表示にすることは可能でしょうか。
お忙しいところ恐れ入りますが、可能でしたらご教示頂けますでしょうか。
宜しくお願い致します。
はまさんコメントありがとうございます。
記事内に、チェックボックスが一つも選択されていない時に
検索結果を全て非表示にするバージョンのコードを追記しました。
ぜひ一度お試しください。
検索結果が無かった場合、「条件に当てはまるお店が見つかりませんでした。」と表示させるにはどうしたら良いのでしょうか?
お返事遅くなってすみません。
本スクリプトでは
変数countが検索結果総数を保持しているので、
setConditions関数の最終行あたりに
if文でcountが0の時に上記メッセージを
表示するような処理を追加すればよいかと思います。