Walker para Menú Mega
Estoy intentando crear un walker para menú mega. Desafortunadamente, los walkers escapan completamente a mi conocimiento de programación. Realmente necesitaría ayuda para que funcione. Estas son las características que necesito:
- Envolver el
<ul>
de segundo nivel en un<section>
. [COMPLETO] - Cuando un usuario establezca la clase "break" en un
<li>
del segundo nivel<ul>
, hacer que ese<li>
comience un nuevo<ul>
. Si es el primer<li>
de la lista, no hacer nada para evitar la formación de listas desordenadas vacías. [COMPLETO] - Cuando un usuario establezca la clase "widget" en un
<li>
de primer nivel que tenga un sub<ul>
, añadir un widget al final de ese<ul>
. [COMPLETO] - Añadir la clase
mega-menu-columns-#
a los elementos<li>
de primer nivel que contengan desplegables con múltiples columnas y/o un widget. El # representa el número de elementos<ul>
, +1 por el widget si existe. [COMPLETO]
Tengo un poco de código que hace parte de esto, pero no todo. Hay secciones recortadas abajo:
Envolver el <ul>
de segundo nivel en <section>
:
function start_lvl(&$output, $depth = 0, $args = array()) {
if ($depth == 0) {
$output .= "<section>";
}
$output .= "<ul class=\"sub-menu\">";
}
function end_lvl(&$output, $depth = 0, $args = array()) {
$output .= "</ul>";
if ($depth == 0) {
$output .= "</section>\n";
}
}
Generar el HTML del widget:
ob_start();
dynamic_sidebar("Navigation Callout");
$widget = ob_get_contents();
ob_end_clean();
El HTML de salida sería:
<ul>
<li id="menu-item-1" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-has-children menu-item-1 mega-menu-columns-2">
<a href="http://www.example.com/about/">
Sobre Nosotros
</a>
<section>
<ul class="sub-menu">
<li id="menu-item-2" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-2">
<a href="http://www.example.com/about/company-profile/">
Perfil de la Empresa
</a>
</li>
<li id="menu-item-3" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-3">
<a href="http://www.example.com/about/leadership-team/">
Equipo Directivo
</a>
</li>
<li id="menu-item-4" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-4">
<a href="http://www.example.com/about/professional-affiliations/">
Afiliaciones Profesionales
</a>
</li>
</ul>
<ul class="sub-menu">
<li id="menu-item-5" class="break menu-item menu-item-type-post_type menu-item-object-page menu-item-5">
<a href="http://www.example.com/about/clients/">
Clientes
</a>
</li>
<li id="menu-item-6" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-6">
<a href="http://www.example.com/about/partnerships/">
Alianzas
</a>
</li>
</ul>
</section>
</li>
<li id="menu-item-7" class="widget menu-item menu-item-type-post_type menu-item-object-page menu-item-has-children menu-item-7 mega-menu-columns-3">
<a href="http://www.example.com/services/">
Servicios
</a>
<section>
<ul class="sub-menu">
<li id="menu-item-8" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-8">
<a href="http://www.example.com/services/civil-engineering/">
Ingeniería Civil
</a>
</li>
<li id="menu-item-9" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-9">
<a href="http://www.example.com/services/land-planning/">
Planificación de Terrenos
</a>
</li>
<li id="menu-item-10" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-10">
<a href="http://www.example.com/services/surveying/">
Topografía
</a>
</li>
</ul>
<ul class="sub-menu">
<li id="menu-item-11" class="break menu-item menu-item-type-post_type menu-item-object-page menu-item-11">
<a href="http://www.example.com/services/information-technology/">
Tecnología de la Información
</a>
</li>
<li id="menu-item-12" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-12">
<a href="http://www.example.com/services/subsurface-utility-engineering/">
Ingeniería de Utilidades Subterráneas
</a>
</li>
</ul>
<aside>
<h6>Título del Widget</h6>
<p>Maecenas quis semper arcu. Quisque consequat risus nisi. Sed venenatis urna porta eros malesuada euismod. Nulla sollicitudin fringilla posuere. Nulla et tellus eu nisi sodales convallis non vel tellus.</p>
</aside>
</section>
</li>
<li id="menu-item-13" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-15">
<a href="http://www.example.com/contact/">
Contáctenos
</a>
</li>
</ul>
ACTUALIZACIÓN: Mis contadores me están dando problemas. Solo cuentan después de que se ha generado el submenú, lo cual no me ayuda. Mira esta captura de pantalla para entender a qué me refiero:
Los números superiores se obtienen en start_el
. Los números inferiores se obtienen en end_el
. Como puedes ver, los números superiores no cuentan mis .breaks
como deberían. Cuentan la clase widget porque esos se están contando en $depth = 0
. ¡Alguien por favor sálveme de esta pesadilla!
// walker para menú mega
/*
UN BUG RESTANTE:
- Necesito añadir clase al LI que contiene mega-menu-columns-#
*/
class megaMenuWalker extends Walker_Nav_Menu {
private $column_limit = 3; /* debe configurarse para cada sitio */
private $show_widget = false;
private $column_count = 0;
static $li_count = 0;
function start_el(&$output, $item, $depth = 0, $args = array(), $id = 0) {
$classes = empty($item->classes) ? array() : (array) $item->classes;
$item_id = $item->ID;
if ($depth == 0) self::$li_count = 0;
if ($depth == 0 && in_array("widget", $classes)) {
$this->show_widget = true;
$this->column_count++;
}
if ($depth == 1 && self::$li_count == 1) {
$this->column_count++;
}
if ($depth == 1 && in_array("break", $classes) && self::$li_count != 1 && $this->column_count < $this->column_limit) {
$output .= "</ul><ul class=\"sub-menu\">";
$this->column_count++;
}
if ($depth == 0 && $this->column_count > 0) {
$mega_menu_class = " mega-menu-columns-" . $this->column_count;
}
$class_names = join(" ", apply_filters("nav_menu_css_class", array_filter($classes), $item));
$class_names = " class=\"" . esc_attr($class_names . $mega_menu_class) . "\"";
$output .= sprintf(
"<li id=\"menu-item-%s\"%s><a href=\"%s\">%s</a>",
$item_id,
$class_names,
$item->url,
$item->title
);
self::$li_count++;
}
function start_lvl(&$output, $depth = 0, $args = array()) {
if ($depth == 0) {
$output .= "<section>";
}
$output .= "<ul class=\"sub-menu\">";
}
function end_lvl(&$output, $depth = 0, $args = array()) {
$output .= "</ul>";
if ($depth == 0) {
if ($this->show_widget) {
ob_start();
dynamic_sidebar("Navigation Callout");
$widget = ob_get_contents();
ob_end_clean();
$output .= $widget;
$this->show_widget = false;
}
$output .= "</section>";
}
}
function end_el(&$output, $item, $depth = 0, $args = array(), $id = 0) {
if ($depth == 0 && $this->column_count > 0) {
/* necesita añadirse al li de nivel 0 de apertura */
$column_count_class = " mega-menu-columns-" . $this->column_count;
$output .= $column_count_class;
/* fin */
$this->column_count = 0;
}
$output .= "</li>";
}
}
ACTUALIZACIÓN 2: Aquí hay un ejemplo de salida con comentarios que describen cómo debería contar la clase mega-menu-columns-
:
<ul>
<!-- +1 porque tiene clase "widget" -->
<li id="menu-item-1" class="widget menu-item menu-item-type-post_type menu-item-object-page menu-item-has-children menu-item-1 mega-menu-columns-3">
<a href="http://www.example.com/about/">
Sobre Nosotros
</a>
<!-- +1 porque existe un desplegable -->
<!-- se añade por mi walker -->
<section>
<!-- fin se añade por mi walker -->
<ul class="sub-menu">
<!-- +0 porque este "break" es el primer hijo -->
<li id="menu-item-2" class="break menu-item menu-item-type-post_type menu-item-object-page menu-item-2">
<a href="http://www.example.com/about/company-profile/">
Perfil de la Empresa
</a>
<ul>
<!-- +0 porque este "break" está en nivel 2 -->
<li id="menu-item-3" class="break menu-item menu-item-type-post_type menu-item-object-page menu-item-3">
<a href="http://www.example.com/about/our-team/">
Nuestro Equipo
</a>
</li>
</ul>
</li>
<li id="menu-item-4" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-4">
<a href="http://www.example.com/about/leadership-team/">
Equipo Directivo
</a>
</li>
<li id="menu-item-5" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-5">
<a href="http://www.example.com/about/professional-affiliations/">
Afiliaciones Profesionales
</a>
</li>
<!-- se añade por mi walker -->
</ul>
<ul class="sub-menu">
<!-- fin se añade por mi walker -->
<!-- +1 porque este "break" está en nivel 1 y no es el primer hijo -->
<li id="menu-item-6" class="break menu-item menu-item-type-post_type menu-item-object-page menu-item-6">
<a href="http://www.example.com/about/clients/">
Clientes
</a>
</li>
<li id="menu-item-7" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-7">
<a href="http://www.example.com/about/partnerships/">
Alianzas
</a>
</li>
</ul>
<!-- se añade por mi walker para .widget -->
<section>
<header>
<h1>Título del Widget</h1>
</header>
<p>Este es un widget. ¡Fue difícil hacerlo aparecer!</p>
</section>
<!-- fin se añade por mi walker para .widget -->
<!-- se añade por mi walker -->
</section>
<!-- fin se añade por mi walker -->
</li>
</ul>
ACTUALIZACIÓN: Aquí está mi Walker y Funciones finales. ¡Esto hace exactamente lo que quería que hiciera! Gracias por la ayuda.
// walker para menú mega
class megaMenuWalker extends Walker_Nav_Menu {
private $column_limit = 3;
private $show_widget = false;
private $column_count = 0;
static $li_count = 0;
function start_el(&$output, $item, $depth = 0, $args = array(), $id = 0) {
$classes = empty($item->classes) ? array() : (array) $item->classes;
$item_id = $item->ID;
if ($depth == 0) {
self::$li_count = 0;
}
if ($depth == 0 && in_array("widget", $classes)) {
$this->show_widget = true;
$this->column_count++;
}
if ($depth == 1 && self::$li_count == 1) {
$this->column_count++;
}
if ($depth == 1 && in_array("break", $classes) && self::$li_count != 1 && $this->column_count < $this->column_limit) {
$output .= "</ul><ul class=\"sub-menu\">";
$this->column_count++;
}
$class_names = join(" ", apply_filters("nav_menu_css_class", array_filter($classes), $item)); // configurar el array de clases para añadirlas como clases a cada li
$class_names = " class=\"" . esc_attr($class_names) . "\"";
$output .= sprintf(
"<li id=\"menu-item-%s\"%s><a href=\"%s\">%s</a>",
$item_id,
$class_names,
$item->url,
$item->title
);
self::$li_count++;
}
function start_lvl(&$output, $depth = 0, $args = array()) {
if ($depth == 0) {
$output .= "<section>";
}
$output .= "<ul class=\"sub-menu\">";
}
function end_lvl(&$output, $depth = 0, $args = array()) {
$output .= "</ul>";
if ($depth == 0) {
if ($this->show_widget) {
ob_start();
dynamic_sidebar("Navigation Callout");
$widget = ob_get_contents();
ob_end_clean();
$output .= $widget;
$this->show_widget = false;
}
$output .= "</section>";
}
}
function end_el(&$output, $item, $depth = 0, $args = array(), $id = 0) {
if ($depth == 0 && $this->column_count > 0) {
$this->column_count = 0;
}
$output .= "</li>";
}
}
// añadir clases mega-menu-columns-#
function add_column_number($items, $args) {
static $column_limit = 3;
static $post_id = 0;
static $x_key = 0;
static $column_count = 0;
static $li_count = 0;
$tmp = array();
foreach($items as $key => $item) {
if (0 == $item->menu_item_parent) {
$x_key = $key;
$post_id = $item->ID;
$column_count = 0;
$li_count = 0;
if (in_array("widget", $item->classes, 1)) {
$column_count++;
}
}
if ($post_id == $item->menu_item_parent) {
$li_count++;
if ($column_count < $column_limit && $li_count == 1) {
$column_count++;
}
if (in_array("break", $item->classes, 1) && $li_count > 1 && $column_count < $column_limit) {
$column_count++;
}
$tmp[$x_key] = $column_count;
}
}
foreach($tmp as $key => $value) {
$items[$key]->classes[] = sprintf("mega-menu-columns-%d", $value);
}
unset($tmp);
return $items;
};
// añadir las clases de columnas
add_filter("wp_nav_menu_args", function($args) {
if ($args["walker"] instanceof megaMenuWalker) {
add_filter("wp_nav_menu_objects", "add_column_number");
}
return $args;
});
// detener la función de clases de columnas
add_filter("wp_nav_menu", function( $nav_menu ) {
remove_filter("wp_nav_menu_objects", "add_column_number");
return $nav_menu;
});

