it-swarm.xyz

Отобразить часть/ветвь дерева меню, используя wp_nav_menu ()

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

alt text

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

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

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

109
jessegavin

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

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

@goldenapples: Ваш класс Уокера не работает. Но идея действительно хорошая. Я создал ходок на основе вашей идеи:

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: Root element: = Элемент меню верхнего уровня, который соответствует текущей странице или является родительским для текущей страницы или родительским для родительского элемента ...

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

14
davidn

Привет @jessegavin :

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

Каждый элемент меню Nav хранится в виде сообщения post_type=='nav_menu_item' (т. Е. "О фирме", найденного в wp_posts), а его атрибуты хранятся в виде пост-мета (в wp_postmeta) с использованием префикса meta_key_menu_item_*, где _menu_item_menu_item_parent - это Идентификатор родительского элемента меню Nav. Сообщение пункта меню.

Связь между меню и элементами меню хранится в wp_term_relationships, где object_id относится к $post->ID для элемента меню Nav, а $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, где каждый элемент меню Nav, который имеет элементы меню под-Nav, также становится его собственным меню Nav.

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

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

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

12
MikeSchinkel

Это расширение 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) предком текущего элемента меню, и расширяет поддерево под ним, только если выполняется одно из этих условий , Надеюсь, что это работает для вас.

Чтобы использовать его, просто добавьте аргумент "ходок" при вызове меню, то есть:

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

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

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

Обновление: Я превратил это в плагин. Скачать здесь .


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

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

8
Marcus Downing

@ 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;
        }
    }
4
Alp Güneysel

Проверьте код в моем плагине или используйте его для своих целей;)

Этот плагин добавляет улучшенный виджет "Меню навигации". Он предлагает множество опций, которые можно настроить для настройки вывода пользовательского меню через виджет.

Особенности включают в себя:

  • Пользовательская иерархия - "Только связанные подпункты" или "Только строго связанные подпункты".
  • Начальная глубина и максимальный уровень для отображения + плоский дисплей.
  • Показать все пункты меню, начиная с выбранного.
  • Отображать только прямой путь к текущему элементу или только потомкам
    выбранный элемент (возможность включить родительский элемент).
  • Пользовательский класс для блока виджетов.
  • И почти все параметры для функции wp_nav_menu.

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

3
Ján Bočínec

Я сделал модифицированный ходок, который должен помочь! Не идеально - он оставляет несколько пустых элементов, но делает свое дело. Модификация - это в основном те биты $ 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;
}

}

3
user2735

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

3
user3017