絞り込み検索導入~検索フォーム作成

ここでは具体的に絞り込みに特化した以下に表示されている検索フォームの作成方法を解説していきます。

すべての山
百名山

もくじ

  1. 事前準備編 ※別ページ
    ※まずこのページを読んでおくと検索フォーム作成への理解が深まります。
  2. 検索フォーム作成
    1. 絞り込み検索の設計
    2. PHP、jQueryの全コード
      1. search-form.php
      2. selection.js
    3. セレクトボックスの表示コントロール
    4. 2つのセレクトボックスの連動
    5. チェックボックスの作成
  3. 絞り込み検索結果表示 ※別ページ

絞り込み検索の設計

今回作成した絞り込み検索機能は、大きく分けて以下のような分岐があります:

  1. すべての山の記事
    • + フリーワード
    • + 都道府県
      • + 山の名称
    • + 温泉、御朱印の条件
  2. 百名山の記事のみ
    • + フリーワード
    • + 都道府県
    • + 温泉、御朱印の条件

大きく分けるとすべての山の記事から検索するか百名山の記事のみに絞って検索するかをはじめに選び、その後フリーワードを入れたり都道府県を選択していくことになります。それぞれの項目は+と書いているとおり掛け合わさってすべての条件に当てはまる記事を抽出して表示することになります。それぞれの項目は以下の仕様でフォームに組み込むことにしました:

  1. すべての山の記事から検索するか百名山の記事に絞るか = ラジオボタン
  2. 都道府県のリスト = セレクトボックスのプルダウン
  3. 山の選択肢は都道府県を選択すると選択可能になる仕様(百名山で記事を絞った場合セレクトボックスが消える)
  4. 温泉、御朱印の条件 = チェックボックス

このフォームで特に難しいのが「都道府県を選ぶともうひとつのセレクトボックスの内容が都道府県ごとの山のリストに絞られる」という「2つのセレクトボックスの連動」で、以前会社で検索フォームを作成しているときもこの機能を実現するのにかなり多くの時間を費やしました。結果的にはPHPとjQueryの両方の連動で解決しています。個別に解説するよりもコードを見せたほうが早いと思うのでPHP、jQuery共にまずは全コードを載せます。

PHP、jQueryの全コード

search-form.php

<form method="get" id="searchform" action="<?php bloginfo('url'); ?>"><label for="s" class="assistive-text"><h5 style="margin-bottom:16px;">山の記事検索</h5><input type="text" name="s" id="s" placeholder="フリーワード" /></label>

<input class="mountain-type" type="radio" name="post_tag[]" value="登山" checked>すべての山
<input class="mountain-type" type="radio" name="post_tag[]" value="百名山">百名山

<select class="parent" style="color:#212529;">
<option value="" selected>都道府県を選択</option>
<?php
$taxonomy_name = 'area';
$args = array('parent'  => '0');
$taxonomys = get_terms($taxonomy_name, $args);
if(!is_wp_error($taxonomys) && count($taxonomys)):
    foreach($taxonomys as $taxonomy):
        $tax_posts = get_posts(array('post_type' => 'post', 'taxonomy' => $taxonomy_name, 'term' => $taxonomy->slug ) );
        if($tax_posts):
?>
<option value="<?php echo $taxonomy->slug; ?>"><?php echo $taxonomy->name; ?></option>
<?php
        endif;
    endforeach;
endif;
?>
</select>

<select class="children" style="color:#212529;" disabled>
<option value="" class="msg" selected>山を選択</option>
<?php
$taxonomy_name = 'area';
$args = array();
$taxonomys = get_terms($taxonomy_name);
if(!is_wp_error($taxonomys) && count($taxonomys)):
    foreach($taxonomys as $taxonomy):
        $parent = get_term($taxonomy->parent);
        $tax_posts = get_posts(array('post_type' => 'post', 'taxonomy' => $taxonomy_name, 'term' => $taxonomy->slug ) );
        if($tax_posts):
?>
<option data-val="<?php echo $parent->slug; ?>" value="<?php echo $taxonomy->slug; ?>"><?php echo $taxonomy->name; ?></option>
<?php
        endif;
    endforeach;
endif;
?>
</select>

<label style="margin-right:15px;"><input type="checkbox" name="post_tag[]" value="温泉"> 温泉</label><label><input type="checkbox" name="post_tag[]" value="御朱印"> 御朱印</label>

<input type="submit" value="検索" /></form>

