Wordpress - Display a portion/ branch of the menu tree using wp_nav_menu()

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

@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.


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! :-)

Tags:

Menus

Sub Menu