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

12 oct 2010, 00:50:29
Vistas: 48.3K
Votos: 113

Tengo un menú definido en el WP Admin que se ve así:

Estructura jerárquica del menú de WordPress mostrando enlaces padre e hijos

Quiero poder mostrar todos los enlaces secundarios en la barra lateral cuando estoy en una página principal. Por ejemplo, si el usuario está en mi página "Sobre Nosotros", quiero que aparezca una lista de los 4 enlaces resaltados en verde en la barra lateral.

Revisé la documentación de wp_nav_menu() y parece que no tiene ninguna forma incorporada de especificar 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 basaba en las relaciones creadas por la página padre, pero estoy buscando una que utilice específicamente el sistema de menús. Cualquier ayuda sería apreciada.

4
Comentarios

¿Así que quieres mantener todo el menú como un menú personalizado, pero crear un walker personalizado que lo muestre expandiendo solo el subárbol activo? Como este código, pero extendiendo wp_nav_menu en lugar de wp_list_pages? Recientemente hice algo similar y podría publicar el código si es lo que estás buscando...

goldenapples goldenapples
12 oct 2010 04:51:36

@goldenapples, eso es exactamente lo que necesito. Si no te importa publicar tu código como respuesta, estaría muy agradecido.

jessegavin jessegavin
12 oct 2010 17:26:41

Me sorprende que una funcionalidad tan obviamente útil no esté ya incorporada. Esto es en general muy útil para cualquier sitio que funcione como "CMS".

hakre hakre
9 feb 2011 17:54:53

Estoy intentando resolver el problema mencionado o algo similar. Como alternativa, se me ocurrió una solución CSS aquí: http://stackoverflow.com/q/7640837/518169

hyperknot hyperknot
4 oct 2011 02:52:14
Todas las respuestas a la pregunta 11
16
78

Esto aún estaba en mi mente, así que lo revisité y armé esta solución, que no depende tanto 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-del-menu', // el mismo usado en register_nav_menus
    'submenu' => 'Sobre Nosotros', // podría usarse __() para traducciones
);

wp_nav_menu( $args );
12 oct 2010 12:00:11
Comentarios

¡Encantadora técnica! ¿Puedo preguntar algo posiblemente relacionado con esta: Cómo mostrarías el contenido de esas páginas de submenú listadas en la plantilla?

daniel.tosaba daniel.tosaba
22 ago 2012 08:09:15

@daniel.tosaba necesitarás subclasificar o usar filtros en la clase Walker_Nav_Menu. Como todo lo relacionado con menús, es demasiado para un comentario - ¿por qué no haces una nueva pregunta al respecto?

Rarst Rarst
22 ago 2012 14:18:19

Una respuesta fantástica. Muchas gracias. Esto realmente debería ser una opción predeterminada en WordPress.

dotty dotty
11 sept 2012 14:19:34

Hmm, en realidad tengo un problema extraño. Tengo una página llamada "Children's". Y parece que el apóstrofe en la palabra impide encontrar la página. ¿Alguna idea?

dotty dotty
11 sept 2012 19:04:34

@dotty No recuerdo exactamente qué se almacena en el campo por el que filtra el código. Podría ser una versión saneada o con barras de escape del título.

Rarst Rarst
11 sept 2012 19:33:12

Lo siento, no estoy seguro de lo que quieres decir. ¿Podrías aclararlo por favor?

dotty dotty
13 sept 2012 14:08:50

@dotty podría estar almacenado como Children\'s o algo así, no tengo este código configurado para probar en este momento...

Rarst Rarst
13 sept 2012 14:12:35

Parece que esto ya no funciona en WP 4. Recibo este error: Strict Standards: Only variables should be passed by reference in -> on line -> $parent_id = array_pop( wp_filter_object_list( $items, array( 'title' => $args->submenu ), 'and', 'ID' ) );

gdaniel gdaniel
5 sept 2014 18:13:58

@gdaniel probó: sí funciona y no tiene nada que ver con la actualización de WP, solo necesitaba un pequeño ajuste para que fuera un código PHP más adecuado :)

Rarst Rarst
5 sept 2014 18:26:46

Extraño. Todos mis submenús que usaban esa función desaparecieron justo después de actualizar a WP 4. Gracias por echarle un vistazo de todos modos. Verificaré qué más pudo haber ocurrido.

gdaniel gdaniel
5 sept 2014 18:36:02

Descubrí mi problema. Estaba usando el argumento menu => "nombre-del-menú" en wp_nav_menu... Eliminé 'menu' y agregué theme_location, y todo volvió a la normalidad.

