For a project I’m working on, I needed to create a split menu, where the top level navigation was in a horizontal menu in the header, with all underlying content listed elsewhere on the page. This turned out to be a little harder than I had anticipated, but I managed to get it working. Here’s how I did it. Feel free to add suggestions in the comments if you see room for improvement.
Image may be NSFW.
Clik here to view.
The horizontal menu
For the main menu (shown in red in the image above), I wanted to use WordPress 3.0′s new ‘Menus’ feature. By doing so, my client has full control over the menu’s content. Setting it up is very easy to do, so I won’t bother explaining it here.
WordPress automatically adds CSS classes to the menu for selected items, so highlighting the current selection is very easy.
#mainmenu li.current-menu-item,
#mainmenu li.current-page-ancestor,
#mainmenu li.current-post-ancestor,
#mainmenu li.current-category-ancestor { background-color: #00f; }
The sub menu
The main issue with the secondary menu (light blue in the image) was figuring out what to display. It’s easy enough in WordPress to feed the current page’s ID number to the wp_list_pages function and get its children, but in this case, that’s not what I wanted. I needed to display the menu from the selected main menu item on down, even if the current page was nested four levels deep. So instead of using the current page’s ID, I needed to figure out the selected top level page, and use that page’s ID.
function rt_getTopPageID( $page_ID ){
$ancestors = get_post_ancestors($page_ID);
if( !empty($ancestors) ){
foreach( $ancestors as $ancestor ){
$anc = get_post_ancestors($ancestor);
if( empty($anc) ){ return $ancestor; }
}
}
return $page_ID;
}
I added this function to my theme’s functions.php file. What it does is move up the page hierarchy step my step until it reaches the top page. It returns that page’s ID for use with wp_list_pages. I’m aware that this may be slow, but it’s the most efficient way I could find to get the top page.
On the sidebar, I used something like this to display the sub menu:
global $wp_query;
$page_ID = $wp_query->post->ID;
$toppage = rt_getTopPageID( $page_ID );
wp_list_pages( 'title_li=&child_of='.$toppage );
What this does is get the current page’s ID from the page query, and feed it to my rt_getTopPageID function. The returned ID is then used to call the correct menu.
Categories
I then needed to do the same for categories. I ended up creating a sidebar widget that, depending on the context, displayed the right menu. The widget is very specific to my project, but here’s the function it uses to get the top category.
function rt_getTopCategoryID( $cat_ID ){
$pathStr = get_category_parents( $cat_ID, false, '[#]', false );
$pathArr = explode( '[#]', $pathStr );
$cat = get_term_by( 'name', $pathArr[0], 'category' );
return $cat->term_id;
}
This function uses a shortcut to get to the top level category. The get_category_parents function returns a string with the full hierarchy, which we can then split. The first item in the resulting array is the top category’s name. Using get_term_by, we can then get that category’s ID. I’m assuming that this is lighter and quicker than trying to move up the hierarchy like I did with the pages.
The widget then uses this function to get the category structure. On category overview pages, I use this code.
$cat_ID = get_query_var('cat');
$topCat = rt_getTopCategoryID( $cat_ID );
wp_list_categories( 'title_li=&child_of='.$topCat );
On single post pages, This is what I currently use, but I’ll be the first to admit this needs work. Posts can be in many categories, which can result in all sorts of weirdness. Some editorial restraint will be in order for my client, but the principle works.
global $post;
$categories = get_the_category($post->ID);
$topCat = rt_getTopCategoryID( $categories[0]->term_id );
wp_list_categories( 'title_li=&child_of='.$topCat );
That’s it. My widget now shows the correct menu on single page, post and category pages. There’s probably some room left for improvement, so don’t be shy and comment away.