jessegavin
  • 0
Учитель

Отображение части/ветви дерева меню с помощью wp_nav_menu()

  • 0

У меня есть меню, определенное в WP Admin, которое выглядит так:

альтернативный текст

Я хочу иметь возможность отображать все дочерние ссылки на боковой панели всякий раз, когда я нахожусь на родительской странице. Например, если пользователь находится на моей странице «О нас», я хочу, чтобы на боковой панели отображался список из 4 ссылок, выделенных зеленым цветом.

Я просмотрел документацию для wp_nav_menu() и, похоже, не имеет встроенного способа указать конкретный узел данного меню для использования в качестве отправной точки при создании ссылок.

Я создал решение для аналогичной ситуации, основанное на отношениях, созданных родительской страницей, но я ищу решение, которое специально использует систему меню. Любая помощь будет оценена по достоинству.

Share
  1. Итак, вы хотите сохранить все меню как пользовательское меню, но создать собственный обходчик, который отображает его, расширяя только активное поддерево? Нравится этот код, но расширение wp_nav_menu вместо wp_list_pages? Недавно я сделал что-то подобное и мог бы опубликовать код, если это то, что вы ищете…

    • 0
  2. Это все еще было у меня на уме, поэтому я пересмотрел его и собрал это решение, которое не так сильно зависит от контекста:

    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 );
    
    • 0
  3. @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' );  //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() ) 
       ); ?>
    

    Результатом является список, содержащий текущий корневой элемент и его дочерние элементы (не их дочерние элементы). Def: Корневой элемент := Пункт меню верхнего уровня, который соответствует текущей странице или является родителем текущей страницы или родителем родителя…

    Это не совсем отвечает на исходный вопрос, но почти, поскольку есть еще элемент верхнего уровня. Это нормально для меня, потому что я хочу, чтобы элемент верхнего уровня был заголовком боковой панели. Если вы хотите избавиться от этого, вам, возможно, придется переопределить display_element или использовать HTML-парсер.

    • 0
  4. Привет @jessegavin :

    Навигационные меню хранятся в виде комбинации настраиваемых типов сообщений и настраиваемых таксономий. Каждое меню хранится как термин (т. е. «О меню», находится в wp_terms ) пользовательской таксономии (т. е. nav_menu находится в wp_term_taxonomy .)

    Каждый пункт навигационного меню хранится в виде поста post_type=='nav_menu_item' (например, «О фирме», найденного в wp_posts ) с его атрибутами, хранящимися в виде мета поста (в wp_postmeta ) с использованием meta_key префикса, _menu_item_* где _menu_item_menu_item_parent находится идентификатор родительского пункта навигационного меню вашего пункта меню.

    Отношения между меню и пунктами меню хранятся в разделе, wp_term_relationships где object_id относится к $post->ID элементу меню навигации, а $term_relationships->term_taxonomy_id относится к меню, определенному совместно в wp_term_taxonomy и wp_terms .

    Я почти уверен, что можно было бы подключить оба 'wp_update_nav_menu' и 'wp_update_nav_menu_item' создать фактические меню wp_terms и параллельный набор отношений, wp_term_taxonomy и wp_term_relationships где каждый пункт навигационного меню, который имеет подпункты навигационного меню, также становится собственным навигационным меню.

    Вы также хотели бы подключиться 'wp_get_nav_menus' (который я предложил добавить в WP 3.0 на основе аналогичной работы, которую я делал несколько месяцев назад), чтобы убедиться, что ваши сгенерированные навигационные меню не отображаются для манипуляции пользователем в админке, иначе они очень быстро выйдет из синхронизации, и тогда у вас будет кошмар с данными.

    Звучит как забавный и полезный проект, но это немного больше кода и тестирования, чем я могу себе позволить прямо сейчас, отчасти потому, что все, что синхронизирует данные, имеет тенденцию быть PITA, когда дело доходит до устранения всех ошибок (и потому что платные клиенты требуют от меня, чтобы я что-то сделал 🙂 Но, вооружившись приведенной выше информацией, я довольно мотивированный разработчик плагинов для WordPress и мог бы закодировать его, если бы захотел.

    Конечно, теперь вы понимаете, что если вы делаете код, вы обязаны опубликовать его здесь, чтобы мы все могли извлечь выгоду из вашей щедрости! 🙂

    • 0
  5. Это расширение ходунка, которое должно делать то, что вы ищете:

    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() ) 
       ); ?>
    
    • 0
  6. Обновление: я превратил это в плагин. Скачать здесь.


    Мне нужно было решить эту проблему самостоятельно, и в итоге я написал фильтр для результатов поиска в меню. Он позволяет использовать его 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')
    ));
    

    Он использует сокращенную версию названия, что должно снисходительно относиться к таким вещам, как заглавные буквы и знаки препинания.

    • 0
  7. Я собрал следующий класс для себя. Он найдет верхний родительский элемент навигации текущей страницы, или вы можете указать ему целевой верхний идентификатор навигации в конструкторе обходчика.

    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
    ));
    
    • 0
  8. @davidn @hakre Привет, у меня есть уродливое решение без HTML-парсера или переопределения 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;
            }
        }
    
    • 0
  9. Вывод навигационного меню включает в себя множество классов для текущего элемента, предка текущего элемента и т. д. В некоторых ситуациях мне удавалось делать то, что вы хотите, позволяя выводить все дерево навигации, а затем используя css, чтобы сократить его до только дети текущей страницы и т.д.

    • 0
  10. Сделал модифицированный ходок, должно помочь! Не идеально — оставляет несколько пустых элементов, но помогает. Модификация в основном состоит из битов $current_branch. Надеюсь, это поможет кому-то!

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

    }

    • 0

Оставить ответ

You must login to add an answer.