Underscores で魔改造したメモその2。
ナビゲーションメニューを改造しようそうしよう。
- Compass導入編
- ナビゲーションメニュー改造編 ←今ここ
マークアップがてら何も考えず昔の手法でメニュー作ってたんだけど、
<ul class="menu">
<li><a class="btn" href="<?php echo get_category_link( 1 ); ?>">News</a></li>
<li><a class="btn" href="<?php echo get_permalink(8) ?>">About</a></li>
<li><a class="btn" href="<?php echo get_permalink(12) ?>">Gallary</a></li>
<li><a class="btn" href="<?php echo get_category_link( 2 ); ?>">Blog</a></li>
</ul>
今のWordpressにはメニューを管理ページで操作出来る機能があるじゃないですか。
ということを思い出したので、対応しておくことにした。
ナビゲーションメニューの登録
テーマがカスタムメニュー機能に対応していれば、管理画面の「外観」内にある『メニュー』という項目であれこれ操作ができる。
Underscoresはデフォルトで対応状態になってます。
その設定は functions.php の78行目付近にある register_nav_menusです。
/** * This theme uses wp_nav_menu() in one location. */ register_nav_menus( array( 'main_navigation' => __( 'Main Navigation', 'my' ), ) );
この関数に渡す配列は '位置'=>'名前' の形式で、設定した数だけ「位置の管理」に表示される。
「メニューを編集」で表示したいものをメニュー構造に設定してメニューを作成する。
あとはテンプレートの表示したい所で wp_nav_menu タグを使うだけ。
<?php wp_nav_menu(array('theme_location' => 'main_navigation')) ?>
くっそ便利…
カスタムウォーカーの使用
wp_nav_menuにパラメータを渡せばそれなりにカスタムできる。
これについては公式のWikiにあらかた書いてあるのでそっちを見てください。
で、このパラメータだけだとリストタグとアンカータグには殆ど何も出来ないのだが、最後らへんに書いてある walker というパラメータに Walker_Nav_Menu クラスのインスタンスを渡すと魔改造ができるのでした。
それについてもWikiの終盤に「カスタムウォーカー機能を使用する」というタイトルで書いてあります。
試しにWikiにあるサンプルソースをfunctions.phpにコピペして
class themeslug_walker_nav_menu extends Walker_Nav_Menu {
// add classes to ul sub-menus
function start_lvl( &$output, $depth ) {
// depth dependent classes
$indent = ( $depth > 0 ? str_repeat( "\t", $depth ) : '' ); // code indent
$display_depth = ( $depth + 1); // because it counts the first submenu as 0
$classes = array(
'sub-menu',
( $display_depth % 2 ? 'menu-odd' : 'menu-even' ),
( $display_depth >=2 ? 'sub-sub-menu' : '' ),
'menu-depth-' . $display_depth
);
$class_names = implode( ' ', $classes );
// build html
$output .= "\n" . $indent . '<ul class="' . $class_names . '">' . "\n";
}
// add main/sub classes to li's and links
function start_el( &$output, $item, $depth, $args ) {
global $wp_query;
$indent = ( $depth > 0 ? str_repeat( "\t", $depth ) : '' ); // code indent
// depth dependent classes
$depth_classes = array(
( $depth == 0 ? 'main-menu-item' : 'sub-menu-item' ),
( $depth >=2 ? 'sub-sub-menu-item' : '' ),
( $depth % 2 ? 'menu-item-odd' : 'menu-item-even' ),
'menu-item-depth-' . $depth
);
$depth_class_names = esc_attr( implode( ' ', $depth_classes ) );
// passed classes
$classes = empty( $item->classes ) ? array() : (array) $item->classes;
$class_names = esc_attr( implode( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item ) ) );
// build html
$output .= $indent . '<li id="nav-menu-item-'. $item->ID . '" class="' . $depth_class_names . ' ' . $class_names . '">';
// link attributes
$attributes = ! empty( $item->attr_title ) ? ' title="' . esc_attr( $item->attr_title ) .'"' : '';
$attributes .= ! empty( $item->target ) ? ' target="' . esc_attr( $item->target ) .'"' : '';
$attributes .= ! empty( $item->xfn ) ? ' rel="' . esc_attr( $item->xfn ) .'"' : '';
$attributes .= ! empty( $item->url ) ? ' href="' . esc_attr( $item->url ) .'"' : '';
$attributes .= ' class="menu-link ' . ( $depth > 0 ? 'sub-menu-link' : 'main-menu-link' ) . '"';
$item_output = sprintf( '%1$s<a%2$s>%3$s%4$s%5$s</a>%6$s',
$args->before,
$attributes,
$args->link_before,
apply_filters( 'the_title', $item->title, $item->ID ),
$args->link_after,
$args->after
);
// build html
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
}
}
wp_nav_menuのパラメータで渡してみると…
<?php wp_nav_menu(array('theme_location' => 'main_navigation', 'walker' => new themeslug_walker_nav_menu)) ?>
排出されるソースが書き変わる。
これだとBEM化もできるわけで…
<?php
wp_nav_menu( array(
'theme_location' => 'main_navigation',
'container' => 'nav',
'container_class' => 'nav',
'container_id' => 'page-nav',
'menu_id' => '',
'items_wrap' => '<ul class="%2$s">%3$s</ul>',
'walker' => new bem_walker_nav_menu
) );
?>
<?php
class bem_walker_nav_menu extends Walker_Nav_Menu {
// add main/sub classes to li's and links
function start_el( &$output, $item, $depth, $args ) {
global $wp_query;
$indent = ( $depth > 0 ? str_repeat( "\t", $depth ) : '' ); // code indent
// build html
$output .= $indent . '<li class="menu__item menu__item--' . $item->ID . '">';
// link attributes
$attributes = ! empty( $item->attr_title ) ? ' title="' . esc_attr( $item->attr_title ) .'"' : '';
$attributes .= ! empty( $item->target ) ? ' target="' . esc_attr( $item->target ) .'"' : '';
$attributes .= ! empty( $item->xfn ) ? ' rel="' . esc_attr( $item->xfn ) .'"' : '';
$attributes .= ! empty( $item->url ) ? ' href="' . esc_attr( $item->url ) .'"' : '';
$attributes .= ' class="menu__link ' . ( $depth > 0 ? 'menu__link--sub' : 'menu__link--main' ) . '"';
$item_output = sprintf( '%1$s<a%2$s>%3$s%4$s%5$s</a>%6$s',
$args->before,
$attributes,
$args->link_before,
apply_filters( 'the_title', $item->title, $item->ID ),
$args->link_after,
$args->after
);
// build html
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
}
}
?>

