Exibe uma porção / ramificação da árvore de menu usando wp_nav_menu ()

107

Eu tenho um menu definido no WP Admin que se parece com isso:

Euqueropoderexibirtodososlinksfilhonabarralateralsemprequeeuestiveremumapáginapai.Porexemplo,seousuárioestivernapágina"Sobre nós", quero que uma lista dos quatro links destacados em verde apareça na barra lateral.

Eu examinei a documentação do wp_nav_menu () e ele não parece ter nenhuma maneira interna de especifique um nó específico de um determinado menu para usar como ponto de partida ao gerar os links.

Eu criei uma solução para uma situação semelhante que dependia dos relacionamentos criados pelo pai da página, mas estou procurando um que use o sistema de menus especificamente. Qualquer ajuda seria apreciada.

    
por jessegavin 11.10.2010 / 23:50
fonte

10 respostas

72

Isso ainda estava em minha mente, então eu revisitei e reuni essa solução, que não depende muito do contexto:

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

Uso

$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 );
    
por Rarst 12.10.2010 / 11:00
fonte
14

@goldenapples: Sua Classe Walker não funciona. Mas a ideia é muito boa. Eu criei um walker com base na sua ideia:

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

Agora você pode usar:

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

A saída é uma lista contendo o elemento raiz atual e seus filhos (não seus filhos). Def: Elemento raiz: = O item de menu de nível superior que corresponde à página atual ou é pai de uma página atual ou um pai de um pai ...

Isso não responde exatamente à pergunta original, mas quase, já que ainda há o item de nível superior. Isso é bom para mim, porque eu quero o elemento de nível superior como um título da barra lateral. Se você quiser se livrar disso, talvez seja necessário substituir o elemento display_element ou usar um analisador de HTML.

    
por davidn 07.02.2011 / 15:15
fonte
12

Oi @jessegavin :

Nav Menus são armazenados em uma combinação de tipos de postagem personalizados e taxonomias personalizadas. Cada menu é armazenado como um Termo (ou seja, "Sobre o Menu" , encontrado em wp_terms ) de uma Taxonomia Personalizada (ou seja, nav_menu , encontrado em wp_term_taxonomy .)

Cada item do menu Nav é armazenado como um post de post_type=='nav_menu_item' (ou seja, "Sobre a Firma" , encontrado em wp_posts ) com seus atributos armazenados como pós meta (em wp_postmeta ) usando o prefixo meta_key de _menu_item_* , em que _menu_item_menu_item_parent é o ID da postagem do item do menu Nav do item de menu.

O relacionamento entre menus e itens de menu é armazenado em wp_term_relationships , em que object_id está relacionado ao $post->ID do item de menu Nav e $term_relationships->term_taxonomy_id refere-se ao menu definido coletivamente em wp_term_taxonomy e wp_terms .

Tenho certeza de que seria possível ligar 'wp_update_nav_menu' e 'wp_update_nav_menu_item' para criar menus reais em wp_terms e um conjunto paralelo de relações em wp_term_taxonomy e wp_term_relationships onde cada item do menu Nav que possui itens do menu sub-Nav também se torna seu próprio menu Nav.

Você também gostaria de ligar 'wp_get_nav_menus' (que eu sugeri que fosse adicionado ao WP 3.0 com base em algum trabalho semelhante que eu estava fazendo há alguns meses atrás) Certifique-se de que seus menus Nav gerados não sejam exibidos para serem manipulados pelo usuário no admin, senão eles sairão de sincronia muito rápido e você terá um pesadelo de dados em mãos.

Parece um projeto divertido e útil, mas é um pouco mais de código e testes do que eu posso enfrentar agora, em parte porque qualquer coisa que sincronize dados tende a ser um PITA quando se trata de eliminar todos os erros (e porque os clientes pagantes estão me pressionando para fazer as coisas. :) Mas armado com as informações acima, eu sou um motivador desenvolvedor de plugins WordPress poderia codificá-lo se quisessem.

Claro que agora você percebe que, se codificá-lo, é obrigado a publicá-lo aqui para que todos possamos nos beneficiar de sua generosidade! : -)

    
por MikeSchinkel 12.10.2010 / 09:50
fonte
10

Esta é uma extensão de walker que deve fazer o que você está procurando:

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

}

Baseado vagamente no código do mfields, referenciado no meu comentário anterior. Tudo o que faz é verificar ao percorrer o menu para ver se o elemento atual é (1) o item de menu atual ou (2) um ancestral do item de menu atual e expande a subárvore abaixo dela somente se qualquer uma dessas condições for verdadeira . Espero que isso funcione para você.

Para usá-lo, basta adicionar um argumento "walker" quando você chamar o menu, ou seja:

<?php wp_nav_menu( 
   array(
       'theme_location'=>'test', 
       'walker'=>new Selective_Walker() ) 
   ); ?>
    
por goldenapples 15.10.2010 / 22:06
fonte
8

Atualização: fiz isso em um plug-in. Faça o download aqui .

Eu mesmo precisei resolver isso e acabei escrevendo um filtro nos resultados da pesquisa de menu. Ele permite que você use wp_nav_menu como normal, mas escolha uma subseção do menu com base no título do elemento pai. Adicione um parâmetro submenu ao menu da seguinte forma:

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

Você pode até mesmo aprofundar vários níveis ao inserir barras:

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

Ou se preferir com uma matriz:

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

Ele usa uma versão do título, que deve perdoar coisas como maiúsculas e pontuação.

    
por Marcus Downing 21.04.2011 / 13:41
fonte
8

Eu coloquei a seguinte aula para mim. Ele localizará o principal pai de navegação da página atual ou você poderá fornecer um ID de navegação superior ao alvo no construtor walker.

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

Chamada de navegação:

wp_nav_menu(array(
    'theme_location' => 'main_menu',
    'walker' => new Walker_SubNav_Menu(22), // with ID
));
    
por Matt 01.02.2012 / 00:19
fonte
4

@davidn @hakre Oi, eu tenho uma solução feia sem um HTML-Parser ou substituindo 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;
        }
    }
    
por Alp Güneysel 15.03.2011 / 19:07
fonte
3

A saída do menu de navegação inclui muitas classes para o item atual, o ancestral do item atual, etc. Em algumas situações, eu pude fazer o que você quer fazer deixando toda a saída da árvore de navegação, e então usando css para pare até os filhos da página atual, etc.

    
por user3017 08.02.2011 / 22:39
fonte
3

Eu fiz um andador modificado que deveria ajudar! Não é perfeito - deixa alguns elementos vazios, mas faz o truque. A modificação é basicamente os bits $ current_branch. Espero que ajude alguém!

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

}

    
por user2735 20.04.2011 / 20:46
fonte
3

Confira o código no meu plugin ou use-o para o seu propósito;)

Este plug-in adiciona o widget "Menu de Navegação" aprimorado. Ele oferece muitas opções que podem ser configuradas para personalizar a saída do menu personalizado por meio do widget.

Os recursos incluem:

  • Hierarquia personalizada - "Somente subitens relacionados" ou "Apenas estritamente relacionados sub-itens ".
  • Início da profundidade e nível máximo para exibir + exibição simples.
  • Exibir todos os itens de menu que começam com o item selecionado.
  • Exibir apenas caminho direto para o elemento atual ou somente filhos do
    item selecionado (opção para incluir o item pai).
  • Classe personalizada para um bloco de widget.
  • E quase todos os parâmetros para a função wp_nav_menu.

enlace

    
por Ján Bočínec 10.01.2012 / 01:59
fonte