こいつの話です。
以後”ナビメニュー”と呼ぶことにします。
そもそもデフォの機能じゃないのね
ナビメニューはデフォの機能ではなく、functions.php に有効化のコードを記述しないと使えません。
add_theme_support( 'menus' );
参考: 関数リファレンス/add theme support – WordPress Codex 日本語版
テンプレートタグ wp_nav_menu の挙動がなんか変
ナビメニューを出力する時は、テンプレートタグ wp_nav_menu を使え、とあります。
引数と初期値は以下の通り。
<?php $defaults = array( 'theme_location' => '', 'menu' => '', 'container' => 'div', 'container_class' => '', 'container_id' => '', 'menu_class' => 'menu', 'menu_id' => '', 'echo' => true, 'fallback_cb' => 'wp_page_menu', 'before' => '', 'after' => '', 'link_before' => '', 'link_after' => '', 'items_wrap' => '<ul id="%1$s" class="%2$s">%3$s</ul>', 'depth' => 0, 'walker' => '' ); wp_nav_menu( $defaults ); ?>
出力したいマークアップ構造に従って $container や $menu_class を調整したりします。
で、
問題は $fallback_cb という引数。
$fallback_cb
(文字列) (オプション) メニューが存在しない場合にコールバック関数を呼び出す
初期値: wp_page_menu
“メニューが存在しない場合”という言葉と、初期値が wp_page_menu であるというところにトラップがある。
“メニューが存在しない場合”とは?
指定したメニューが存在しない場合ではなく、ナビメニューがひとつも存在しない場合、ですッ
以下のように gnav という名前のナビメニューを出力するとします。
<?php $defaults = array( 'menu' => 'gnav', ); wp_nav_menu( $defaults );
gnav という名前のナビメニューが存在しない場合は、初期値 wp_page_menu が呼ばれるので、固定ページのリンクリストが生成されます。
この時点でうざい。
この想定外の挙動を回避するために $fallback_cb を指定すればオケか?
<?php $defaults = array( 'menu' => 'gnav', 'fallback_cb' => 'no_menu', ); wp_nav_menu( $defaults ); function no_menu() { return false; } ?>
こうすると、gnav という名前のメニューが存在しない場合は false が返されて、なにも出力されません。
これで一見解決したかのように思えますが、違います。
snav というナビメニューが存在していた場合、snav が呼ばれるのです。
snav という指定などまったくしていなくてもねッ
おどろきー
つまり、
なので、どっちにしても想定外のナビメニューが出力されてしまうことがあるのです。
この仕様、実にうざい。
存在確認をするために wp_get_nav_menu_items を使う
テンプレートタグ wp_nav_menu が変な挙動をしてウザいから、wp_get_nav_menu_items 関数を使ってナビメニューのオブジェクトを取得してループ表示すればいい。
<?php $gnav = wp_get_nav_menu_items( 'gnav', array() ); ?> <?php if( $gnav ) : ?> <?php $current_url = ( empty( $_SERVER[ "HTTPS" ] ) ? "http://" : "https://" ) . $_SERVER[ "HTTP_HOST" ] . $_SERVER[ "REQUEST_URI" ]; ?> <ul> <?php foreach( $gnav as $menu ) : ?> <li class="<?php if( $menu->url == $current_url ) echo 'current' ?>"> <a href="<?php echo $menu->url; ?>" target="<?php echo $menu->target; ?>"><?php echo $menu->title; ?></a> </li> <?php endforeach; ?> </ul> <?php endif; ?>
wp_nav_menu だとカレントのメニューに current-menu-item クラスが付くのが便利〜、とか言うけど、wp_get_nav_menu_items でループ表示しても、上記のコードのようにカレントのURLを取得して判定してCSSクラス付ければ再現可能。
管理画面のメニューの位置に違和感
ナビメニューのメニューが”外観”のサブメニューであるという違和感!
ナビメニューは”運用”されるのです。
そういう想定でナビメニュー化しているはずなのです。
一方、”外観”の中にあるのは、テーマ選択やテーマ編集、ウィジェットなど、運用ではなく”管理・設定”に分類される操作ばかりです。
運用者と管理者とでユーザの操作権限をわけることを考えると、ナビメニューのメニューが”外観”のサブメニューである弊害は大きい。
↓黄で色付けしたメニューが運用エリア、赤で色付けしたメニューが管理・設定エリアです。
このように、管理・設定の操作メニューの中に運用操作メニューであるナビメニューが紛れ込んでいるのが違和感、というか、ユーザの操作権限をわける場合に弊害になるのです。
なんでなん?
ナビメニューのメタデータは拡張できない
「URL」「ナビゲーションラベル」「タイトル属性」「リンクターゲット」「CSS Class」「XFN」「説明」しかない。
これ以上メタデータを拡張できない。
これだと、ナビゲーションラベルに改行を入れられない!とか、画像メニューにできない!とか、地味な悩みも出てくるものです。
ある程度CSSクラスで代用はできるものの、ナビメニューは運用されるのですッ!
いっそのことカスタム投稿にして自由を手に入れる?
gnav という名前のカスタム投稿タイプを定義して、必要なメタデータをカスタムフィールドとして追加すればいい。
既存の記事へのオートリンクもカスタムフィールドでなんとかなるし。
<?php if( $gnav->have_posts() ) : ?> <?php $current_url = ( empty( $_SERVER[ "HTTPS" ] ) ? "http://" : "https://" ) . $_SERVER[ "HTTP_HOST" ] . $_SERVER[ "REQUEST_URI" ]; ?> <ul> <?php while ( $gnav->have_posts() ) : $gnav->the_post(); ?> <li class="<?php if( get_field( 'url' ) == $current_url ) echo 'current' ?>"> <a class="<?php the_field( 'class' ) ?>" href="<?php the_field( 'url' ); ?>" target="<?php the_field( 'target' ); ?>"><?php the_field( 'title' ); ?></a> </li> <?php endwhile; wp_reset_postdata(); ?> </ul> <?php endif; ?>
Custom Post Type Generator と Advanced Custom Field を使ってます。
こうしておけば、ナビメニューの機能は網羅できるしさまざまな運用に耐えらえる。
それでもなおナビメニュー使う必要ある?
そのままでよければいいけど、ね。