カスタムが捗りますなあ。
新着があったらNEW付ける
ミーティングでこんな会話をした。
「たとえばNewsに新しい投稿があったら、メニューにNew!アイコンを付けられる?」
「Wordpressならできますよ」
やっべ、安請け合いだったかな…って思ってたけど、カスタムウォーカーで一気に解決だ!!!
<? php
wp_nav_menu(array('walker' => new addnew_walker_nav_menu) );
?>
<? php
class addnew_walker_nav_menu extends Walker_Nav_Menu {
//編集日時と現在時刻の差分チェック
function get_interval_days( $item ) {
$param = 'numberposts=1&orderby=modified&order=DESC';
switch ( $item->object ) {
case 'category':
$param .= '&category=' . $item->object_id;
break;
case 'page':
$param .= '&post_type=page&post_parent='. $item->object_id;
break;
}
$post = get_posts($param);
if ( !count($post) ) {
return -1;
}
$timezone = new DateTimeZone('Asia/Tokyo');
$post_modified_date = date_create( $post[0]->post_modified, $timezone );
$today_date = date_create('now', $timezone);
$interval = $today_date->diff($post_modified_date);
return $interval->days;
}
// add main/sub classes to li's and links
function start_el( &$output, $item, $depth, $args ) {
global $wp_query;
$indent = ( $depth > 0 ? str_repeat( "\t", $depth ) : '' ); // code indent
// passed classes
$classes = empty( $item->classes ) ? array() : (array) $item->classes;
$class_names = esc_attr( implode( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item ) ) );
// build html
$output .= $indent . '<li id="menu-item-'. $item->ID . '" class="' . $class_names . ' menu-item-'. $item->ID . '">';
$interval = $this->get_interval_days($item);
//編集3日以内の記事があったらNEWつける
if ( $interval >= 0 && $interval <= 3 ) {
$output .= '<i class="icon icon-new" title="New!"></i>';
}
// link attributes
$attributes = ! empty( $item->attr_title ) ? ' title="' . esc_attr( $item->attr_title ) .'"' : '';
$attributes .= ! empty( $item->target ) ? ' target="' . esc_attr( $item->target ) .'"' : '';
$attributes .= ! empty( $item->xfn ) ? ' rel="' . esc_attr( $item->xfn ) .'"' : '';
$attributes .= ! empty( $item->url ) ? ' href="' . esc_attr( $item->url ) .'"' : '';
$item_output = sprintf( '%1$s<a%2$s>%3$s%4$s%5$s</a>%6$s',
$args->before,
$attributes,
$args->link_before,
apply_filters( 'the_title', $item->title, $item->ID ),
$args->link_after,
$args->after
);
// build html
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
}
}
?>
このカスタムウォーカーを使うと、3日以内に更新された記事があるカテゴリーか、
3日以内に更新された子ページを持つ固定ページのメニューリストの中にNEWアイコンのタグが入る。

