113

I have a menu defined in WP Admin that looks like this:

alt text

I want to be able to display all the child links on the sidebar whenever I am at a parent page. For example, if the user is on my "About Us" page, I want a list of the 4 links highlighted in green to appear on the sidebar.

I looked at the documentation for wp_nav_menu() and it doesn't appear to have any built-in way to specify a particular node of a given menu to use as the starting point when generating the links.

I created a solution for a similar situation which relied on the relationships created by the page parent, but I am looking for one which uses the menu system specifically. Any help would be appreciated.

4
  • 2
    So you want to maintain the entire menu as a custom menu, but create a custom walker which displays it expanding only the active subtree? Like this code, but extending wp_nav_menu instead of wp_list_pages? I recently did something similar and could post the code if thats what you're looking for... Oct 12, 2010 at 1:51
  • 1
    @goldenapples, that's exactly what I am after. If you don't mind posting your code as an answer I would be very grateful.
    – jessegavin
    Oct 12, 2010 at 14:26
  • 1
    I wonder that such a obvious useful functionality is not already build it. That's overall very useful for any site that does "CMS".
    – hakre
    Feb 9, 2011 at 15:54
  • I'm trying to solve the above problem or something similar. As an alternative I came up with a CSS solution here: stackoverflow.com/q/7640837/518169
    – hyperknot
    Oct 3, 2011 at 23:52

11 Answers 11

78

This was still on my mind so I revisited it and put together this solution, that does not rely on context that much:

add_filter( 'wp_nav_menu_objects', 'submenu_limit', 10, 2 );

