Walker per Mega Menu
Sto cercando di creare un walker per mega menu. Sfortunatamente, i walker sono completamente al di fuori delle mie conoscenze di codifica. Potrei davvero aver bisogno di aiuto per farlo funzionare. Ecco le funzionalità di cui ho bisogno:
- Racchiudere il tag
<ul>
di secondo livello in un tag<section>
. [COMPLETATO] - Quando un utente imposta la classe "break" su un
<li>
nel tag<ul>
di secondo livello, fare in modo che quel<li>
diventi l'inizio di un nuovo<ul>
. Se è il primo<li>
dell'elenco, non fare nulla, per evitare la formazione di elenchi non ordinati vuoti. [COMPLETATO] - Quando un utente imposta la classe "widget" su un
<li>
di primo livello che ha un<ul>
secondario, aggiungere un widget alla fine di quel<ul>
. [COMPLETATO] - Aggiungere la classe
mega-menu-columns-#
agli elementi<li>
di primo livello che contengono menu a discesa con più colonne e/o un widget. Il # rappresenta il numero di elementi<ul>
, +1 per il widget se esiste. [COMPLETATO]
Ho un po' di codice per fare alcune di queste cose, ma non tutte. Ci sono sezioni tagliate di seguito:
Racchiudere il tag <ul>
di secondo livello in <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";
}
}
Generare l'HTML del widget:
ob_start();
dynamic_sidebar("Navigation Callout");
$widget = ob_get_contents();
ob_end_clean();
L'output HTML sarebbe:
<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/">
About Us
</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/">
Company Profile
</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/">
Leadership Team
</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/">
Professional Affiliations
</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/">
Clients
</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/">
Partnerships
</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/">
Services
</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/">
Civil Engineering
</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/">
Land Planning
</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/">
Surveying
</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/">
Information Technology
</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/">
Subsurface Utility Engineering
</a>
</li>
</ul>
<aside>
<h6>Widget Title</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/">
Contact Us
</a>
</li>
</ul>
AGGIORNAMENTO: I miei contatori mi stanno dando problemi. Stanno contando solo dopo che il sottomenu è stato generato, il che non mi aiuta. Guarda questo screenshot per capire cosa intendo:
I numeri in alto vengono estratti in start_el
. I numeri in basso vengono estratti in end_el
. Come puoi vedere, i numeri in alto non contano i miei .breaks
come dovrebbero. Contano la classe widget perché queste vengono contate in $depth = 0
. Qualcuno mi salvi da questo orrore!
// mega menu walker
/*
ONE REMAINING BUG:
- Need to add class to LI containing mega-menu-columns-#
*/
class megaMenuWalker extends Walker_Nav_Menu {
private $column_limit = 3; /* needs to be set for each site */
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) {
/* needs to be added to opening level 0 li */
$column_count_class = " mega-menu-columns-" . $this->column_count;
$output .= $column_count_class;
/* end */
$this->column_count = 0;
}
$output .= "</li>";
}
}
AGGIORNAMENTO 2: Ecco un esempio di output con commenti che descrivono come la classe mega-menu-columns-
dovrebbe contare le cose:
<ul>
<!-- +1 perché questo ha una classe "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/">
About Us
</a>
<!-- +1 perché esiste un menu a discesa -->
<!-- viene aggiunto dal mio walker -->
<section>
<!-- fine aggiunto dal mio walker -->
<ul class="sub-menu">
<!-- +0 perché questo "break" è il primo figlio -->
<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/">
Company Profile
</a>
<ul>
<!-- +0 perché questo "break" è nel livello 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/">
Our Team
</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/">
Leadership Team
</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/">
Professional Affiliations
</a>
</li>
<!-- viene aggiunto dal mio walker -->
</ul>
<ul class="sub-menu">
<!-- fine aggiunto dal mio walker -->
<!-- +1 perché questo "break" è nel livello 1 e non è il primo figlio -->
<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/">
Clients
</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/">
Partnerships
</a>
</li>
</ul>
<!-- viene aggiunto dal mio walker per .widget -->
<section>
<header>
<h1>Widget Title</h1>
</header>
<p>Questo è un widget. È stato difficile farlo apparire!</p>
</section>
<!-- fine aggiunto dal mio walker per .widget -->
<!-- viene aggiunto dal mio walker -->
</section>
<!-- fine aggiunto dal mio walker -->
</li>
</ul>
AGGIORNAMENTO: Ecco il mio Walker e le Funzioni finali. Questo fa esattamente ciò che volevo. Grazie per l'aiuto!
// mega menu walker
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)); // imposta l'array delle classi da aggiungere come classi a ciascun 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>";
}
}
// aggiungi classi 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;
};
// aggiungi le classi delle colonne
add_filter("wp_nav_menu_args", function($args) {
if ($args["walker"] instanceof megaMenuWalker) {
add_filter("wp_nav_menu_objects", "add_column_number");
}
return $args;
});
// ferma la funzione delle classi delle colonne
add_filter("wp_nav_menu", function( $nav_menu ) {
remove_filter("wp_nav_menu_objects", "add_column_number");
return $nav_menu;
});

