WordPressのナビメニューがいまいち気に入らない


150722-0002

こいつの話です。

以後”ナビメニュー”と呼ぶことにします。

そもそもデフォの機能じゃないのね

ナビメニューはデフォの機能ではなく、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クラス付ければ再現可能。

管理画面のメニューの位置に違和感

ナビメニューのメニューが”外観”のサブメニューであるという違和感!

ナビメニューは”運用”されるのです。
そういう想定でナビメニュー化しているはずなのです。

一方、”外観”の中にあるのは、テーマ選択やテーマ編集、ウィジェットなど、運用ではなく”管理・設定”に分類される操作ばかりです。

運用者と管理者とでユーザの操作権限をわけることを考えると、ナビメニューのメニューが”外観”のサブメニューである弊害は大きい。

↓黄で色付けしたメニューが運用エリア、赤で色付けしたメニューが管理・設定エリアです。

150722-0004

このように、管理・設定の操作メニューの中に運用操作メニューであるナビメニューが紛れ込んでいるのが違和感、というか、ユーザの操作権限をわける場合に弊害になるのです。

なんでなん?

ナビメニューのメタデータは拡張できない

150722-0006

「URL」「ナビゲーションラベル」「タイトル属性」「リンクターゲット」「CSS Class」「XFN」「説明」しかない。

これ以上メタデータを拡張できない。

これだと、ナビゲーションラベルに改行を入れられない!とか、画像メニューにできない!とか、地味な悩みも出てくるものです。
ある程度CSSクラスで代用はできるものの、ナビメニューは運用されるのですッ!

いっそのことカスタム投稿にして自由を手に入れる?

gnav という名前のカスタム投稿タイプを定義して、必要なメタデータをカスタムフィールドとして追加すればいい。
既存の記事へのオートリンクもカスタムフィールドでなんとかなるし。

150722-0008

<?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 GeneratorAdvanced Custom Field を使ってます。

こうしておけば、ナビメニューの機能は網羅できるしさまざまな運用に耐えらえる。

それでもなおナビメニュー使う必要ある?

そのままでよければいいけど、ね。