function submenu_limit( $items, $args ) {

    if ( empty( $args->submenu ) ) {
        return $items;
    }

    $ids       = wp_filter_object_list( $items, array( 'title' => $args->submenu ), 'and', 'ID' );
    $parent_id = array_pop( $ids );
    $children  = submenu_get_children_ids( $parent_id, $items );

    foreach ( $items as $key => $item ) {

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

    return $items;
}

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

Usage

$args = array(
    'theme_location' => 'slug-of-the-menu', // the one used on register_nav_menus
    'submenu' => 'About Us', // could be used __() for translations
);

wp_nav_menu( $args );
16
  • Lovely technique! May I ask something possibly regarding to this one: How would you display content of those listed submenu pages in template? Aug 22, 2012 at 5:09
  • 2
    @daniel.tosaba you will need to subclass or use filters in Walker_Nav_Menu class. Like all menu stuff too much for comment - ask new question about it?
    – Rarst
    Aug 22, 2012 at 11:18
  • 3
    Such a fantastic answer. Thank you so much. This should really be a default option within WordPress.
    – dotty
    Sep 11, 2012 at 11:19
  • 5
    Really neat. If anyone is interested, to do the same but by page ID, change the wp_filter_object_list line to wp_filter_object_list( $items, array( 'object_id' => $args->submenu ), 'and', 'ID' );
    – Ben
    Apr 30, 2015 at 10:25
14

@goldenapples: Your Walker Class does not work. But the idea is really good. I created a walker based on your idea:

class Selective_Walker extends Walker_Nav_Menu
{
    function walk( $elements, $max_depth) {

        $args = array_slice(func_get_args(), 2);
        $output = '';

        if ($max_depth < -1) //invalid parameter
            return $output;

        if (empty($elements)) //nothing to walk
            return $output;

        $id_field = $this->db_fields['id'];
        $parent_field = $this->db_fields['parent'];

        // flat display
        if ( -1 == $max_depth ) {
            $empty_array = array();
            foreach ( $elements as $e )
                $this->display_element( $e, $empty_array, 1, 0, $args, $output );
            return $output;
        }

        /*
         * need to display in hierarchical order
         * separate elements into two buckets: top level and children elements
         * children_elements is two dimensional array, eg.
         * children_elements[10][] contains all sub-elements whose parent is 10.
         */
        $top_level_elements = array();
        $children_elements  = array();
        foreach ( $elements as $e) {
            if ( 0 == $e->$parent_field )
                $top_level_elements[] = $e;
            else
                $children_elements[ $e->$parent_field ][] = $e;
        }

        /*
         * when none of the elements is top level
         * assume the first one must be root of the sub elements
         */
        if ( empty($top_level_elements) ) {

            $first = array_slice( $elements, 0, 1 );
            $root = $first[0];

            $top_level_elements = array();
            $children_elements  = array();
            foreach ( $elements as $e) {
                if ( $root->$parent_field == $e->$parent_field )
                    $top_level_elements[] = $e;
                else
                    $children_elements[ $e->$parent_field ][] = $e;
            }
        }

        $current_element_markers = array( 'current-menu-item', 'current-menu-parent', 'current-menu-ancestor' );  //added by continent7
        foreach ( $top_level_elements as $e ){  //changed by continent7
            // descend only on current tree
            $descend_test = array_intersect( $current_element_markers, $e->classes );
            if ( !empty( $descend_test ) ) 
                $this->display_element( $e, $children_elements, 2, 0, $args, $output );
        }

        /*
         * if we are displaying all levels, and remaining children_elements is not empty,
         * then we got orphans, which should be displayed regardless
         */
         /* removed by continent7
        if ( ( $max_depth == 0 ) && count( $children_elements ) > 0 ) {
            $empty_array = array();
            foreach ( $children_elements as $orphans )
                foreach( $orphans as $op )
                    $this->display_element( $op, $empty_array, 1, 0, $args, $output );
         }
        */
         return $output;
    }
}

Now you can use:

<?php wp_nav_menu( 
   array(
       'theme_location'=>'test', 
       'walker'=>new Selective_Walker() ) 
   ); ?>

The output is a list containing the current root element and it's children (not their children). Def: Root element := The top level menu item that corresponds to the current page or is parent of a current page or a parent of a parent ...

This does not exactly answer the original question but almost, since there is still the top level item. This is fine for me, because I want the top level element as a headline of the sidebar. If you want to get rid of this, you might have to override display_element or use a HTML-Parser.

0
13

Hi @jessegavin:

Nav Menus are stored in a combination of custom post types and custom taxonomies. Each menu is stored as a Term (i.e. "About Menu", found in wp_terms) of a Custom Taxonomy (i.e. nav_menu, found in wp_term_taxonomy.)

Each Nav Menu Item is stored as a post of post_type=='nav_menu_item' (i.e. "About the Firm", found in wp_posts) with it's attributes stored as post meta (in wp_postmeta) using a meta_key prefix of _menu_item_* where _menu_item_menu_item_parent is the ID of your menu item's parent Nav Menu item post.

The relationship between menus and menu items is stored in wp_term_relationships where object_id relates to the $post->ID for the Nav Menu Item and the $term_relationships->term_taxonomy_id relates to the menu defined collectively in wp_term_taxonomyand wp_terms.

I'm pretty sure it would be possible to hook both 'wp_update_nav_menu' and 'wp_update_nav_menu_item' to create actual menus in wp_terms and a parallel set of relations in wp_term_taxonomy and wp_term_relationships where every Nav Menu Item that has sub-Nav Menu items also becomes it's own Nav Menu.

You'd also want to hook 'wp_get_nav_menus' (which I suggested be added to WP 3.0 based on some similar work I was doing a few months ago) to ensure that your generated Nav Menus are not displayed for manipulation by the user in the admin, otherwise they'd get out of sync really fast and then you'd have a data nightmare on your hand.

Sounds like a fun and useful project, but it is a little bit more code and testing than I can afford to tackle right now in part because anything that synchronizes data tends to be a PITA when it comes to ironing out all the bugs (and because paying clients are pressing me to get things done. :) But armed with the above info I'm pretty a motivated WordPress plugin developer could code it if they wanted to.

Of course you do realize now if you do code it you are obligated to post it back here so we can all benefit from your largesse! :-)