Se ho capito correttamente il problema, potresti provare a contare le classi break e widget all'interno del filtro wp_nav_menu_objects
.
Ecco un esempio aggiornato, piuttosto esteso a causa della parte di debug aggiuntiva:
add_filter( 'wp_nav_menu_objects',
function( $items, $args ) {
// Applica questo solo per il menu 'primary':
if( 'primary' !== $args->theme_location )
return $items;
// Qui "x_" si riferisce all'ultimo li radice (profondità 0)
static $x_pid = 0; // ID del post dell'ultimo li radice (profondità 0)
static $x_key = 0; // chiave dell'array dell'ultimo li radice (profondità 0)
static $x_cols = 0; // n break o widget danno n+1 colonne
static $x_has_dropdown = false; // se l'ultimo li radice (profondità 0) ha un dropdown
// Variabili interne:
$tmp = array();
$debug_string = '';
$show_debug = true; // Modifica questo in base alle tue esigenze:
foreach( $items as $key => $item )
{
// Debug:
$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;
// Colleziona gli incrementi delle colonne:
$inc = 0;
// Profondità 0:
if( 0 == $item->menu_item_parent )
{
$debug['depth'] = 0;
// Reset:
$x_key = $key;
$x_pid = $item->ID;
$x_cols = 0;
$x_has_dropdown = false;
// Se esiste la classe widget:
if( in_array( 'widget', $item->classes, 1 ) )
{
$debug['has_widget_class'] = '1';
$inc++;
}
}
// Profondità 1:
if( $x_pid == $item->menu_item_parent )
{
$debug['depth'] = 1;
// Incrementa il conteggio delle colonne per un dropdown esistente:
if( ! $x_has_dropdown )
{
$inc++;
$x_has_dropdown = true;
}
// Controlla la classe 'break':
if( in_array( 'break', $item->classes, 1 ) )
{
$debug['x_has_break_class'] = 1;
// Primo figlio 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;
// Incrementa il conteggio delle colonne:
$debug['x_cols_increase'] = $inc;
$x_cols += $inc;
$debug['x_cols'] = $x_cols;
// Colleziona il debug:
$debug_string .= print_r( $debug, 1 );
} // end foreach
// Mostra le informazioni di debug:
if( $show_debug )
printf( "<!-- debug: %s -->", $debug_string );
// Inserisci la nuova classe 'mega menu' nel corrispondente oggetto menu:
foreach( $t as $key => $value )
{
$items[$key]->classes[] = sprintf( 'mega-menu-columns-%d', $value );
}
return $items;
}
, PHP_INT_MAX, 2 );
Con la tua struttura del menu attuale, ottengo queste informazioni di debug:
<!-- debug: Array
(
[ID] => 3316
[title] => Chi Siamo
[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] => Profilo Aziendale
[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] => Il Nostro 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] => Team Dirigente
[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] => Affiliazioni Professionali
[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] => Clienti
[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] => Partnership
[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
)
-->
Se vuoi verificare se l'oggetto walker è della classe megaMenuWalker
, puoi usare:
if( ! is_object( $args->walker ) || ! is_a( $args->walker, 'megaMenuWalker' ) )
return $items;
invece di
if( 'primary' !== $args->theme_location )
return $items;
Spero che questo ti aiuti.

Stranamente questo sta restituendo 1 come numero. Vedi http://hfracquetandfitness.myweblinx.net/ (ispeziona il primo li nella navigazione in alto). EDIT -- Penso di sapere il perché. La classe break
viene aggiunta agli elementi <li>
figli diretti, non all'attuale <li>
. Dovrebbe verificare che sia un figlio diretto, e non il primo figlio che ha anche la classe break
.

Ho aggiornato la risposta, c'era un errore di battitura (&& invece di ||).

Ah, questo ha aiutato un po', ma vedo che conta se l'elemento di lista corrente ha la classe break
. In realtà dovrebbe contare se i figli diretti hanno la classe break
, non se stesso, ed escludere il primo figlio li
nell'elemento ul
figlio, se ha senso.

Presumo che gli elementi <li>
radice siano al livello 0
, e poi conto le classi break/widget dei sotto-elementi <li>
al livello 1
. Penso che dovrei iniziare $x_cols
da 1
invece che da 0
, poiché n
break danno n+1
colonne.

Sì, come nel mio walker, se ha dei figli, dovrebbe iniziare da 1. Vedi la mia domanda aggiornata per una descrizione più chiara (tra ~2 minuti).

Non potrò testare completamente fino a lunedì (ho dimenticato di annotare il login WP prima di lasciare il lavoro), ma sembra che questo conti il primo elemento li
figlio nella lista se ha una classe break`. Devo escluderlo. È possibile?

Sono riuscito a reimpostare la password, questo conta sicuramente il primo li
nella lista se ha una classe break
, cosa che non dovrebbe fare.

In realtà sono riuscito a capire questa cosa. C'è un modo per aggiungerlo al Walker, o farlo scattare quando il walker viene eseguito? Preferirei dover configurare solo una cosa quando lo imposto sui siti, piuttosto che due. Se non è possibile, posso gestirlo.

Ok bene. Ho appena aggiornato la risposta, con alcuni elementi di debug aggiuntivi per aiutarti a navigare attraverso questo. Speriamo che ci stiamo avvicinando. Forse c'è un modo per farlo nel walker, ma dovrei pensarci di più ;-) Invece di attivarlo per il menu principale, potremmo usare qualcos'altro per attivarlo, ad esempio il titolo del menu o una sottostringa nel titolo del menu o qualsiasi cosa ti piaccia ;-)

Ho pubblicato il mio codice finale sopra. Idealmente dovrebbe attivarsi ogni volta che il walker si attiva, ma vabbè. Grazie per l'aiuto!
