WebTecNote

[WordPress] カテゴリーリストのマークアップを変更する

WordPressの自作テーマではテーマ関数が生成するマークアップを変更するのは基本中の基本である。

今回は「カテゴリーリストをアコーディオン風に開閉できるようにしたい」というオーダーを受けてやっつけたものの没になったのでここで供養する。

2014年に書いたナビゲーションメニューの改造話と一部かぶるところがあるのだが、カスタムウォーカーによるマークアップ変更についてより具体的に書いてみた。

アコーディオンというUIについて

アコーディオンというのはボタンによってターゲット要素を開閉するUIの名称である。

こういうのはCSSだけでも作れるが、CSSだけの場合はinput要素を使わざるを得なかったり、WAI-ARIAの変更ができなかったりするので私は使ったことがない。

JavaScriptを利用すればその辺の問題は解決できるが、フレームワークを利用したUIだとマークアップそのものを利用するライブラリに合わせて変更しなければならないことが多いため、Wordpressが出力するHTMLを変更できないと対応できなかったりする。慣れてないと挫折すると思う。

今回はカテゴリーリストのHTMLをBootstrapのアコーディオンのマークアップに変更している。

wp_list_categories デフォルトの動作

WordPressのカテゴリーリストを表示する時に使うテンプレートタグといえば wp_list_categories である。

デフォルトのオプションはこのようになっていて:

    $args = array(
	'show_option_all'    => '',
	'orderby'            => 'name',
	'order'              => 'ASC',
	'style'              => 'list',
	'show_count'         => 0,
	'hide_empty'         => 1,
	'use_desc_for_title' => 1,
	'child_of'           => 0,
	'feed'               => '',
	'feed_type'          => '',
	'feed_image'         => '',
	'exclude'            => '',
	'exclude_tree'       => '',
	'include'            => '',
	'hierarchical'       => 1,
	'title_li'           => __( 'Categories' ),
	'show_option_none'   => __( '' ),
	'number'             => null,
	'echo'               => 1,
	'depth'              => 0,
	'current_category'   => 0,
	'pad_counts'         => 0,
	'taxonomy'           => 'category',
	'walker'             => null
    );
    wp_list_categories( $args ); 

デフォルト設定での表示動作はこう:

マークアップはリスト要素の入れ子になる。

<li class="categories">カテゴリー
  <ul>
	<li class="cat-item cat-item-1"><a href="//localhost:3000/category/category1/">カテゴリー1</a></li>
	<li class="cat-item cat-item-2"><a href="//localhost:3000/category/category2/">カテゴリー2</a></li>
  </ul>
</li>

オプションでタイトルなしと子カテゴリ表示階層を指定した場合:

wp_list_categories( array('title_li' => null, 'depth'=> 2) ); 

タイトルが消えたぶんだけ入れ子は減るが、リスト要素での出力は変わらない。

<li class="cat-item cat-item-1"><a href="//localhost:3000/category/category-1/">カテゴリー1</a>
    <ul class=“children”>
       <li class=“cat-item cat-item-6"><a href=“//localhost:3000/category/category-1/category-7/“>カテゴリー7</a></li>
       <li class=“cat-item cat-item-7”><a href=“//localhost:3000/category/category-1/category-8/“>カテゴリー8</a></li>
       <li class=“cat-item cat-item-11”><a href=“//localhost:3000/category/category-1/category-9/“>カテゴリー9</a></li>
     </ul>
 </li>
<li class="cat-item cat-item-2"><a href="//localhost:3000/category/category-2/">カテゴリー2</a></li>
<li class="cat-item cat-item-3"><a href="//localhost:3000/category/category-3/">カテゴリー3</a>
  <ul class="children">
	<li class="cat-item cat-item-4"><a href="//localhost:3000/category/category-3/category-4/">カテゴリー4</a>
</li>
	<li class="cat-item cat-item-5"><a href="//localhost:3000/category/category-3/category-4/">カテゴリー4</a>
</li>
	<li class="cat-item cat-item-15"><a href="//localhost:3000/category/category-3/category-5/">カテゴリー5</a>
</li>
</ul>
</li>

WalkerオプションとWalkerクラス