selection.js

jQuery(function() {
  var jQuerychildren = jQuery('.children');
  var original = jQuerychildren.html();

  jQuery('.mountain-type').click(function() {
    var val3 = jQuery(this).val();

    if (val3 === '百名山') {
      jQuerychildren.attr('style', 'display:none;');
    } else {
        jQuerychildren.removeAttr('style');
        jQuerychildren.attr('style', 'color:#212529;');
    }
  });

  jQuery('.parent').change(function() {
    var val1 = jQuery(this).val();

    if (val1 != '') {
      jQuery(this).attr('name', 'area[]');
      jQuerychildren.removeAttr('disabled');
    } else {
      jQuery(this).removeAttr('name');
      jQuerychildren.attr('disabled', 'disabled');
    }

    jQuerychildren.html(original).find('option').each(function() {
      var val2 = jQuery(this).data("val");

      if (val1 != val2) {
        jQuery(this).not('.msg').remove();
      }
    });

  });

  jQuerychildren.change(function() {
    var val4 = jQuerychildren.val()

    if (val4 != '') {
      jQuerychildren.attr('name', 'area[]');
    } else {
      jQuerychildren.removeAttr('name');
    }
  });

});

セレクトボックスの表示コントロール

まずは「百名山の記事に絞ると山のセレクトボックスが消える」仕様について解説します。上記PHPのフォームの中で、ラジオボックスで設置している以下のボタンの切り替えで山のセレクトボックスが消えたり現れたりします。

<input class="mountain-type" type="radio" name="post_tag[]" value="登山" checked>すべての山
<input class="mountain-type" type="radio" name="post_tag[]" value="百名山">百名山

反応する山のセレクトボックスの中で重要なのは以下の部分の「children」というclassです。

<select class="children" name="" style="color:#212529;" disabled>

山のセレクトボックスが消えたり現れたりする動作を可能にするjQueryは以下の部分で、childrenクラスが与えられていることが前提となります:

  var jQuerychildren = jQuery('.children');

  jQuery('.mountain-type').click(function() {
    var val3 = jQuery(this).val();

    if (val3 === '百名山') {
      jQuerychildren.attr('style', 'display:none;');
    } else {
        jQuerychildren.removeAttr('style');
        jQuerychildren.attr('style', 'color:#212529;');
    }
  });

mountain-typeというクラスを持つラジオボタンがクリックされると発火し、valueが百名山の場合childrenクラスを持つ要素にstyle=”display:none;”が与えられ画面から消えるようになっています。これだけ設定すると百名山のチェックが外されたときにもとに戻らないので、百名山のチェックが外された場合はchildrenクラスを持つ要素からstyleが外され再び姿を現すようにしています。これでラジオボタンによる特定のセレクトボックスの表示コントロールを可能にしています。※色の指定もstyleで行っていたので改めてcolor:#212529のスタイルが与えられるようにもしています。

2つのセレクトボックスの連動

フォーム作成の中で最も苦労したのがこれでした。以下のサイトを大いに参考にさせていただきました:

2つのセレクトボックスを連動させるjQuery

上記サイトのコードがベースにはなっていますが、カスタムタクソノミーから情報を引っ張ってきているのでいろいろと調整が必要でした。まずぶち当たった壁が「都道府県のリストを表示する際に子カテゴリー(山の名前)も含め全て表示されてしまう」ということで、これは

$args = array('parent'  => '0');

と記述し自身が親であるカテゴリー名のみ表示することで解決。山の選択肢はデフォルトでは選択不可(disabled)としておき、都道府県のリストは都道府県のみ表示され、山のリストはデフォルトでは選択不可というのをベースとしてその後の流れを検討しました。

親カテゴリーを選択しても子カテゴリーがうまく表示されなかったりなど数え切れないくらい試しては失敗し、親と子で比較させるデータを選ぶことがミソだということに気づきさらに試行錯誤を続行。どことどこを比較させるとうまく行くのか突き詰めていった結果、「都道府県の選択肢ごとのvalueにその都道府県のslugを設定(例:静岡県ならshizuoka)」し、「山の選択肢ごとのdata-valに親カテゴリーのslugを設定(例:富士山ならshizuokaになる)」することで「親と子で同じ値」を抽出することに成功。具体的には;

親の選択肢

<option value="<?php echo $taxonomy->slug; ?>"><?php echo $taxonomy->name; ?></option>

子の選択肢