gdaniel gdaniel
5 sept 2014 19:25:04

¡Fantástica solución, @Rarst! Como siempre.

Eric Holmes Eric Holmes
24 feb 2015 17:03:30

Hola @Rarst, todavía apareces en primer lugar en muchos sitios de esta respuesta. La versión aceptada debería reescribirse para tener en cuenta los navegadores personalizados, ya que uno podría simplemente filtrar por "current-menu-item", "current-menu-parent" y "current-menu-ancestor" y luego renderizar el submenú. Sin filtros, sin trucos.

Imperative Ideas Imperative Ideas
10 mar 2015 03:26:00

@ImperativeIdeas Mi respuesta muestra una rama arbitraria por nombre, no solo la actual.

Rarst Rarst
10 mar 2015 08:47:20

Muy interesante. Si a alguien le interesa, para hacer lo mismo pero por ID de página, cambia la línea wp_filter_object_list por wp_filter_object_list( $items, array( 'object_id' => $args->submenu ), 'and', 'ID' );

Ben Ben
30 abr 2015 13:25:17
Mostrar los 11 comentarios restantes
0
14

@goldenapples: Tu Clase Walker no funciona. Pero la idea es realmente buena. Creé un walker 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) //parámetro inválido
            return $output;

        if (empty($elements)) //nada que recorrer
            return $output;

        $id_field = $this->db_fields['id'];
        $parent_field = $this->db_fields['parent'];

        // visualización plana
        if ( -1 == $max_depth ) {
            $empty_array = array();
            foreach ( $elements as $e )
                $this->display_element( $e, $empty_array, 1, 0, $args, $output );
            return $output;
        }

        /*
         * necesita mostrarse en orden jerárquico
         * separa elementos en dos grupos: elementos de nivel superior y elementos hijos
         * children_elements es un array bidimensional, ej.
         * children_elements[10][] contiene todos los sub-elementos cuyo padre es 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;
        }

        /*
         * cuando ninguno de los elementos es de nivel superior
         * asume que el primero debe ser la raíz de los sub-elementos
         */
        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' );  //añadido por continent7
        foreach ( $top_level_elements as $e ){  //modificado por continent7
            // desciende solo en el árbol actual
            $descend_test = array_intersect( $current_element_markers, $e->classes );
            if ( !empty( $descend_test ) ) 
                $this->display_element( $e, $children_elements, 2, 0, $args, $output );
        }

        /*
         * si estamos mostrando todos los niveles, y children_elements no está vacío,
         * entonces tenemos huérfanos, que deberían mostrarse de todos modos
         */
         /* eliminado por 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() ) 
   ); ?>

El resultado es una lista que contiene el elemento raíz actual y sus hijos (no los hijos de estos). 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 está el elemento de nivel superior. Esto está bien para mí, porque quiero el elemento de nivel superior como título de la barra lateral. Si quieres deshacerte de esto, podrías tener que sobrescribir display_element o usar un analizador HTML.

7 feb 2011 16:15:28
3
13

Hola @jessegavin:

Los menús de navegación se almacenan en una combinación de tipos de entradas personalizadas y taxonomías personalizadas. Cada menú se guarda como un Término (ej. "Menú Acerca de", ubicado en wp_terms) de una Taxonomía Personalizada (ej. nav_menu, ubicada en wp_term_taxonomy).

Cada elemento del menú de navegación se almacena como una entrada con post_type=='nav_menu_item' (ej. "Acerca de la Empresa", ubicado en wp_posts) con sus atributos guardados como metadatos (en wp_postmeta) usando un prefijo meta_key de _menu_item_* donde _menu_item_menu_item_parent es el ID de la entrada padre del elemento de menú.

La relación entre menús y elementos de menú se guarda en wp_term_relationships donde object_id se relaciona con $post->ID del elemento de menú y $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 enganchar 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 de menú que tenga sub-elementos se convierta también en su propio menú de navegación.

También querrías enganchar 'wp_get_nav_menus' (que sugerí que se añadiera a WP 3.0 basándome en un trabajo similar que hice hace unos meses) para asegurarte de que los menús generados no se muestren para su manipulación por el usuario en el admin, de lo contrario se desincronizarían muy rápido y tendrías una pesadilla de datos.

Suena como un proyecto divertido y útil, pero requiere un poco más de código y pruebas de las que puedo permitirme ahora, en parte porque cualquier cosa que sincronice datos tiende a ser un dolor de cabeza al depurar todos los errores (y porque los clientes que pagan me presionan para que termine otros trabajos. :) Pero con la información anterior, estoy seguro de que un desarrollador motivado de plugins para WordPress podría codificarlo si lo deseara.

¡Por supuesto, ahora que si lo codificas, estás obligado a publicarlo aquí para que todos podamos beneficiarnos de tu generosidad! :-)

12 oct 2010 10:50:41
Comentarios

No estoy seguro de estar siguiendo lo que dices. Estoy buscando una solución de solo lectura para mostrar "submenús" relacionados con la página actual en la que se encuentra un usuario. ¿Estamos hablando de lo mismo? - Aprecio mucho tu explicación detallada sobre el esquema de la base de datos.

jessegavin jessegavin
14 oct 2010 04:24:47

@jessegavin - Sí, si quieres usar wp_nav_menu() entonces necesitarás clonar los menús porque wp_nav_menu() está estrechamente acoplado a la estructura del menú. La otra opción es copiar el código de wp_nav_menu() y hacer las modificaciones necesarias para mostrarlo como un submenú.

MikeSchinkel MikeSchinkel
14 oct 2010 05:34:14

¡Esta es la respuesta que he estado buscando todo el día, muchísimas gracias!

Chris Haas Chris Haas
13 dic 2019 22:39:44
4
10

Esta es una extensión del 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) //parámetro inválido
            return $output;

        if (empty($elements)) //nada que recorrer
            return $output;

        $id_field = $this->db_fields['id'];
        $parent_field = $this->db_fields['parent'];

        // visualización plana
        if ( -1 == $max_depth ) {
            $empty_array = array();
            foreach ( $elements as $e )
                $this->display_element( $e, $empty_array, 1, 0, $args, $output );
            return $output;
        }

        /*
         * necesita mostrar en orden jerárquico
         * separa elementos en dos grupos: nivel superior y elementos hijos
         * children_elements es un array bidimensional, ej.
         * children_elements[10][] contiene todos los sub-elementos cuyo padre es 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;
        }

        /*
         * cuando ninguno de los elementos es de nivel superior
         * asume que el primero debe ser la raíz de los sub-elementos
         */
        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 ) {

            // desciende solo en el árbol actual
            $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 );
        }

        /*
         * si estamos mostrando todos los niveles, y children_elements restante no está vacío,
         * entonces tenemos huérfanos, que deben mostrarse de todos modos
         */
        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 que mencioné en mi comentario anterior. Todo lo que hace es verificar al recorrer el menú si el elemento actual es (1) el elemento de menú actual, o (2) un ancestro del elemento de menú actual, y expande el subárbol debajo de él solo si alguna de esas condiciones es verdadera. Espero que te funcione.