前にもwp_nav_menuをWalkerクラスでカスタムする話を書いているのだが、wp_list_categoriesも同じことができる。
デフォルト動作の説明に、Walker_Category クラスでリストをレンダリング とあるように、オプションで walker が設定できる関数にはレンダリング専用のクラスが利用されているので、マークアップをカスタマイズしたい時はこのレンダリング用のWalkerクラスを変更すればいいのだ。

Worlerクラス説明
Walker全てのWalkerクラスの親
Walker_CategoryDropdownカテゴリーリストをドロップダウンで生成する
wp_dropdown_categories(変更不可)
Walker_Categoryカテゴリーリストを生成する
wp_list_categories(変更可)
Walker_Commentコメントリストを生成する
wp_list_comments(変更可)
get_comment_pages_count(変更不可)
Walker_Nav_Menuナビゲーションメニューを生成する
wp_nav_menu(変更可)
walk_nav_menu_tree(変更可)
_wp_ajax_menu_quick_search(変更可)
Walker_PageDropdownページのドロップダウンリストを生成する
walk_page_dropdown_tree(変更可)
Walker_Pageページのリストを生成する
wp_list_pages(変更可)
wp_page_menu(変更可)
walk_page_tree(変更可)
walk_page_dropdown_tree(変更可)

Walkerクラスのファイルは全てwp-includes内にある。

カスタムウォーカーの作成

/walker/accordion-category.php (ディレクトリと名前はなんでもいい)を作成して、
extendsWalker_Category を継承したクラスを作る。

<?php
/**
 * Bootstrapのアコーディオン用マークアップをする
 * カテゴリー用カスタムウォーカー
 * wp_list_categories の walkerオプションでセットするとマークアップが切り替わる
 * 'walker' => new \sample\walker\AccordionCategory
 */

namespace sample\walker;

class AccordionCategory extends \Walker_Category {
// ここに処理を書く
}

Walkerクラスはリストのレベル変更時の前後とリスト要素の前後でそれぞれ関数を呼び出すので、その関数をカスタムクラスでオーバーライドすることでマークアップが変更できる。

オーバーライドするメソッド

関数名説明
start_elリストアイテム要素の開始タグの位置で呼び出される
end_elリストアイテム要素の終了位置で呼び出される
start_lvlリストの入れ子開始時に呼び出される
end_lvlリストの入れ子終了時に呼び出される

パラメーター

上記4つの関数に渡されるパラメーター。

パラメーター(変数)説明
$outputこの変数に代入した文字列がHTMLとして出力される。
$category
$object
カテゴリーのデータオブジェクト。
$depth0 であればルートの階層で、0以上であれば子階層という判別ができる。
$args関数実行時に渡されたオプション。

以下のコードは$argsによる分岐処理は入れてないので、必要なものがあれば追加するといい。

start_el

リストアイテム要素の開始タグの位置で呼び出される関数。

親カテゴリーは子カテゴリーがある場合とない場合でマークアップを変更。
子カテゴリーがある場合はアコーディオン化、ない場合はリンクボタンとする。
end_el関数の処理を簡略化するためにマークアップを合わせている。

