1

I am trying to display a secondary menu on pages that displays all the coordinating sub-pages of the top-level menu item they navigated there from, but my issue is that some pages appear under multiple top-level menu items. Since I know I can't assign a page to multiple parents, is there a way to do this with custom fields or taxonomies maybe? Or is there a better way?

I'm probably not explaining this the best way, but you can see an example of what I’m trying to achieve on http://parkered.org. If you navigate to Advantages > Key Industries, you’ll see how the entire Advantages submenu appears across the top. But, Key Industries page also exists under the Business Services menu item, so if you click on it from there, the Business Services submenu shows across the top instead.

1

In your example, the 'Key Industries' page appears to be a different page under each of those top level menus - or at the very least, the URL is different.

This most likely allows their CMS to differentiate and use the correct submenu.

Here's what I do when I want to achieve this:

  1. Hook into wp_nav_menu_objects and check for a custom submenu argument, which will hold the parent menu item ID
  2. Within that hook, unset any menu items that don't have that same parent
  3. Then, when calling wp_nav_menu(), supply a submenu argument with the parent menu item ID of the submenu to display (I'll go into how to do this below)

Hooking into wp_nav_menu_objects and unsetting non-child menu items:

Here's the function I use for this. It will check for the submenu argument on each call to wp_nav_menu(). If submenu is set, it will proceed to remove any item which is not a child of the menu item ID that submenu refers to:

add_filter("wp_nav_menu_objects", "mytheme_submenu_limit", 10, 2);

function mytheme_submenu_limit($items, $args){

    if(empty($args->submenu)){ return $items; } // if no submenu arg is set, return immediately

    $filtered = wp_filter_object_list($items, array("ID" => $args->submenu), "and", "ID");
    $parent_id = array_pop($filtered);
    $children = mytheme_submenu_get_children_ids($parent_id, $items);

    foreach($items as $key => $item){
        if(!in_array($item->ID, $children)){
            unset($items[$key]);
        }
    }

    return $items;

}

function my_theme_submenu_get_children_ids($id, $items){
    $ids = wp_filter_object_list($items, array("menu_item_parent" => $id), "and", "ID");
    foreach($ids as $id){ $ids = array_merge($ids, submenu_get_children_ids($id, $items)); }
    return $ids;
}

Calling wp_nav_menu():

When you call wp_nav_menu(), you then need to add the custom submenu argument, eg:

wp_nav_menu(array("theme_location" => "my_menu_area", "submenu" => 240));

Of course the key here is - how do you get that submenu ID?

You can do this manually if you really want to, but a better way would be to automagically determine what this ID should be by doing some hunting through the current query.

Essentially you just want to determine what the menu item ID is of the page currently being displayed, and then:

  • if that is a top level menu item (i.e. the parent is 0), send that menu item ID to the submenu argument, or
  • if that is not a top level menu item, send it's parent ID to the submenu argument

This will then result - as long as that page is not used more than once in the menu of course! - in the correct submenu being displayed.

Techniques for this will vary depending on the set up of your site, and, crucically, how many 'levels' of submenus you want to support. But it could look something like this:

global $wp_query;
$queried_object = $wp_query->get_queried_object();
$menu_query_args = array("meta_query" => array("key" => "_menu_item_object_id", "value" => $queried_object->ID));

$locations = get_nav_menu_locations();
$menu_objects = wp_get_nav_menu_items($locations["my_menu_area"], $menu_query_args);

if($menu_objects[0]->menu_item_parent == 0){ $submenu_id = $menu_objects[0]->ID; }
else{ $submenu_id = $menu_objects[0]->menu_item_parent; }

wp_nav_menu(array("theme_location" => "my_menu_area", "submenu" => $submenu_id));

I must stress this is a really simplified version; you will need to understand how it works rather than just dropping it into your site. And you will probably need to add additional logic to make it behave exactly how you want to on your site. But hopefully, this is what you need to get going!

  • Thanks! That makes sense, but I think part of my issue is that several pages do in fact exist more than once in the menu - I have about 4-5 pages that exist under more than one top-level menu item. So in those cases, there would need to be something that basically tracks/recognizes what top-level menu item is the referrer, correct? Could this possibly be done with wp_get_referer()?? – CBlanch May 4 '16 at 14:20
  • Yeah there's no easy way around that - even the example you shared uses different pages. Checking the referrer would probably work; you'd need logic to determine if the referrer was the top level item or another item within that menu. Another way to do it might be adding query strings to the end of the menu links. You'd still need to rely on a "default" if the page was visited directly or from elsewhere. – Tim Malone May 4 '16 at 21:15
  • But, perhaps consider if you should have separate pages instead. Another issue you'll encounter using the same page in multiple places: which menu items will show in the "currently active" state? – Tim Malone May 4 '16 at 21:16
  • Yes, that definitely throws a wrench in things too. I'd like to avoid creating multiple pages to avoid any confusion or oversight when the client is updating content down the line. Unless there's a way to duplicate pages programmatically, so only one appears in the WP backend?? That seems to be over complicating things though. I may reconsider the submenu altogether and possibly just display a list of "related pages" instead so I don't have to worry about which submenu to display on duplicated pages. Thanks for all your help! – CBlanch May 5 '16 at 15:47
  • I've done exactly that for a client before - duplicating pages programmatically in the backend. The way I did it was having a custom field where the admin could select another page to use to replace the current page's content (using the ACF plugin because the inbuilt fields are excellent). If that option was set, the content field would hide and on the frontend I'd grab the content from the alternative page. – Tim Malone May 5 '16 at 20:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.