Para usarlo, simplemente agrega un argumento "walker" cuando llames al menú, por ejemplo:

<?php wp_nav_menu( 
   array(
       'theme_location'=>'test', 
       'walker'=>new Selective_Walker() ) 
   ); ?>
15 oct 2010 23:06:30
Comentarios

Oh... Acabo de releer tu pregunta y me di cuenta de que la había entendido mal al principio. Este walker mostrará todos los demás elementos del menú de primer nivel, pero no los expandirá. Esto no era exactamente lo que querías hacer. Aún así, este código se puede modificar como desees. Solo revisa el bucle a través de $top_level_elements y añade tu propia prueba antes de la llamada a $this->display_element.

goldenapples goldenapples
16 oct 2010 22:46:34

¿Es posible hacer que esta clase muestre la profundidad de la subpágina actual? Es decir... Si tengo una profundidad de tres o más niveles, ¿que se muestren el tercer nivel y los siguientes para la (sub)página actual? En este momento, solo muestra A > B, pero no > C (siendo C el tercer nivel)

Zolomon Zolomon
20 feb 2011 22:10:29

@Zolomon - No estoy seguro de entender tu pregunta. Esto debería expandir todo el árbol bajo cualquier elemento del menú con las clases 'current-menu-item', 'current-menu-parent' o 'current-menu-ancestor'. Cuando lo pruebo, muestra todos los niveles de subpáginas en el menú. ¿Qué es lo que estás intentando hacer?

goldenapples goldenapples
21 feb 2011 22:39:20

Quizás quieras pasar un parámetro depth a la llamada de wp_nav_menu, en caso de que tu tema esté anulando el valor por defecto de 0 (mostrar todos los niveles)?

goldenapples goldenapples
21 feb 2011 22:41:09
3