3
  • I am not sure that I am following what you're saying. I am looking for a read-only solution for displaying "sub-menus" related to the current page a user is at. Are we talking about the same thing? - I do appreciate your deeper explanation about the database schema though.
    – jessegavin
    Oct 14, 2010 at 1:24
  • @jessegavin - Yes, if you want to call wp_nav_menu() then you'll need to clone the menus because wp_nav_menu() is tightly coupled to the menu structure. The other option is to copy the wp_nav_menu() code and make the modifications required to display as a submenu. Oct 14, 2010 at 2:34
  • This is the answer I've been looking for all day, thank you very much!
    – Chris Haas
    Dec 13, 2019 at 20:39
10

This is a walker extension which should do what you're looking for:

class Selective_Walker extends Walker_Nav_Menu
{

    function walk( $elements, $max_depth) {

        $args = array_slice(func_get_args(), 2);
        $output = '';

        if ($max_depth < -1) //invalid parameter
            return $output;

        if (empty($elements)) //nothing to walk
            return $output;

        $id_field = $this->db_fields['id'];
        $parent_field = $this->db_fields['parent'];

        // flat display
        if ( -1 == $max_depth ) {
            $empty_array = array();
            foreach ( $elements as $e )
                $this->display_element( $e, $empty_array, 1, 0, $args, $output );
            return $output;
        }

        /*
         * need to display in hierarchical order
         * separate elements into two buckets: top level and children elements
         * children_elements is two dimensional array, eg.
         * children_elements[10][] contains all sub-elements whose parent is 10.
         */
        $top_level_elements = array();
        $children_elements  = array();
        foreach ( $elements as $e) {
            if ( 0 == $e->$parent_field )
                $top_level_elements[] = $e;
            else
                $children_elements[ $e->$parent_field ][] = $e;
        }

        /*
         * when none of the elements is top level
         * assume the first one must be root of the sub elements
         */
        if ( empty($top_level_elements) ) {

            $first = array_slice( $elements, 0, 1 );
            $root = $first[0];

            $top_level_elements = array();
            $children_elements  = array();
            foreach ( $elements as $e) {
                if ( $root->$parent_field == $e->$parent_field )
                    $top_level_elements[] = $e;
                else
                    $children_elements[ $e->$parent_field ][] = $e;
            }
        }

        $current_element_markers = array( 'current-menu-item', 'current-menu-parent', 'current-menu-ancestor' );

        foreach ( $top_level_elements as $e ) {

            // descend only on current tree
            $descend_test = array_intersect( $current_element_markers, $e->classes );
            if ( empty( $descend_test ) )  unset ( $children_elements );

            $this->display_element( $e, $children_elements, $max_depth, 0, $args, $output );
        }

        /*
         * if we are displaying all levels, and remaining children_elements is not empty,
         * then we got orphans, which should be displayed regardless
         */
        if ( ( $max_depth == 0 ) && count( $children_elements ) > 0 ) {
            $empty_array = array();
            foreach ( $children_elements as $orphans )
                foreach( $orphans as $op )
                    $this->display_element( $op, $empty_array, 1, 0, $args, $output );
         }

         return $output;
    }

}

Based loosely on mfields' code I referenced in my comment earlier. All it does is check when walking the menu to see whether the current element is (1) the current menu item, or (2) an ancestor of the current menu item, and expands the subtree below it only if either of those conditions is true. Hope this works for you.

To use it, just add a "walker" argument when you call the menu, ie:

<?php wp_nav_menu( 
   array(
       'theme_location'=>'test', 
       'walker'=>new Selective_Walker() ) 
   ); ?>