<option data-val="<?php echo $parent->slug; ?>" value="<?php echo $taxonomy->slug; ?>"><?php echo $taxonomy->name; ?></option>

としています。$parentについてはループの中で

$parent = get_term($taxonomy->parent);

と宣言することで拾ってきています。このやり方にたどり着くのにかなり苦労しており、最終的には以下のサイトを発見したことがブレイクスルーになりました。

子カテゴリー(ターム)から親カテゴリーの情報を取得する方法

比較するデータ探しにはjQueryの連動が必須で、PHPとJSを同時に動かしながら検証していたのでそれぞれ単独で完成したというよりは同時に完成に近づいたというのが正解です。JSの中ではまず

  jQuery('.parent').change(function() {
    var val1 = jQuery(this).val();

ここでparentのクラスを持つ要素の値(都道府県のセレクトボックス)が変更(いずれかの都道府県を選ぶ)されると都道府県ごとのvalue(例:shizuoka)が変数val1として格納されます。そして以下の関数によって

    if (val1 != '') {
      jQuery(this).attr('name', 'area[]');
      jQuerychildren.removeAttr('disabled');
    } else {
      jQuery(this).removeAttr('name');
      jQuerychildren.attr('disabled', 'disabled');
    }

選択肢のvalueがブランクではない(「都道府県を選択」という選択肢ではない)場合name属性とarea[]という値が付与されます。[]をつけているのは配列として認識させるためで、検索結果を表示させる際に必要になります。また同時に子カテゴリーのセレクトボックスのdisabledが外されて選択可能になります。「都道府県を選択」に戻した場合、選択肢のvalueがブランクなのでname属性が外され子カテゴリーのセレクトボックスも再びdisabledになり選択不可になります。name属性を外すかどうかは非常に重要で、name属性に値が残っている場合検索にその値も含まれてしまいます。

都道府県の選択肢が変更されると以下の関数も発火します。

    jQuerychildren.html(original).find('option').each(function() {
      var val2 = jQuery(this).data("val");

      if (val1 != val2) {
        jQuery(this).not('.msg').remove();
      }
    });

子カテゴリー(山ごと)のセレクトボックスのoption(選択肢)の中から、data-valを変数val2として格納し、val1(都道府県ごとのvalue)と比較します。val1とval2が一致しない(!=)選択肢の中で、msgクラス(「山を選択」というデフォルトの選択肢にのみ与えているクラス)を持たない項目全てが消されます。簡単に言えば、data-valでshizuokaを持つ富士山や天城山とmsgクラスを持つ「山を選択」以外の全ての選択肢が消えることによって、静岡の山のみ選択肢として残ることになります。

これでセレクトボックスごとの連動自体は完成していますが、親カテゴリー同様子カテゴリーにもname属性を与えて検索に引っかかるようにしないといけません。以下の関数で子の処理を行っています。

  jQuerychildren.change(function() {
    var val4 = jQuerychildren.val()

    if (val4 != '') {
      jQuerychildren.attr('name', 'area[]');
    } else {
      jQuerychildren.removeAttr('name');
    }
  });

子カテゴリーが選択(山の名前が選択)されたらname属性とarea[]という値が付与され、「山を選択」に戻した場合選択肢のvalueがブランクなのでname属性が外されます。

チェックボックスの作成

これは正直手を抜いて作成しましたが、すでに記事ごとに設定しているタグの中から「温泉」「御朱印」という二項目を抜き取って検索時の条件としてフォームの一部としています。以下のコードを見ればわかるとおり

<label style="margin-right:15px;"><input type="checkbox" name="post_tag[]" value="温泉"> 温泉</label><label><input type="checkbox" name="post_tag[]" value="御朱印"> 御朱印</label>

カスタムタクソノミーから情報を引っ張ってきたセレクトボックスと異なり直接値を入力しています。本来は新しくカスタムタクソノミーで「検索条件」みたいなタグを作成して温泉、御朱印、その他検索条件になりうる項目を記事ごとに登録すればこのチェックボックスも自動で更新されると思いますが、今回はそこまでする必要もないと感じたので割愛しました。ただ、ラジオボタンと同じタグをチェックボックスにも使っているので検索時にURLに飛ばされるパラメータがpost_tag[]で被っています。これにより検索結果の表示の際に少し工夫が必要になりました。

検索フォームはこれで完成です。残りはこのフォームから飛ばされた情報を基にブラウザ上に表示させる検索結果のファイルをいじれば作業は終了です。