Actualización: He convertido esto en un plugin. Descárgalo aquí.


Necesitaba resolver esto por mí mismo y terminé escribiendo un filtro sobre los resultados de la búsqueda del menú. Te permite usar wp_nav_menu normalmente, pero seleccionar una sub-sección del menú basada en el título del elemento padre. Agrega un parámetro submenu al menú de la siguiente manera:

wp_nav_menu(array(
  'menu' => 'header',
  'submenu' => 'Sobre Nosotros',
));

Incluso puedes ir varios niveles de profundidad usando barras:

wp_nav_menu(array(
  'menu' => 'header',
  'submenu' => 'Sobre Nosotros/Consejo Directivo'
));

O si prefieres con un array:

wp_nav_menu(array(
  'menu' => 'header',
  'submenu' => array('Sobre Nosotros', 'Consejo Directivo')
));

Utiliza una versión en slug del título, lo que lo hace tolerante a cosas como mayúsculas y puntuación.

21 abr 2011 14:41:12
Comentarios

¿Es posible acceder a un submenú mediante un id? Me refiero al id de página o al id de entrada.

Digerkam Digerkam
28 feb 2013 18:28:45

split() está obsoleto, reemplaza $loc = split( "/", $loc ); en el plugin con $loc = preg_split( "~/~", $loc );

Floris Floris
4 sept 2018 13:29:51

También sugeriría hacer $submenu opcional. Así aún puedes obtener el menú completo cuando sea necesario. Añade esto al filtro al principio: if ( ! isset( $args->submenu ) ) { return $items; }

Floris Floris
4 sept 2018 13:43:48
0

Armé la siguiente clase para mí mismo. Encontrará el elemento principal de navegación superior de la página actual, o puedes proporcionarle un ID de navegación superior objetivo en el constructor del 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();

        // si el padre no está establecido, configúralo basado en el post
        if (!$target_id) {
            global $post;
            foreach ($items as $item) {
                if ($item->object_id == $post->ID) {
                    $target_id = $item->ID;
                }
            }
        }

        // si no hay un padre, muestra un menú regular
        if (!$target_id) return parent::walk($items, $depth, $args);

        // obtén el elemento de navegación superior
        $target_id = $this->top_level_id($items, $target_id);

        // solo incluye elementos bajo el padre
        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);
    }

    // obtiene el ID de nivel superior para un ID de elemento
    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;
            }
        }

        // encuentra el elemento de nivel superior
        while (array_key_exists($item_id, $parents)) {
            $item_id = $parents[$item_id];
        }

        return $item_id;
    }
}

Llamada al menú de navegación:

wp_nav_menu(array(
    'theme_location' => 'main_menu',
    'walker' => new Walker_SubNav_Menu(22), // con ID
));
1 feb 2012 01:19:49
1

@davidn @hakre Hola, tengo una solución poco elegante sin un analizador HTML o sobrescribir 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) //parámetro inválido
                return $output;

            if (empty($elements)) //nada que recorrer
                return $output;

            $id_field = $this->db_fields['id'];
            $parent_field = $this->db_fields['parent'];

            // visualización plana
            if ( -1 == $max_depth ) {
                $empty_array = array();
                foreach ( $elements as $e )
                    $this->display_element( $e, $empty_array, 1, 0, $args, $output );
                return $output;
            }

            /*
             * necesita mostrarse en orden jerárquico
             * separar elementos en dos grupos: nivel superior y elementos hijos
             * children_elements es un array bidimensional, ej.
             * children_elements[10][] contiene todos los sub-elementos cuyo padre es 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;
            }

            /*
             * cuando ninguno de los elementos es de nivel superior
             * asumir que el primero debe ser la raíz de los sub-elementos
             */
            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' );  //añadido por continent7
            foreach ( $top_level_elements as $e ){  //modificado por continent7
                // descender solo en el árbol actual
                $descend_test = array_intersect( $current_element_markers, $e->classes );
                if ( !empty( $descend_test ) ) 
                    $this->display_element( $e, $children_elements, 2, 0, $args, $output );
            }

            /*
             * si estamos mostrando todos los niveles, y children_elements no está vacío,
             * entonces tenemos huérfanos, que deben mostrarse de todos modos
             */
             /* eliminado por 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 );
             }
            */

