Agregar la clase 'has_children' al elemento li padre al modificar Walker_Nav_Menu
Estoy escribiendo una clase walker personalizada para wp_nav_menu y quiero poder especificar si un elemento li contiene un submenú. Quiero que mi marcado sea así:
<li class="has_children [otras-clases-wordpress]">
<a class="parent-link">Algún elemento</a>
<ul class="sub-menu">
Sé cómo agregar y eliminar las clases correctamente, solo que no puedo encontrar nada que me indique si el elemento actual tiene elementos hijos.
¿Alguna idea?
Gracias de antemano.

El método start_el()
debería recibir esta información en su parámetro $args
, pero parece que WordPress solo llena esto si $args
es un array, mientras que para los menús de navegación personalizados es un objeto. Esto está reportado en un ticket de Trac. Pero no hay problema, puedes llenar esto tú mismo, si también sobrescribes el método display_element()
en tu walker personalizado (porque este es el lugar más fácil para acceder al array de elementos hijos):
class WPSE16818_Walker extends Walker_Nav_Menu
{
function display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output )
{
$id_field = $this->db_fields['id'];
if ( is_object( $args[0] ) ) {
$args[0]->has_children = ! empty( $children_elements[$element->$id_field] );
}
return parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output );
}
function start_el( &$output, $item, $depth, $args ) {
if ( $args->has_children ) {
// ...
}
}

Hola Jan, ¿Puedes ayudarme con esta pregunta? Intenté tu código pero no pude hacerlo funcionar. ¿Podrías darme un poco más de código de ejemplo?

Consulta el ejemplo de implementación completo más abajo en esta página.

Actualización: A partir de WordPress 3.7 (octubre de 2013), se han añadido clases CSS para indicar elementos y páginas hijas en los menús de temas — no es necesario usar un walker personalizado ya que está incluido en el núcleo de WordPress.
Las clases CSS se llaman menu-item-has-children
y page_item_has_children
.
Para una solución completa para cualquiera que tenga prisa (crédito a la respuesta previa de Jan Fabry), mira la implementación completa a continuación.
Genera la navegación en la plantilla de tu tema:
wp_nav_menu( array(
'theme_location' => 'navigation-primary',
'container' => false,
'container_class' => '',
'container_id' => '',
'menu_class' => '',
'menu_id' => '',
'walker' => new Selective_Walker(),
'depth' => 2
)
);
Luego, incluye lo siguiente en el archivo functions.php
de tu tema:
class Selective_Walker extends Walker_Nav_Menu {
function display_element( $element, &$children_elements, $max_depth, $depth=0, $args, &$output ) {
$id_field = $this->db_fields['id'];
if ( is_object( $args[0] ) ) {
$args[0]->has_children = !empty( $children_elements[$element->$id_field] );
}
return parent::display_element( $element, $children_elements, $max_depth, $depth, $args, $output );
}
function start_el( &$output, $item, $depth, $args ) {
if ( $args->has_children ) {
$item->classes[] = 'has_children';
}
parent::start_el(&$output, $item, $depth, $args);
}
}
El resultado en HTML será similar al siguiente:
<ul>
<li><a href="#">Inicio</a></li>
<li class="has_children"><a href="#">Nosotros</a>
<ul class="sub-menu">
<li><a href="#">Nuestra Misión</a></li>
</ul>
</li>
<li><a href="#">Servicios</a></li>
<li class="has_children"><a href="#">Productos</a>
<ul class="sub-menu">
<li><a href="#">Lorem Ipsum</a></li>
<li><a href="#">Lorem Ipsum</a></li>
</ul>
</li>
<li><a href="#">Contáctanos</a></li>
</ul>
Para más información sobre cómo usar la clase walker de WordPress, consulta Understanding the Walker Class.
¡Disfrútalo!

Esta función es exactamente lo que necesitas tener. También te muestra una forma bastante efectiva de modificar los elementos del menú de navegación. Además, puedes abrirla para funciones más avanzadas (por ejemplo, en un tema hijo) mediante el filtro de elementos:
/**
* Clases para una navegación llamada "Topnav" en la ubicación del menú "top".
* Muestra ejemplos sobre cómo modificar el elemento actual del menú de navegación
*
* @param (object) $items
* @param (object) $menu
* @param (object) $args
*/
function wpse16818_nav_menu_items( $items, $menu, $args )
{
# >>>> comienza la edición
// ejemplos de posibles objetivos
$target['name'] = 'Topnav';
// Los elementos del menú objetivo/s
$target['items'] = array( (int) 6 );
# <<<< detén la edición
// filtro para temas hijos: "config_nav_menu_topnav"
$target = apply_filters( 'config_nav_menu_'.strtolower( $target['name'] ), $target );
// Abortar si no estamos con el menú nombrado
if ( $menu->name !== $target['name'] )
return;
foreach ( $items as $item )
{
// Comprueba qué contiene $item
echo '<pre>'; print_r($item); echo '</pre>';
// Primer ejemplo del mundo real:
$item->classes = 'span-4';
// Segundo ejemplo del mundo real:
// Añade esta clase si estamos en uno de los elementos objetivo
if ( in_array( (int) $item->menu_order, $target['items'] ) )
$item->classes .= ' last';
}
return $items;
}
add_filter( 'wp_get_nav_menu_items', 'wpse16818_nav_menu_items', 10, 3 );
Y sí, en casi todos los casos no hay necesidad de un walker personalizado.

Si deseas crear un menú desplegable, puedes hacerlo solo con CSS. Crea un menú personalizado en WordPress con elementos hijos, WordPress automáticamente asigna la clase .sub-menu al ul hijo. Prueba este CSS:
nav li {position:relative;}
.sub-menu {display:none; position:absolute; width:300px;}
nav ul li:hover ul {display:block;}
Puedes añadir algo de jQuery para mejorarlo un poco, pero esto debería darte un menú desplegable funcional.