4
  • Oh... I just re-read your question and realized that I had misunderstood it at first. This walker will show all the other top-level menu items, just not expand them. This wasn't exactly what you had wanted to do. Still, this code can be modified in any way you want. Just look at the loop through $top_level_elements and add your own test before the call to $this->display_element. Oct 16, 2010 at 19:46
  • Is it possible to get this class to show the depth of the current subpage? That is.. If I have a depth of three or more levels, that the third and subsequent levels are shown for the current (sub)page? At the moment, it only shows A > B, and not > C (C being the third (level)
    – Zolomon
    Feb 20, 2011 at 20:10
  • @Zolomon - I'm not sure I understand your question. This should expand the entire tree under any menu item with the classes 'current-menu-item', 'current-menu-parent', or 'current-menu-ancestor'. When I test it, it displays all the levels of subpages in the menu. What are you looking to do? Feb 21, 2011 at 20:39
  • Maybe you want to pass a depth parameter to the call to wp_nav_menu, in case your theme is somehow over-riding the default of 0 (show all levels)? Feb 21, 2011 at 20:41
8

Update: I made this into a plugin. Download here.


I needed to solve this myself and eventually wound up writing a filter on the results of the menu lookup. It lets you use wp_nav_menu as normal, but choose a sub-section of the menu based on the title of the parent element. Add a submenu parameter to the menu like so:

wp_nav_menu(array(
  'menu' => 'header',
  'submenu' => 'About Us',
));

You can even go several levels deep by putting slashes in:

wp_nav_menu(array(
  'menu' => 'header',
  'submenu' => 'About Us/Board of Directors'
));

Or if you prefer with an array:

wp_nav_menu(array(
  'menu' => 'header',
  'submenu' => array('About Us', 'Board of Directors')
));

It uses a slug version of the title, which should make it forgiving of things like capitals and punctuation.

3
  • Is it possible to reach submenu via id? I mean page id, or post id.
    – Digerkam
    Feb 28, 2013 at 16:28
  • split() is deprecated, replace $loc = split( "/", $loc ); in plugin with $loc = preg_split( "~/~", $loc );
    – Floris
    Sep 4, 2018 at 10:29
  • I would also suggest making $submenu optional. So you can still fetch the entire menu when needed. Add this to the filter to the top: ` if ( ! isset( $args->submenu ) ) { return $items; }`
    – Floris
    Sep 4, 2018 at 10:43
8

I put together the following class for myself. It will find the top nav parent of the current page, or you can give it a target top nav ID in the walker constructor.

class Walker_SubNav_Menu extends Walker_Nav_Menu {
    var $target_id = false;

    function __construct($target_id = false) {
        $this->target_id = $target_id;
    }

    function walk($items, $depth) {
        $args = array_slice(func_get_args(), 2);
        $args = $args[0];
        $parent_field = $this->db_fields['parent'];
        $target_id = $this->target_id;
        $filtered_items = array();

        // if the parent is not set, set it based on the post
        if (!$target_id) {
            global $post;
            foreach ($items as $item) {
                if ($item->object_id == $post->ID) {
                    $target_id = $item->ID;
                }
            }
        }

        // if there isn't a parent, do a regular menu
        if (!$target_id) return parent::walk($items, $depth, $args);

        // get the top nav item
        $target_id = $this->top_level_id($items, $target_id);

        // only include items under the parent
        foreach ($items as $item) {
            if (!$item->$parent_field) continue;

            $item_id = $this->top_level_id($items, $item->ID);

            if ($item_id == $target_id) {
                $filtered_items[] = $item;
            }
        }

        return parent::walk($filtered_items, $depth, $args);
    }

    // gets the top level ID for an item ID
    function top_level_id($items, $item_id) {
        $parent_field = $this->db_fields['parent'];

        $parents = array();
        foreach ($items as $item) {
            if ($item->$parent_field) {
                $parents[$item->ID] = $item->$parent_field;
            }
        }

        // find the top level item
        while (array_key_exists($item_id, $parents)) {
            $item_id = $parents[$item_id];
        }

        return $item_id;
    }
}

Nav call:

wp_nav_menu(array(
    'theme_location' => 'main_menu',
    'walker' => new Walker_SubNav_Menu(22), // with ID
));
4

@davidn @hakre Hi, i have an ugly solution without an HTML-Parser or overriding display_element.

 class Selective_Walker extends Walker_Nav_Menu
    {
        function walk( $elements, $max_depth) {

            $args = array_slice(func_get_args(), 2);
            $output = '';

            if ($max_depth < -1) //invalid parameter
                return $output;

            if (empty($elements)) //nothing to walk
                return $output;

            $id_field = $this->db_fields['id'];
            $parent_field = $this->db_fields['parent'];

            // flat display
            if ( -1 == $max_depth ) {
                $empty_array = array();
                foreach ( $elements as $e )
                    $this->display_element( $e, $empty_array, 1, 0, $args, $output );
                return $output;
            }

            /*
             * need to display in hierarchical order
             * separate elements into two buckets: top level and children elements
             * children_elements is two dimensional array, eg.
             * children_elements[10][] contains all sub-elements whose parent is 10.
             */
            $top_level_elements = array();
            $children_elements  = array();
            foreach ( $elements as $e) {
                if ( 0 == $e->$parent_field )
                    $top_level_elements[] = $e;
                else
                    $children_elements[ $e->$parent_field ][] = $e;
            }

            /*
             * when none of the elements is top level
             * assume the first one must be root of the sub elements
             */
            if ( empty($top_level_elements) ) {

                $first = array_slice( $elements, 0, 1 );
                $root = $first[0];

                $top_level_elements = array();
                $children_elements  = array();
                foreach ( $elements as $e) {
                    if ( $root->$parent_field == $e->$parent_field )
                        $top_level_elements[] = $e;
                    else
                        $children_elements[ $e->$parent_field ][] = $e;
                }
            }

            $current_element_markers = array( 'current-menu-item', 'current-menu-parent', 'current-menu-ancestor' );  //added by continent7
            foreach ( $top_level_elements as $e ){  //changed by continent7
                // descend only on current tree
                $descend_test = array_intersect( $current_element_markers, $e->classes );
                if ( !empty( $descend_test ) ) 
                    $this->display_element( $e, $children_elements, 2, 0, $args, $output );
            }

            /*
             * if we are displaying all levels, and remaining children_elements is not empty,
             * then we got orphans, which should be displayed regardless
             */
             /* removed by continent7
            if ( ( $max_depth == 0 ) && count( $children_elements ) > 0 ) {
                $empty_array = array();
                foreach ( $children_elements as $orphans )
                    foreach( $orphans as $op )
                        $this->display_element( $op, $empty_array, 1, 0, $args, $output );
             }
            */

/*added by alpguneysel  */
                $pos = strpos($output, '<a');
            $pos2 = strpos($output, 'a>');
            $topper= substr($output, 0, $pos).substr($output, $pos2+2);
            $pos3 = strpos($topper, '>');
            $lasst=substr($topper, $pos3+1);
            $submenu= substr($lasst, 0, -6);

        return $submenu;
        }
    }
1
  • After trying them all, Alp's solution was the only one that worked for me. However one problem with it. It on only show's the first level children, but does not show the third or fourth level children. I've been trying to for days to get it to do so. Anyone know how to modify his solution as such? PS. It won't let me add comments so need to do it as an answer. May 24, 2011 at 1:30
3

The nav menu output includes lots of classes for current item, current item ancestor, etc. In some situations, I have been able to do what you want to do by letting the entire nav tree output, and then using css to pare it down to only children of the current page, etc.

3

I made a modified walker that should help! Not perfect - it leaves a few empty elements, but it does the trick. The modification is basically those $current_branch bits. Hope it helps someone!

class Kanec_Walker_Nav_Menu extends Walker {
/**
 * @see Walker::$tree_type
 * @since 3.0.0
 * @var string
 */
var $tree_type = array( 'post_type', 'taxonomy', 'custom' );

/**
 * @see Walker::$db_fields
 * @since 3.0.0
 * @todo Decouple this.
 * @var array
 */
var $db_fields = array( 'parent' => 'menu_item_parent', 'id' => 'db_id' );

/**
 * @see Walker::start_lvl()
 * @since 3.0.0
 *
 * @param string $output Passed by reference. Used to append additional content.
 * @param int $depth Depth of page. Used for padding.
 */
function start_lvl(&$output, $depth) {
    $indent = str_repeat("\t", $depth);
    $output .= "\n$indent<ul class=\"sub-menu\">\n";
}

/**
 * @see Walker::end_lvl()
 * @since 3.0.0
 *
 * @param string $output Passed by reference. Used to append additional content.
 * @param int $depth Depth of page. Used for padding.
 */
function end_lvl(&$output, $depth) {
    global $current_branch;
    if ($depth == 0) $current_branch = false;
    $indent = str_repeat("\t", $depth);
    $output .= "$indent</ul>\n";
}

/**
 * @see Walker::start_el()
 * @since 3.0.0
 *
 * @param string $output Passed by reference. Used to append additional content.
 * @param object $item Menu item data object.
 * @param int $depth Depth of menu item. Used for padding.
 * @param int $current_page Menu item ID.
 * @param object $args
 */
function start_el(&$output, $item, $depth, $args) {
    global $wp_query;
    global $current_branch;

    // Is this menu item in the current branch?
    if(in_array('current-menu-ancestor',$item->classes) ||
    in_array('current-menu-parent',$item->classes) ||
    in_array('current-menu-item',$item->classes)) {
        $current_branch = true; 
    }

    if($current_branch && $depth > 0) {
        $indent = ( $depth ) ? str_repeat( "\t", $depth ) : '';

        $class_names = $value = '';

        $classes = empty( $item->classes ) ? array() : (array) $item->classes;
        $classes[] = 'menu-item-' . $item->ID;

        $class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item ) );
        $class_names = ' class="' . esc_attr( $class_names ) . '"';

        $id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args );
        $id = strlen( $id ) ? ' id="' . esc_attr( $id ) . '"' : '';

        $output .= $indent . '<li' . $id . $value . $class_names .'>';

        $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 = $args->before;
        $item_output .= '<a'. $attributes .'>';
        $item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
        $item_output .= '</a>';
        $item_output .= $args->after;

        $output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
    }

}

