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 ); } }