投稿日時の差分チェックは get_interval_days 関数でやってて、
PHP5.5らしく DateTime、DateInterval、 DateTimeZone を使ってナウく仕上げてみた\(^o^)/
//編集日時と現在時刻の差分チェック
function get_interval_days( $item ) {
//編集日時の最新順に1件だけ( ゚д゚)ホスィ
$param = 'numberposts=1&orderby=modified&order=DESC';
switch ( $item->object ) {
//カテゴリーだったら
case 'category':
//カテゴリーID指定
$param .= '&category=' . $item->object_id;
break;
//固定ページだったら
case 'page':
//子ページだけ得る
$param .= '&post_type=page&post_parent='. $item->object_id;
break;
}
//上で作ったパラメータをget_postsに投げて投稿オブジェクトを得る
$post = get_posts($param);
//投稿がなかった
if ( !count($post) ) {
return -1;
}
//タイムゾーンを東京に設定
$timezone = new DateTimeZone('Asia/Tokyo');
//編集日時の DateTime インスタンスを作る
$post_modified_date = date_create( $post[0]->post_modified, $timezone );
//現在時刻の DateTime インスタンスを作る
$today_date = date_create('now', $timezone);
//差分の DateInterval インスタンスを得る
$interval = $today_date->diff($post_modified_date);
//何日差があるか返す
return $interval->days;
}
この関数はそのカテゴリーor子ページで1番新しい投稿が編集されてから何日経過しているか、を返すので
カスタムウォーカー内で適当な日数でチェックしてtrueならタグ入れるようにすればNewがつく。
$interval = $this->get_interval_days($item);
//編集3日以内の記事があったらNEWつける
if ( $interval >= 0 && $interval <= 3 ) {
$output .= '<i class="icon icon-new" title="New!"></i>';
}
なおget_postsはデータベースにクエリを投げつけてデータを得る割と乱暴な関数なので、実行回数が多いとかなりの負荷を生み出す。
商用サイト風なナビゲーションの対象が少ないサイトには向いているが、
普通のブログ形式で普通にカテゴリーとか羅列しているサイトでの使用はオススメできない。
関数の中身をちょっと変更すればループ内でも使えます。
カテゴリー内の投稿数表示
カテゴリーリストの変更はwp_list_categoriesでWalker_Category使うっていう手があるんだけど、
親カテゴリーの表示は別に用意しなきゃだめだったことと、書き出されるHTMLにULを含める事がWalkerだけでは出来なかったので、
wp_nav_menuを使ってカウント表示させることにした。

メニューの設定で表示したいカテゴリーを1つずつ追加する。

カスタムウォーカー
<?php
class Category_walker_nav_menu extends Walker_Nav_Menu {
// add main/sub classes to li's and links
function start_el( &$output, $item, $depth, $args ) {
global $wp_query;
$indent = ( $depth > 0 ? str_repeat( "\t", $depth ) : '' ); // code indent
$count = '';
if ($item->object === 'category') {
$category = get_category($item->object_id);
$count = ' (' . number_format_i18n( $category->count ) . ')';
$current = $item->current ? ' active' : '';
}
// build html
$output .= $indent . '<li id="tab-'. $item->ID . '" class="' . $current . '">';
// link attributes
$attributes = ! empty( $item->attr_title ) ? ' title="' . esc_attr( $item->attr_title ) .'"' : '';
$attributes .= ! empty( $item->target ) ? ' target="' . esc_attr( $item->target ) .'"' : '';
$attributes .= ! empty( $item->xfn ) ? ' rel="' . esc_attr( $item->xfn ) .'"' : '';
$attributes .= ! empty( $item->url ) ? ' href="' . esc_attr( $item->url ) .'"' : '';
$attributes .= ' class="btn"';
$item_output = sprintf( '%1$s<a%2$s>%3$s%4$s%5$s%6$s</a>%7$s',
$args->before,
$attributes,
$args->link_before,
apply_filters( 'the_title', $item->title, $item->ID ),
$count,
$args->link_after,
$args->after
);
// build html
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
}
}