Si entiendo correctamente la configuración del problema, podrías intentar hacer el conteo de las clases break y widget dentro del filtro wp_nav_menu_objects
.
Aquí hay un ejemplo actualizado, está bastante expandido debido a la parte extra de depuración:
add_filter( 'wp_nav_menu_objects',
function( $items, $args ) {
// Solo aplicar esto para el menú 'primary':
if( 'primary' !== $args->theme_location )
return $items;
// Aquí "x_" significa el último li raíz (profundidad 0)
static $x_pid = 0; // ID del post del último li raíz (profundidad 0)
static $x_key = 0; // clave del array del último li raíz (profundidad 0)
static $x_cols = 0; // n breaks o widgets dan n+1 columnas
static $x_has_dropdown = false; // si el último li raíz (profundidad 0) tiene dropdown
// Internos:
$tmp = array();
$debug_string = '';
$show_debug = true; // Edita esto según tus necesidades:
foreach( $items as $key => $item )
{
// Depuración:
$debug = array();
$debug['ID'] = $item->ID;
$debug['title'] = $item->title;
$debug['key'] = $key;
$debug['x_key'] = $x_key;
$debug['depth'] = '';
$debug['menu_item_parent'] = $item->menu_item_parent;
$debug['has_widget_class'] = 0;
$debug['is_depth_1_first_child'] = 0;
$debug['x_has_dropdown'] = 0;
$debug['has_break_class'] = 0;
$debug['x_cols_increase'] = 0;
// Recolectar incrementos de columnas:
$inc = 0;
// Profundidad 0:
if( 0 == $item->menu_item_parent )
{
$debug['depth'] = 0;
// Reinicios:
$x_key = $key;
$x_pid = $item->ID;
$x_cols = 0;
$x_has_dropdown = false;
// Si existe la clase widget:
if( in_array( 'widget', $item->classes, 1 ) )
{
$debug['has_widget_class'] = '1';
$inc++;
}
}
// Profundidad 1:
if( $x_pid == $item->menu_item_parent )
{
$debug['depth'] = 1;
// Incrementar el conteo de columnas para un dropdown existente:
if( ! $x_has_dropdown )
{
$inc++;
$x_has_dropdown = true;
}
// Verificar la clase 'break':
if( in_array( 'break', $item->classes, 1 ) )
{
$debug['x_has_break_class'] = 1;
// Primer hijo li:
if( $x_key+1 == $key+0 )
{
$debug['is_depth_1_first_child'] = 1;
}
else
{
$debug['is_depth_1_first_child'] = 0;
$inc++;
}
}
$t[$x_key] = $x_cols;
}
$debug['x_has_dropdown'] = (int) $x_has_dropdown;
// Incrementar el conteo de columnas:
$debug['x_cols_increase'] = $inc;
$x_cols += $inc;
$debug['x_cols'] = $x_cols;
// Recolectar la depuración:
$debug_string .= print_r( $debug, 1 );
} // end foreach
// Mostrar información de depuración:
if( $show_debug )
printf( "<!-- debug: %s -->", $debug_string );
// Insertar la nueva clase 'mega menu' al objeto de menú correspondiente:
foreach( $t as $key => $value )
{
$items[$key]->classes[] = sprintf( 'mega-menu-columns-%d', $value );
}
return $items;
}
, PHP_INT_MAX, 2 );
Con tu árbol de menú actual, obtengo esta información de depuración:
<!-- debug: Array
(
[ID] => 3316
[title] => About Us
[key] => 1
[x_key] => 0
[depth] => 0
[menu_item_parent] => 0
[has_widget_class] => 1
[is_depth_1_first_child] => 0
[x_has_dropdown] => 0
[has_break_class] => 0
[x_cols_increase] => 1
[x_cols] => 1
)
Array
(
[ID] => 3317
[title] => Company Profile
[key] => 2
[x_key] => 1
[depth] => 1
[menu_item_parent] => 3316
[has_widget_class] => 0
[is_depth_1_first_child] => 1
[x_has_dropdown] => 1
[has_break_class] => 0
[x_cols_increase] => 1
[x_has_break_class] => 1
[x_cols] => 2
)
Array
(
[ID] => 3318
[title] => Our Team
[key] => 3
[x_key] => 1
[depth] =>
[menu_item_parent] => 3317
[has_widget_class] => 0
[is_depth_1_first_child] => 0
[x_has_dropdown] => 1
[has_break_class] => 0
[x_cols_increase] => 0
[x_cols] => 2
)
Array
(
[ID] => 3319
[title] => Leadership Team
[key] => 4
[x_key] => 1
[depth] => 1
[menu_item_parent] => 3316
[has_widget_class] => 0
[is_depth_1_first_child] => 0
[x_has_dropdown] => 1
[has_break_class] => 0
[x_cols_increase] => 0
[x_cols] => 2
)
Array
(
[ID] => 3320
[title] => Professional Affiliations
[key] => 5
[x_key] => 1
[depth] => 1
[menu_item_parent] => 3316
[has_widget_class] => 0
[is_depth_1_first_child] => 0
[x_has_dropdown] => 1
[has_break_class] => 0
[x_cols_increase] => 0
[x_cols] => 2
)
Array
(
[ID] => 3321
[title] => Clients
[key] => 6
[x_key] => 1
[depth] => 1
[menu_item_parent] => 3316
[has_widget_class] => 0
[is_depth_1_first_child] => 0
[x_has_dropdown] => 1
[has_break_class] => 0
[x_cols_increase] => 1
[x_has_break_class] => 1
[x_cols] => 3
)
Array
(
[ID] => 3322
[title] => Partnerships
[key] => 7
[x_key] => 1
[depth] => 1
[menu_item_parent] => 3316
[has_widget_class] => 0
[is_depth_1_first_child] => 0
[x_has_dropdown] => 1
[has_break_class] => 0
[x_cols_increase] => 0
[x_cols] => 3
)
-->
Si quieres verificar si el objeto walker es de la clase megaMenuWalker
, puedes usar:
if( ! is_object( $args->walker ) || ! is_a( $args->walker, 'megaMenuWalker' ) )
return $items;
en lugar de
if( 'primary' !== $args->theme_location )
return $items;
Espero que esto ayude.

