使用wp_NAV_MENU()显示菜单树的一部分/分支

时间:2010-10-11 作者:jessegavin

我在WP Admin中定义了一个菜单,如下所示:

alt text

每当我在父页面时,我希望能够在侧边栏上显示所有子链接。例如,如果用户在我的“关于我们”页面上,我希望侧栏上显示以绿色突出显示的4个链接的列表。

我查看了文档wp_nav_menu() 而且它似乎没有任何内置方式来指定给定菜单的特定节点,以作为生成链接时的起点。

我创造了一个解决方案a similar situation 这依赖于页面父级创建的关系,但我正在寻找一个专门使用菜单系统的。任何帮助都将不胜感激。

9 个回复
最合适的回答,由SO网友:Rarst 整理而成

这仍然在我的脑海中,所以我重新审视了它,并提出了这个解决方案,它不太依赖于上下文:

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;
}
用法
$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 );

SO网友:davidn

@黄金应用程序:Your Walker Class 不起作用。但是这个主意真的很好。我根据你的想法创造了一个助行器:

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;
    }
}
现在您可以使用:

<?php wp_nav_menu( 
   array(
       \'theme_location\'=>\'test\', 
       \'walker\'=>new Selective_Walker() ) 
   ); ?>
输出是一个包含当前根元素及其子元素(而不是其子元素)的列表。定义:根元素:=与当前页面相对应的顶级菜单项,或者是当前页面的父级或父级的父级。。。

这并不能完全回答最初的问题,但几乎可以,因为仍然有顶级项目。这对我来说很好,因为我希望顶层元素作为提要栏的标题。如果要消除此问题,可能必须重写display\\u元素或使用HTML解析器。

SO网友:MikeSchinkel

你好@jessegavin:

导航菜单存储在自定义帖子类型和自定义分类法的组合中。每个菜单都存储为一个术语(即“关于菜单”,可在中找到wp_terms) 自定义分类法(即。nav_menu, 在中找到wp_term_taxonomy.)

每个导航菜单项存储为post_type==\'nav_menu_item\' (即“关于公司”,见wp_posts) 其属性存储为post meta(在wp_postmeta) 使用meta_key 的前缀_menu_item_* 哪里_menu_item_menu_item_parent 是菜单项的父Nav菜单项post的ID。

菜单和菜单项之间的关系存储在wp_term_relationships 哪里object_id$post->ID 对于Nav菜单项和$term_relationships->term_taxonomy_id 与中共同定义的菜单相关wp_term_taxonomywp_terms.

我很确定这两种方法都可以\'wp_update_nav_menu\'\'wp_update_nav_menu_item\' 在中创建实际菜单的步骤wp_terms 和一组平行关系wp_term_taxonomywp_term_relationships 其中,每个具有子导航菜单项的导航菜单项也成为其自己的导航菜单。

您还需要钩住\'wp_get_nav_menus\' (基于几个月前我所做的一些类似工作,我建议将其添加到WP 3.0中)以确保生成的导航菜单不会显示给管理员中的用户进行操作,否则它们会很快失去同步,然后你会面临数据噩梦。

听起来是个有趣又有用的项目,但这比我现在能负担得起的代码和测试多了一点,部分原因是,任何同步数据的东西在消除所有bug方面都是一个难题(而且因为付费客户都在催促我完成任务)。但有了上述信息,我是一个非常有动力的WordPress插件开发人员,如果他们愿意的话,可以编写代码。

当然,您现在确实意识到,如果您编写代码,您有义务将其发回这里,这样我们都可以从您的慷慨中受益!:-)

SO网友:goldenapples

这是一个walker扩展,它应该满足您的需要:

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

}
大致基于我在前面的评论中引用的mfields代码。它所做的只是在遍历菜单时检查当前元素是(1)当前菜单项还是(2)当前菜单项的祖先,并且仅当其中一个条件为真时才展开其下方的子树。希望这对你有用。

要使用它,只需在调用菜单时添加一个“walker”参数,即:

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

SO网友:Marcus Downing

Update: 我把它做成了一个插件。Download here.

我需要自己解决这个问题,最终在菜单查找的结果上编写了一个过滤器。它允许您使用wp_nav_menu 正常情况下,但根据父元素的标题选择菜单的子部分。添加一个submenu 菜单中的参数,如下所示:

wp_nav_menu(array(
  \'menu\' => \'header\',
  \'submenu\' => \'About Us\',
));
您甚至可以通过在其中添加斜线来深入到多个级别:

wp_nav_menu(array(
  \'menu\' => \'header\',
  \'submenu\' => \'About Us/Board of Directors\'
));
或者,如果您喜欢使用阵列:

wp_nav_menu(array(
  \'menu\' => \'header\',
  \'submenu\' => array(\'About Us\', \'Board of Directors\')
));
它使用了一个鼻涕虫版本的标题,这应该可以让它原谅大写和标点符号之类的东西。

SO网友:Matt

我为自己安排了以下课程。它将找到当前页面的顶部导航父级,或者您可以在walker构造函数中为其指定一个目标顶部导航ID。

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;
    }
}
导航呼叫:

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

SO网友:Alp Güneysel

@davidn@hakreHi,我有一个丑陋的解决方案,没有HTML解析器或覆盖display\\u元素。

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

SO网友:Ján Bočínec

查看我的插件中的代码,或将其用于您的目的;)

此插件添加了增强的“导航菜单”小部件。它提供了许多选项,可以设置这些选项来通过小部件自定义自定义菜单的输出。

功能包括:

自定义层次结构-“仅相关子项”或“仅严格相关子项”

  • 仅显示当前元素的直接路径,或仅显示选定项的子项(包含父项的选项)

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

  • SO网友:Ethan C

    公认的答案承认它需要输入,而不是基于上下文。这是一个支持给定上下文的版本,或者可以使用WordPress的内置current-x 用于确定上下文的类:

    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;
    }
    
    和用法:

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

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

    结束

    相关推荐

    Menu API not switching menus?

    我正在使用菜单API,我想切换到其他菜单,但出于某种原因,它保留了第一个菜单这是我的密码在函数中。php add_action( \'init\', \'register_my_menus\',10 ); function register_my_menus() { register_nav_menu(\'main-navigation\', \'Main Navigation\'); } 下面是我的主题文件(header.ph