javascriptのみで複数カテゴリの絞り込み検索を実現するコード

はじめに

こんにちは、北陸のwebコーダーdaimaです。

本日はjavascript(jQuery)のみで
項目の絞り込み検索を実現する
サンプルコード
をご紹介します。

今回ご紹介するコード(スクリプト)には
主に次のようなメリット、デメリットがあります。

このコードのメリット

  • コピペで簡単&すぐに利用可能
  • 検索件数表示&検索条件リセット機能付き
  • PHPやajax、CMSなどが不要
  • HTMLを触るだけで検索条件の変更が可能(jsに触れなくてもOK!)
  • polyfill無しでChrome、Firefox、safari、IE、スマホ全ての環境で動作可能(記事投稿時点最新版)

このコードのデメリット

  • 絞り込みを行う情報が全て
    HTML内に書かれている必要があるため
    件数が膨大になると
    読み込みや処理が遅くなる可能性がある。
  • 検索結果表示時のアニメーションは無し(動作がほしい方は後述のisotope.jsがおすすめです)

デモページとコード

  <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;で
表示を隠すという処理をしているのです。

自分用にHTMLをカスタマイズする際は…

実際に当コードを改変する際は
上記の解説に従って
必要な情報の差し替えや
増減を行ってください。

正しい形式で改変を行えば、
スクリプトファイルに触れなくても
基本的な改変は行えるように作ってあります。

その他

https://isotope.metafizzy.co/

より本格的な
絞り込み検索をお求めであれば、
商用利用に条件はありますが、
上記のisotope.jsがおすすめです。