Extrañamente, esto está mostrando 1 como el número. Mira http://hfracquetandfitness.myweblinx.net/ (inspecciona el primer li en la navegación superior). EDIT -- Creo que sé por qué. Break se agrega a elementos hijos directos <li>
, no al <li>
actual. Necesita verificar que sea un hijo directo, y no el primer hijo que también tenga la clase break
.

Actualicé la respuesta, tenía un error tipográfico (&& en lugar de ||).

Ah, eso ayudó un poco, pero veo que está contando si el elemento de lista actual tiene la clase break
. En realidad necesita contar si los hijos directos tienen la clase break
, no él mismo, y excluir el primer hijo li
en el ul
hijo, si eso tiene sentido.

Asumo que los elementos raíz <li>
están en el nivel 0
, y luego cuento las clases break/widget de los subelementos <li>
en el nivel 1
. Creo que debería comenzar $x_cols
en 1
en lugar de 0
, ya que n
breaks generan n+1
columnas.

Sí, como en mi walker, si tiene algún hijo, debería comenzar en 1. Mira mi pregunta actualizada para una descripción más clara (en ~2 minutos).

No podré probar completamente hasta el lunes (olvidé anotar el login de WP antes de salir del trabajo), pero parece que contará el primer hijo li
en la lista si tiene una clase break. Necesito excluir ese. ¿Es posible?

Pude restablecer la contraseña, esto definitivamente está contando el primer li
en la lista si tiene una clase de break
, lo cual no debería hacer.

En realidad pude resolver eso. ¿Hay alguna manera de agregar esto al Walker, o activarlo cuando el walker se ejecuta? Preferiría tener que configurar solo una cosa cuando estoy configurando esto en los sitios, en lugar de dos. Si no, puedo lidiar con ello.

Bien, genial. Acabo de actualizar la respuesta, con algo de información de depuración adicional para ayudarte a navegar por esto. Esperemos que estemos más cerca. Tal vez haya una manera de hacer esto en el walker, pero tendría que pensarlo más ;-) En lugar de activar esto para el menú principal, podríamos usar algo más para activarlo, por ejemplo el título del menú o una subcadena en el título del menú o lo que prefieras ;-)

He publicado mi código final arriba. Idealmente debería ejecutarse cada vez que el walker se active, pero bueno. ¡Gracias por la ayuda!