/**
 * @see Walker::end_el()
 * @since 3.0.0
 *
 * @param string $output Passed by reference. Used to append additional content.
 * @param object $item Page data object. Not used.
 * @param int $depth Depth of page. Not Used.
 */
function end_el(&$output, $item, $depth) {
    global $current_branch;
    if($current_branch && $depth > 0) $output .= "</li>\n";
    if($depth == 0) $current_branch = 0;
}

}

3

Check out the code in my plugin or use it for your purpose ;)

This plugin adds enhanced "Navigation Menu" widget. It offers many options which could be set to customize the output of the custom menu through the widget.

Features include:

  • Custom hierarchy - "Only related sub-items" or "Only strictly related sub-items".
  • Starting depth and maximum level to display + flat display.
  • Display all menu items starting with the selected one.
  • Display only direct path to current element or only children of
    selected item (option to include the parent item).
  • Custom class for a widget block.
  • And almost all the parameters for the wp_nav_menu function.

http://wordpress.org/extend/plugins/advanced-menu-widget/

0
0

The accepted answer acknowledges it requires input and isn't based on context. Here's a version that supports being given context, or can use WordPress's built-in current-x classes to determine context:

add_filter( 'wp_nav_menu_objects', 'limit_tree', 10, 2 );

function limit_tree( $items, $args ) {

    if ( empty( $args->context ) ) {
        return $items;
    }

    if('current' == $args->context) {

        $current_pages = array_filter($items, function($item) {
            return !empty(array_intersect(['current-menu-parent', 'current-menu-ancestor', 'current-menu-item'], $item->classes));
        });

        $parent_id = array_pop($current_pages)->ID;
        
    } else {
        $ids       = wp_filter_object_list( $items, array( 'object_id' => $args->context ), 'and', 'ID' );
        $parent_id = array_pop( $ids );
    }
    
    $children = submenu_get_children_ids( $parent_id, $items );

    foreach ( $items as $key => $item ) {

        // This can be adjusted if you don't want to include the parent
        if ( $item->ID != $parent_id && ! in_array( $item->ID, $children ) ) {
            unset( $items[$key] );
        }
    }

    return $items;
}

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

And usage:

wp_nav_menu([
    'theme_location'  => 'foo-bar',
    'context' => 'current',
]);

Or...

wp_nav_menu([
    'theme_location'  => 'foo-bar',
    'context' => get_queried_object_id(), // or some arbitrary post ID
]);

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