Mostrar una porción / rama del árbol de menú usando wp_nav_menu ()

107

Tengo un menú definido en WP Admin que tiene este aspecto:

Quieropodermostrartodoslosenlacessecundariosenlabarralateralcuandoestoyenunapáginaprincipal.Porejemplo,sielusuarioestáenmipágina"Acerca de nosotros", quiero que aparezca una lista de los 4 enlaces resaltados en verde en la barra lateral.

Miré la documentación de wp_nav_menu () y parece que no tiene ninguna forma integrada de especifique un nodo particular de un menú determinado para usar como punto de partida al generar los enlaces.

Creé una solución para una situación similar que se basó en las relaciones creadas por el padre de la página, pero estoy buscando una que use el sistema de menús específicamente. Cualquier ayuda sería apreciada.

    
pregunta jessegavin 11.10.2010 - 23:50

10 respuestas

72

Esto aún estaba en mi mente, así que lo volví a visitar y armé esta solución, que no depende mucho del 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 );
    
respondido por el Rarst 12.10.2010 - 11:00
14

@goldenapples: Su clase Walker no funciona. Pero la idea es realmente buena. Creé un caminante basado en tu 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;
    }
}

Ahora puedes usar:

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

La salida es una lista que contiene el elemento raíz actual y es hijo (no sus hijos). Def: Elemento raíz: = El elemento de menú de nivel superior que corresponde a la página actual o es padre de una página actual o padre de un padre ...

Esto no responde exactamente a la pregunta original, pero casi, ya que todavía existe el elemento de nivel superior. Esto está bien para mí, porque quiero que el elemento de nivel superior sea un título de la barra lateral. Si desea deshacerse de esto, es posible que deba anular display_element o usar un analizador de HTML.

    
respondido por el davidn 07.02.2011 - 15:15
12

Hola @jessegavin :

Los menús de navegación se almacenan en una combinación de tipos de correos personalizados y taxonomías personalizadas. Cada menú se almacena como un término (es decir, "Acerca del menú" , que se encuentra en wp_terms ) de una taxonomía personalizada (es decir, nav_menu , que se encuentra en wp_term_taxonomy .)

Cada elemento del menú de navegación se almacena como una publicación de post_type=='nav_menu_item' (es decir, "Acerca de la empresa" , que se encuentra en wp_posts ) con sus atributos almacenados como publicación meta (en wp_postmeta ) usando un prefijo meta_key de _menu_item_* donde _menu_item_menu_item_parent es el ID de la publicación del elemento del menú de navegación del elemento del menú.

La relación entre los menús y los elementos de menú se almacena en wp_term_relationships donde object_id se relaciona con el $post->ID para el elemento de menú de navegación y el $term_relationships->term_taxonomy_id se relaciona con el menú definido colectivamente en wp_term_taxonomy y wp_terms .

Estoy bastante seguro de que sería posible enlazar tanto 'wp_update_nav_menu' como 'wp_update_nav_menu_item' para crear menús reales en wp_terms y un conjunto paralelo de relaciones en wp_term_taxonomy y wp_term_relationships donde cada elemento del menú de navegación que tiene elementos de menú de sub-navegación también se convierte en su propio menú de navegación.

También querría enganchar 'wp_get_nav_menus' (que sugerí que se agregue a WP 3.0 en base a un trabajo similar que estaba haciendo hace unos meses) para asegúrese de que los menús de navegación generados no se muestren para que los manipule el usuario en el administrador; de lo contrario, se desincronizarán muy rápido y luego tendrá una pesadilla de datos en su mano.

Suena como un proyecto divertido y útil, pero es un poco más de código y pruebas de lo que puedo afrontar ahora, en parte porque cualquier cosa que sincronice los datos tiende a ser un PITA cuando se trata de solucionar todos los errores. (y porque los clientes que me pagan me están presionando para que haga las cosas. :) Pero armado con la información anterior, soy un motivado desarrollador de plugins de WordPress que podría codificarlo si lo desearan.

Por supuesto que ahora se da cuenta de que si lo codifica, está obligado a publicarlo aquí para que todos podamos beneficiarnos de su generosidad. :-)

    
respondido por el MikeSchinkel 12.10.2010 - 09:50
10

Esta es una extensión de walker que debería hacer lo que estás buscando:

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

}

Basado libremente en el código de mfields al que hice referencia en mi comentario anterior. Todo lo que hace es verificar al recorrer el menú para ver si el elemento actual es (1) el elemento del menú actual, o (2) un antepasado del elemento del menú actual, y expande el subárbol debajo si solo se cumple alguna de estas condiciones. . Espero que esto funcione para ti.

Para usarlo, simplemente agregue un argumento de "caminante" cuando llame al menú, es decir:

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

Actualización: hice esto en un complemento. Descargue aquí .

Necesitaba resolver esto yo mismo y, finalmente, terminé escribiendo un filtro sobre los resultados de la búsqueda en el menú. Le permite usar wp_nav_menu como de costumbre, pero elija una subsección del menú basada en el título del elemento principal. Agregue un parámetro submenu al menú así:

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

Incluso puedes profundizar en varios niveles poniendo barras diagonales en:

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

O si prefieres con una matriz:

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

Utiliza una versión babosa del título, que debería hacer que perdone cosas como mayúsculas y puntuación.

    
respondido por el Marcus Downing 21.04.2011 - 13:41
8

Yo armé la siguiente clase para mí. Encontrará el elemento superior de navegación superior de la página actual, o puede asignarle un ID de navegación superior de destino en el constructor de 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;
    }
}

Llamada de navegación:

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

@davidn @hakre Hola, tengo una solución fea sin un HTML-Parser o un reemplazo de 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;
        }
    }
    
respondido por el Alp Güneysel 15.03.2011 - 19:07
3

La salida del menú de navegación incluye muchas clases para el elemento actual, el antepasado del elemento actual, etc. En algunas situaciones, he podido hacer lo que usted desea hacer al permitir que salga todo el árbol de navegación y luego usar css para pare Se reduce solo a los hijos de la página actual, etc.

    
respondido por el user3017 08.02.2011 - 22:39
3

¡Hice un andador modificado que debería ayudar! No es perfecto: deja algunos elementos vacíos, pero cumple su cometido. La modificación es básicamente esos $ bits de current.branch. Espero que ayude a alguien!

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

}

    
respondido por el user2735 20.04.2011 - 20:46
3

Verifique el código en mi complemento o utilícelo para su propósito;)

Este complemento agrega un widget mejorado del "Menú de navegación". Ofrece muchas opciones que podrían configurarse para personalizar la salida del menú personalizado a través del widget.

Las características incluyen:

  • Jerarquía personalizada: "Solo subelementos relacionados" o "Solo estrictamente relacionado subelementos ".
  • Profundidad inicial y nivel máximo para mostrar + pantalla plana.
  • Muestra todos los elementos del menú que comienzan con el seleccionado.
  • Mostrar solo la ruta directa al elemento actual o solo los hijos de
    elemento seleccionado (opción para incluir el elemento principal).
  • Clase personalizada para un bloque de widgets.
  • Y casi todos los parámetros para la función wp_nav_menu.

enlace

    
respondido por el Ján Bočínec 10.01.2012 - 01:59

Lea otras preguntas en las etiquetas