Creating a split menu in WordPress 3.0

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.


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.

21 Comments

  1. Link: Creating a split menu in WordPress 3.0 | Roy Tanck's weblog | Manually Chosen Best Free WordPress 3 Themes
  2. Hi Roy,

    I am interested in splitting the menu. A client is looking for this while also wants the ease of the new WP 3 Menu feature. In this case they want the TOP LEVEL menu as a horizontal menu up along the top of the site:

    FRUIT —— VEGETABLES —— MEATS

    BUT when you click on say FRUIT above THAT menu items SUBMENU ITEMS ( apples, oranges, bananas ) get displayed vertically in a sidebar on the left side of the FRUIT Page or any of the fruit submenu item pages.

    I assume this is what your tute above does??

    I can follow the addition of the code to the FUNCTIONS.PHP, but adding this code to the sidebar? Do you mean sidebar.php?

    My understanding of PHP is basic to say the least, I am getting better though!
    Aidan

    Comment by aidan o driscoll — September 22, 2010 @ 6:03 pm

    • Hi Aidan, I’ve opted to put the code in this post into a widget, but I didn’t include that into the code for reasons of clarify. Widgets require a bit of code to set up, which would clutter the examples. By making it a widget, I can add the sub menu to the sidebar by dragging the widget there. There are many other options.

      The codex page you mention has examples that move up only one step in the page or category hierarchy, so they won’t work for pages/categories nested more than one level deep.

      Comment by Roy — September 23, 2010 @ 2:17 pm

  3. Hi Again Roy,

    I think this might do the trick? Your Thoughts??

    http://codex.wordpress.org/Template_Tags/wp_list_pages#List_Sub-Pages

    Aidan

    Comment by aidan o driscoll — September 22, 2010 @ 6:14 pm

  4. Hi, this is something that I have been trying to do for a very long time so thanks for the informative article.
    John

    Comment by John Silva — September 23, 2010 @ 7:55 pm

  5. Hi Thanks for this tips.

    But I want to display all of my categories in one single drop down menu.

    For Example in this blog post your Categories are displaying as single drop down menu.

    Let me know how to get do this. ? Please teach me I will great full to you.

    Thanks.

    Comment by Albert — October 19, 2010 @ 7:21 pm

    • Albert, that’s simply a setting in the categories widget.

      Comment by Roy — October 20, 2010 @ 8:47 am

  6. thanks for sharing. i’m experiencing the same stuation, could i ask some questions?

    1. when i set up the mainmenu(horizontal) in WP menu builder, should i add also all child pages? or simply add top level menu items?

    2.once used your method, how to arrange submenu items’ order in WP admin?

    thanks in advance!

    Comment by jianglong — October 30, 2010 @ 10:20 pm

    • Hi Jianglong. 1. You can do this if you like, but the child pages will show up twice if you do. If you create a nice fly-out menu for them that would be perfect. 2. You can use the page order numbers for pages, but you’ll need to choose one of the default ordering mechanisms for categories.

      Comment by Roy — November 9, 2010 @ 9:04 am

  7. Thanks, i’ll try it method!

    Comment by Max — November 9, 2010 @ 10:34 am

  8. Awesome work Roy, thanks for sharing :)
    a small question about your currently implemented horizontal menu, the one you call “shortcuts>>”.
    is this some plugin that I don’t know of? or you did the customization yourself? how did you do it?

    looking forward to your reply
    many Thanks!

    Ozai, New York City

    Comment by Ozai — December 13, 2010 @ 6:16 pm

    • Ozai, that’s a quick hack in my theme. I really should do somthing like that with the new Menus function, but this predates WP 3.0.

      Comment by Roy — December 21, 2010 @ 11:47 am

  9. Thank you for this great tip. This saved me a lot of headache!

    Comment by Esther — April 6, 2011 @ 7:14 am

  10. hello,
    is it possible to show title of posts in categories?

    category1
    subcategory1
    post1
    post2
    subcategory2
    post
    category2
    category3

    and 2nd question: is it possible to show “featured images” of pages/posts in list? [function the_post_thumbnail]?

    thank you a lot!

    Comment by hali — May 12, 2011 @ 8:55 am

    • Hali, I’m afraid both require a bit of additional programming.

      Comment by Roy — May 13, 2011 @ 10:00 am

  11. I was looking to do something silimar to what hail had in mind.

    Roy how much extra programming do you think it would require?

    Comment by Phil Vandal — June 14, 2011 @ 3:30 am

  12. Phil, I’m afraid WordPress does not have easy functions to list posts. You’d need to use queries, and they’d need to be inbetween the categories somehow. This would require the cateegories to also be queried directly from the database. I’m thinking aloud right now, but I’m sure it’s something that would take (me) a lot of time.

    Comment by Roy — June 14, 2011 @ 12:44 pm

  13. Hi Roy. Thanks a lot for the well-explained example. I’ll try it. It’s just what I need.

    I’ve been working with Joomla sites a long time – where split menus are commonplace – and I was surprised that it was an uncommon way to display menus in WordPress.

    Did you bother to make this into a plugin?

    Cheers!

    Comment by Fredrik Lindgårdh — January 24, 2012 @ 10:38 am

    • No, no plugin. I mostly posted this to help people building themes.

      Comment by Roy — January 25, 2012 @ 11:27 am

  14. Hi Roy,

    thanks for this manual to build a splitmenu in wp 3.0. It was wonderfully working on a local testsite, having 2 sublevels like this:
    – sublevel 1
    – sublevel 2
    – sublevel 2
    – sublevel 1
    – sublevel 1
    – sublevel 2

    but now the problem is that it doesn’t correctly work anymore after updating to wp 3.3.1. it’s very interesting: structure is displayed correctly when navigating to a sublevel 1-item. but when navigating to a sublevel-2-item, then sublevels 1 won’t be displayed anymore.

    do you have any idea what could have happened?

    looking forward for a reply.
    best wishes,
    dan

    Comment by dan — February 6, 2012 @ 11:47 am

    • I’ve not played with this in a long time, so I have no clue what could be causing this. I assume you’ve checked things like the depth parameters that control how many levels are displayed?

      Comment by Roy — February 8, 2012 @ 8:56 am