Lors d’un projet récent j’ai eu besoin de dupliquer le sous-menu (du menu principal du site) correspondant à la page courante vers une zone déportée, telle qu’une barre latérale. Aujourd’hui je vais vous exposer la technique que j’ai employée pour réaliser ceci assez simplement.
Situation
Le menu principal de votre site comporte des pages, des catégories, etc., le tout organisé avec des sous-menus. Lorsque vous vous trouvez sur une page, le sous-menu correspondant à cette page doit être affiché dans une sidebar. On duplique donc le « sous-menu courant ».
L’idée
L’idée de base est très simple : plutôt que de vouloir aller chercher les items de menu correspondants et en quelques sortes, réinventer la roue, pourquoi ne pas commencer avec l’outil le plus adapté pour afficher ces items de menu ? Oui, je parle bien de la fonction wp_nav_menu()
, la même fonction qui a normalement été utilisée précédemment dans votre thème. Il « suffirait » ensuite de filtrer les items à afficher.
La pratique
Nous avons notre menu principal avec quelques paramètres :
123456789
$menu_args = array(
'theme_location' => 'primary',
'container' => 'nav',
'container_id' => 'header-menu-container',
'container_class' => 'menu-container clearfix',
'menu_class' => 'clearfix nav site-navigation',
'menu_id' => 'site-navigation',
);
wp_nav_menu( $menu_args );
Tous ces paramètres ne sont pas importants aujourd’hui, sauf un : 'theme_location'
.
A l’endroit où nous mettrons le sous-menu dupliqué (sidebar.php
?), il nous faudra donc utiliser wp_nav_menu()
avec le même paramètre 'theme_location'
. Ceci nous permet de récupérer les mêmes items que dans notre menu principal.
12345678
$menu_args = array(
'theme_location' => 'primary',
'container_id' => 'header-menu-container-sub',
'container_class' => 'menu-container',
'menu_class' => 'nav site-navigation',
'menu_id' => 'site-navigation-sub',
);
wp_nav_menu( $menu_args );
A ce stade, nous dupliquons entièrement notre menu. Les autres paramètres de mise en forme, comme les classes CSS et id, peuvent être modifiés.
Il nous faut maintenant ajouter des filtres dans le fichier functions.php
afin de ne garder que les items de menu qui nous intéressent, et ça tombe bien…
123456
add_filter( 'wp_nav_menu_objects', 'minime_nav_menu_objects', 10, 2 );
function minime_nav_menu_objects( $items, $args ) {
// Do magic
return $items;
}
Ici, $items
contient les items de menu sous forme d’objet, et $args
contient les paramètres que l’on a passés au menu.
Première chose à faire, savoir si l’on est dans le menu original ou dans son « clone ». Pour cela on utilise une variable static qui, pour chaque menu (on peut avoir d’autres menus que ‘primary’), nous dira si ce menu a déjà été exécuté une fois. Si oui, cela veut dire que c’est un « clone ».
010203040506070809101112
add_filter( 'wp_nav_menu_objects', 'minime_nav_menu_objects', 10, 2 );
function minime_nav_menu_objects( $items, $args ) {
static $duplicate = array();
if ( ! empty( $duplicate[ $args->theme_location ] ) ) {
// Virer les indésirables
}
$duplicate[ $args->theme_location ] = 1;
return $items;
}
Une seule contrainte ici, il faut que notre « clone » soit exécuté APRÈS le menu original dans la page. Par exemple, si vous souhaitiez dupliquer un menu situé en pied de page, c’est celui-ci qui serait réduit au sous-menu courant.
Il y a bien sûr un moyen de contourner le problème, en indiquant soi-même quel menu est le « clone » : au lieu d’utiliser la variable $duplicate
, nous passerions un paramètre personnalisé 'is_duplicate' => true
à wp_nav_menu()
, et côté filtre, si ce paramètre existe et est à true
, on ne retourne que les items du sous-menu courant. Ça enlève juste un peu la côté automatique, mais au moins on garde le contrôle.
0102030405060708091011121314151617181920212223
// Exemple avec un paramètre personnalisé "is_duplicate"
// sidebar.php
$menu_args = array(
'theme_location' => 'primary',
'container_id' => 'header-menu-container-sub',
'container_class' => 'menu-container',
'menu_class' => 'nav site-navigation',
'menu_id' => 'site-navigation-sub',
'is_duplicate' => true,
);
wp_nav_menu( $menu_args );
// functions.php
add_filter( 'wp_nav_menu_objects', 'minime_nav_menu_objects', 10, 2 );
function minime_nav_menu_objects( $items, $args ) {
if ( isset( $args->is_duplicate ) && $args->is_duplicate ) {
// Virer les indésirables
}
return $items;
}
La prochaine chose à faire est de savoir où nous sommes, c’est à dire trouver l’item courant. Enfin, pas tout à fait, il nous faut trouver l’item qui n’a pas de parent (à la racine du menu) et dont l’item courant est un fils de celui-ci (vous avez suivi ?) :
0102030405060708091011121314151617181920
add_filter( 'wp_nav_menu_objects', 'minime_nav_menu_objects', 10, 2 );
function minime_nav_menu_objects( $items, $args ) {
static $duplicate = array();
if ( ! empty( $duplicate[ $args->theme_location ] ) ) {
$current = 0;
$new_items = array();
foreach ( $items as $item ) {
if ( ! $item->menu_item_parent && ( $item->current || $item->current_item_ancestor ) ) {
$current = $item;
break;
}
}
// La suite...
}
$duplicate[ $args->theme_location ] = 1;
return $items;
}
Dans le if
situé à l’intérieur de la boucle foreach
, !$item->menu_item_parent
signifie que l’on recherche un item situé à la racine du menu. Ensuite nous cherchons un item qui a le statut « courant » ou « ancêtre de l’item courant ». Nous le rangeons alors dans une variable $current
et nous sortons de la boucle.
Maintenant il nous faut trouver les items fils de notre item $current
.
01020304050607080910111213141516171819202122232425262728
add_filter( 'wp_nav_menu_objects', 'minime_nav_menu_objects', 10, 2 );
function minime_nav_menu_objects( $items, $args ) {
static $duplicate = array();
if ( ! empty( $duplicate[ $args->theme_location ] ) ) {
$current = 0;
$new_items = array();
foreach ( $items as $item ) {
if ( ! $item->menu_item_parent && ( $item->current || $item->current_item_ancestor ) ) {
$current = $item;
break;
}
}
if ( $current ) {
foreach ( $items as $item ) {
if ( $item->menu_item_parent === $current->ID ) {
$new_items[] = $item;
}
}
}
// La suite...
}
$duplicate[ $args->theme_location ] = 1;
return $items;
}
Nous trouvons là une limite à notre script : avec if ( $item->menu_item_parent === $current->ID )
nous cherchons un item dont $current
est le parent direct. Si vous avez plusieurs sous-menus imbriqués, ceux-ci ne seront pas affichés. Pour cela il faudrait certainement une boucle supplémentaire qui répèterait notre foreach
jusqu’à ce que tous les items « petit-fils » soient trouvés, en limitant leur profondeur avec $args->depth
si celui-ci est fourni en paramètre du menu. Vu la lourdeur, ce n’est pas une voie que j’ai souhaité explorer.
Dernière étape (ou presque), ajouter $current
aux items à retourner, et ranger tout ce petit monde dans $items
.
010203040506070809101112131415161718192021222324252627282930
add_filter( 'wp_nav_menu_objects', 'minime_nav_menu_objects', 10, 2 );
function minime_nav_menu_objects( $items, $args ) {
static $duplicate = array();
if ( ! empty( $duplicate[ $args->theme_location ] ) ) {
$current = 0;
$new_items = array();
foreach ( $items as $item ) {
if ( ! $item->menu_item_parent && ( $item->current || $item->current_item_ancestor ) ) {
$current = $item;
break;
}
}
if ( $current ) {
foreach ( $items as $item ) {
if ( $item->menu_item_parent === $current->ID ) {
$new_items[] = $item;
}
}
}
array_unshift( $new_items, $current );
$items = $new_items;
}
$duplicate[ $args->theme_location ] = 1;
return $items;
}
Voilà, notre sous-menu dupliqué est presque prêt. Tel quel, il fonctionne, reste un détail : qu’arrive t’il si le visiteur se trouve sur une page sans sous-menu ou non listée dans le menu ? Le sous-menu ne sortira aucun item (et provoquera une notice php car $current
est égal alors à 0), mais imprimera tout de même ce qui va autour (les conteneurs) : les balises <ul>
et <div>
ou <nav>
.
Comment faire ?
Si aucun item $current
n’a été trouvé, nous allons prendre le premier item qui nous tombe sous la main (pour avoir un item valide à sortir) et lui donner une url spéciale facilement repérable lors d’une recherche de caractères.
01020304050607080910111213141516171819202122232425262728293031323334
add_filter( 'wp_nav_menu_objects', 'minime_nav_menu_objects', 10, 2 );
function minime_nav_menu_objects( $items, $args ) {
static $duplicate = array();
if ( ! empty( $duplicate[ $args->theme_location ] ) ) {
$current = 0;
$new_items = array();
foreach ( $items as $item ) {
if ( ! $item->menu_item_parent && ( $item->current || $item->current_item_ancestor ) ) {
$current = $item;
break;
}
}
if ( $current ) {
foreach ( $items as $item ) {
if ( $item->menu_item_parent === $current->ID ) {
$new_items[] = $item;
}
}
}
else {
$current = reset( $items );
$current->url = '#empty_items##';
}
array_unshift( $new_items, $current );
$items = $new_items;
}
$duplicate[ $args->theme_location ] = 1;
return $items;
}
Ensuite, il nous faut filtrer la sortie du menu et retourner une chaine vide si notre url spéciale est trouvée.
3637383940414243
add_filter( 'wp_nav_menu', 'minime_nav_menu', 10, 2 );
function minime_nav_menu( $nav_menu, $args ) {
if ( strpos( $nav_menu, '"#empty_items##"' ) !== false ) {
return '';
}
return $nav_menu;
}
Hop, terminé.
See ya!
Commentaires
Commentaire de TweetPress de France @ TweetPress.fr.
Excellent d’autant plus avec le fallback ! J’ai bien compris ce que faisait le code mais assez mal le contexte d’utilisation. Tu peux préciser?
Commentaire de Greg.
Salut, et merci :)
Je bossais sur un site incluant :
– Un menu principal en haut de page,
– Une barre latérale.
Dans le menu, certains items au 1er niveau ont des éléments fils, un sous-menu donc (incluant tout et n’importe quoi, que ce soit une page, une catégorie, ou autre). L’idée était de répéter ce sous-menu dans la barre latérale pour lui offrir une meilleure visibilité, lorsque je suis sur une de ces pages.
Par exemple, dans le menu j’ai un élément « Machin » (une page), et cet élément à un sous-menu (composé de catégories). Lorsque je me rend sur la page « Machin », les liens vers les catégories doivent être visibles dans la barre latérale.
Est-ce que c’est plus clair ? :)
Commentaire de TweetPress de France @ TweetPress.fr.
AH ok. Merci. En effet ça peut servir. bookmarkage. 8)
Commentaire de SG.
Salut,
Je n’ai pas bien compris ce qu’il fallait faire pour arriver au bout. Lorsque j’ai testé tout le menu est dupliqué donc peut-être que le filtre ne fonctionne pas ou bien je n’ai pas compris ce qu’il fallait changer pour le theme-location. Primary c’est quoi du coup ?
Il n’existe pas de plugin conçus pour faire ça plus facilement ?
Si j’ai un menu du style :
Menu1 Menu2
Avec un sous-menu pour Menu1 :
Menu11
Menu12
Dans ma sidebar j’aimerais que Menu1 s’affiche avec Menu11 et Menu12 sans afficher Menu2.
Est ce qu’on arrive bien à ce résultat à la fin ? Car je ne souhaite pas récupérer seulement les sous-menus mais aussi le menu de haut niveau qui correspond à ces sous-menus.
Merci d’avance :)
Commentaire de Greg.
Salut SG.
– ‘primary’ est un exemple pour ‘theme_location’, il faut reprendre la valeur du menu que tu veux dupliquer.
– Un plugin ? Je ne sais pas, pas cherché.
– Oui on arrive bien à ça, ton Menu1 sera inclus normalement. Précision supplémentaire : tout ceci sera dupliqué seulement quand on visite la page de ton Menu1.
Commentaire de SG.
Par valeur du menu tu entends quoi ?
Commentaire de Greg.
Et bien vas voir le code de ton menu (l’original) et regarde la valeur de ‘theme_location’.
Commentaire de SG.
J’ai le menu de base de WP avec le code suivant :
‘primary’, ‘menu_class’ => ‘nav-menu’ ) ); ?>
Du coup c’est toujours primary qu’il faut mettre dans theme-location ? Je copies/colles ce code à l’endroit où je veux inclure ma partie de sous-menu ?
Si c’est ça je l’ai fait mais du coup le problème doit venir du filtre.
Commentaire de Greg.
Ton code a été coupé x)
Mais je vois ‘primary’ au début, donc je suppose que c’est la valeur de ‘theme_location’, donc ça « devrait » marcher :/
Commentaire de SG.
Ah oui désolé X)
Beh c’est bien parce que ça devrait marcher que je posais la question du code de filtrage ^^
Commentaire de Greg.
OK donc lorsque tu es sur ta page Menu1, rien ne sort dans ta sidebar… Hmm… Hélas, sans pouvoir accéder au code du site pour débuguer, je ne vois pas ce que je peux faire de plus. Je viens de copier/coller mon code sur un site et ça marche parfaitement.
(au fait, t’es sûr d’avoir mis un
wp_nav_menu( array( 'theme_location' => 'primary' ) );
à l’endroit où tu veux dupliquer ton menu ? Le fichier est bien inclus dans la page que tu visites ? ^^)