子カテゴリーはUL要素の子になるのでリスト要素を使う。

  /**
   * Starts the element output.
   *
   * @since 2.1.0
   * @access public
   *
   * @see Walker::start_el()
   *
   * @param string $output   Passed by reference. Used to append additional content.
   * @param object $category Category data object.
   * @param int    $depth    Optional. Depth of category in reference to parents. Default 0.
   * @param array  $args     Optional. An array of arguments. See wp_list_categories(). Default empty array.
   * @param int    $id       Optional. ID of the current category. Default 0.
   */
  public function start_el( &$output, $category, $depth = 0, $args = array(), $id = 0 ) {

    $header = '';
    $body = '';
    $link = '';
    $container_id = 'site-category';
    $item_id = 'cat-' . $category->slug;
    $collapse_id = 'collapse-' . $category->slug;
    
    /** This filter is documented in wp-includes/category-template.php */
    $cat_name = apply_filters(
      'list_cats',
      esc_attr( $category->name ),
      $category
    );

    //子カテゴリ
    $child_categories = get_term_children($category->term_id, 'category');

    $child_categories_count = count($child_categories);

    // 親カテゴリー
    if($depth === 0) {
      // 子カテゴリーがある
      if ($child_categories_count > 0) {
        $header .= '<div class="accordion-item">';
        $header .= '<h3 class="accordion-header" id="' . $item_id . '">';
        $header .= '<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#' . $collapse_id . '" aria-expanded="false" aria-controls="' . $collapse_id . '">';
        $header .= $cat_name . '</button></h3>';
        
        $body .= '<div id="' . $collapse_id . '" class="accordion-collapse collapse" aria-labelledby="' . $item_id . '" data-bs-parent="#' . $container_id . '">';
        $body .= '<div class="accordion-body">';
      } else {
        // 子カテゴリーがない
        $header .= '<div>';
        $header .= '<div class="px-1">';
        $header .= '<a class="btn btn-light p-3 d-block text-start bg-white" ';
        $header .= 'href="' . esc_url( get_term_link( $category ) ) . '">';
        $header .= $cat_name;
        $header .= '</a>';
      }

    } else {
      // 子カテゴリー
      $link .= '<a class="d-inline-flex align-items-center rounded py-3" ';
      $link .= 'href="' . esc_url( get_term_link( $category ) ) . '">';
      $link .= $cat_name;
      $link .= '</a>';
      $body .= '<li>' . $link . '</li>';
    }

    $output .= $header;
    $output .= $body;
  }

end_el

リストアイテム要素の終了位置で呼び出される関数。

親カテゴリーの場合にDIV要素を閉じるだけ。

  /**
	 * Ends the element output, if needed.
	 *
	 * The $args parameter holds additional values that may be used with the child class methods.
	 *
	 * @since 2.1.0
	 * @abstract
	 *
	 * @param string $output Used to append additional content (passed by reference).
	 * @param object $object The data object.
	 * @param int    $depth  Depth of the item.
	 * @param array  $args   An array of additional arguments.
	 */
	public function end_el( &$output, $object, $depth = 0, $args = array() ) {
    // カテゴリールート
    if($depth === 0) {
      $output .= '</div></div>';
    }
  }

start_lvl

リストの入れ子開始時に呼び出される関数。

カテゴリーリストだと必ず子カテゴリーの開始時になるので、親要素になるULを追加する。

  /**
	 * Starts the list before the elements are added.
	 *
	 * @since 2.1.0
	 *
	 * @see Walker::start_lvl()
	 *
	 * @param string $output Used to append additional content. Passed by reference.
	 * @param int    $depth  Optional. Depth of category. Used for tab indentation. Default 0.
	 * @param array  $args   Optional. An array of arguments. Will only append content if style argument
	 *                       value is 'list'. See wp_list_categories(). Default empty array.
	 */
	public function start_lvl( &$output, $depth = 0, $args = array() ) {
    $indent = str_repeat("\t", $depth);
		$output .= "$indent<ul class='list-unstyled fw-normal m-0 p-0 small'>\n";
  }

end_lvl

リストの入れ子終了時に呼び出される関数。

カテゴリーリストだと必ず子カテゴリーの終了時になるので、start_lvl関数で開始したタグの終了タグを追加する。

  /**
	 * Ends the list of after the elements are added.
	 *
	 * The $args parameter holds additional values that may be used with the child
	 * class methods. This method finishes the list at the end of output of the elements.
	 *
	 * @since 2.1.0
	 * @abstract
	 *
	 * @param string $output Used to append additional content (passed by reference).
	 * @param int    $depth  Depth of the item.
	 * @param array  $args   An array of additional arguments.
	 */
	public function end_lvl( &$output, $depth = 0, $args = array() ) {
    $output .= '</ul>';
  }

カスタムウォーカーの利用

functions.phpでrequireしたら、

require( get_template_directory() . '/walker/accordion-category.php' );

wp_list_categoriesのオプションで作ったクラスを指定する。
上記コードはアコーディオンのネストには対応していないマークアップなのでdepthは2にする。

wp_list_categories( array( 'depth'  => 2, 'walker' => new \sample\walker\AccordionCategory ) ); 

BootstrapのCSSとJSが読みこまれていればスタイリングもついてこうなる。

モバイルバージョンを終了