/*añadido por 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;
        }
    }
15 mar 2011 20:07:17
Comentarios

Después de probarlos todos, la solución de Alp fue la única que funcionó para mí. Sin embargo, hay un problema con ella. Solo muestra los hijos de primer nivel, pero no muestra los hijos de tercer o cuarto nivel. He estado intentando durante días lograr que lo haga. ¿Alguien sabe cómo modificar su solución para que lo haga? PD: No me permite agregar comentarios, así que tuve que hacerlo como una respuesta.

TechRemarker TechRemarker
24 may 2011 04:30:45
0

El resultado del menú de navegación incluye muchas clases para el elemento actual, ancestro del elemento actual, etc. En algunas situaciones, he podido hacer lo que quieres hacer dejando que se genere todo el árbol de navegación y luego usando CSS para reducirlo solo a los hijos de la página actual, etc.

8 feb 2011 23:39:06
0

Hice un walker modificado que debería ayudar! No es perfecto - deja algunos elementos vacíos, pero hace el trabajo. La modificación son básicamente esas partes de $current_branch. ¡Espero que le sirva 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 Pasado por referencia. Usado para añadir contenido adicional.
 * @param int $depth Profundidad de página. Usado para 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 Pasado por referencia. Usado para añadir contenido adicional.
 * @param int $depth Profundidad de página. Usado para 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 Pasado por referencia. Usado para añadir contenido adicional.
 * @param object $item Objeto de datos del item de menú.
 * @param int $depth Profundidad del item de menú. Usado para padding.
 * @param int $current_page ID del item de menú.
 * @param object $args
 */
function start_el(&$output, $item, $depth, $args) {
    global $wp_query;
    global $current_branch;

    // ¿Está este item de menú en la rama actual?
    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 Pasado por referencia. Usado para añadir contenido adicional.
 * @param object $item Objeto de datos de página. No usado.
 * @param int $depth Profundidad de página. No usado.
 */
function end_el(&$output, $item, $depth) {
    global $current_branch;
    if($current_branch && $depth > 0) $output .= "</li>\n";
    if($depth == 0) $current_branch = 0;
}

}

20 abr 2011 21:46:33
0

Echa un vistazo al código de mi plugin o utilízalo para tus propósitos ;)

Este plugin añade un widget mejorado de "Menú de Navegación". Ofrece muchas opciones que se pueden configurar para personalizar la salida del menú personalizado a través del widget.

Las características incluyen:

  • Jerarquía personalizada - "Solo subelementos relacionados" o "Solo subelementos estrictamente relacionados".
  • Profundidad inicial y nivel máximo a mostrar + visualización plana.
  • Mostrar todos los elementos del menú comenzando por el seleccionado.
  • Mostrar solo la ruta directa al elemento actual o solo los hijos del elemento seleccionado (opción para incluir el elemento padre).
  • Clase personalizada para el bloque del widget.
  • Y casi todos los parámetros para la función wp_nav_menu.

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

10 ene 2012 02:59:05
0

La respuesta aceptada reconoce que requiere entrada y no se basa en el contexto. Aquí hay una versión que admite que se le proporcione un contexto, o puede usar las clases integradas de WordPress current-x para determinar el contexto:

add_filter( 'wp_nav_menu_objects', 'limit_tree', 10, 2 );

function limit_tree( $items, $args ) {

    if ( empty( $args->context ) ) {
        return $items;
    }

    if('current' == $args->context) {

        $current_pages = array_filter($items, function($item) {
            return !empty(array_intersect(['current-menu-parent', 'current-menu-ancestor', 'current-menu-item'], $item->classes));
        });

        $parent_id = array_pop($current_pages)->ID;
        
    } else {
        $ids       = wp_filter_object_list( $items, array( 'object_id' => $args->context ), 'and', 'ID' );
        $parent_id = array_pop( $ids );
    }
    
    $children = submenu_get_children_ids( $parent_id, $items );

    foreach ( $items as $key => $item ) {

        // Esto se puede ajustar si no deseas incluir el elemento padre
        if ( $item->ID != $parent_id && ! in_array( $item->ID, $children ) ) {
            unset( $items[$key] );
        }
    }

    return $items;
}

function submenu_get_children_ids( $id, $items ) {

    $ids = wp_filter_object_list( $items, array( 'menu_item_parent' => $id ), 'and', 'ID' );

    foreach ( $ids as $id ) {

        $ids = array_merge( $ids, submenu_get_children_ids( $id, $items ) );
    }

    return $ids;
}

Y su uso:

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

O...

wp_nav_menu([
    'theme_location'  => 'foo-bar',
    'context' => get_queried_object_id(), // o algún ID de publicación arbitrario
]);
21 oct 2021 00:59:53