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 SG.
Ah mais ça fonctionne, le seul truc qui ne fonctionne pas c’est le filtrage. J’ai bien tout fait, mais ça duplique le menu intégral au lieu de filtrer seulement les sous-menus du menu courant.
Commentaire de Greg.
Ha ok, on s’est mal compris.
Ben j’en sais pas plus ^^ Il faudrait voir la valeur de
$current
et voir pourquoi tout sort :|Commentaire de SG.
C’est assez étrange, j’ai revu le code que j’ai replacé autre part et ça semblait marcher pendant un moment sauf quand je ne suis plus sur Accueil X) Mais c’est pas grave, j’ai trouvé une alternative : à la place je fais un menu avec un :hover ça revient au même, seule la mise en page change j’ai donc revu mon design (pour respecter les délais). Merci quand même d’avoir répondu, je pense que je me pencherai sur ce cas un peu plus tard et si je trouve la faille de mon côté je préviendrai :) peut-être que c’est juste un « ; » quelque part (comme souvent quand on cherche beaucoup pour rien ^^)
Bon courage pour la suite ^^
Commentaire de Jano.
Bonjour,
premièrement merci pour ton super blog !
J’ai une petite question :
Je veux créer sur une page une liste de liens vers des catégories qui sont liées à cette catégorie via le sous menu. Grâce à ton tuto j’ai pu arriver à afficher les différentes catégories (pas peu fier ! :)). Le soucis c’est que je n’arrive pas à supprimer le lien principal de la page actuelle. J’aimerai garder dans ce menu 2 uniquement les élements du sous-menu et non le sous-menu + la page parente.
Je ne sais pas si c’est clair ? Peut être a tu déjà répondu à cette question, dans ce cas n’hésites pas à me remettre en place !
Je te remercie d’avance ! Et je te souhaite une bonne continuation !
Commentaire de Grégory Viguier.
Salut Jano.
Essaies ceci : déplace le
array_unshift($new_items, $current);
pour le mettre juste après$current->url = '#emty_items##';
.Non testé mais ça devrait le faire.
A+
Commentaire de Jano.
Bonsoir Greg,
je te remercie pour ta réactivité je vais tester ça ! :)
Bonne soirée !
Commentaire de Sylvain.
Merci pour cet article.
Exactement ce dont j’avais besoin :)
Commentaire de Toque.
Bonjour,
Je viens chercher de l’aide, car j’ai un petit problème au niveau de ma barre de menu, qui place le dernier onglet de mes catégories en dessous, c’est à dire sur une deuxième ligne , alors qu’il reste largement la place pour qu’il soit aligné avec les autres.
Est il possible d’avoir tous ses onglets sur une seule ligne peu importe la taille de l’écran?
Merci d’avance pour votre aide
Ps: j’utilise ubermenu
Commentaire de Nis.
Bonjour,
Tout d’abord, merci pour ce partage !
J’ai donc vaillamment inclus tous ces éléments dans mes fichiers, mais le résultat obtenu n’est pas tout à fait celui auquel je m’attendais : je pensais que le menu initial (votre menu dans le header) restait tel quel, et que le menu dupliqué apparaissait en supplément dans la sidebar…
… J’obtiens actuellement les deux menus, l’initial et le dupliqué, à deux endroits différents de mes pages (chouette), mais ces deux menus sont identiques, donc ce n’est pas vraiment ce à quoi j’aspirais.
Je n’ai pas l’impression de préciser auquel des deux menus « primary » doit s’appliquer le filtre : est-ce une erreur de ma part ? Ou tout simplement une incompréhension de l’objectif de ce code ?
Merci encore !
Commentaire de Grégory Viguier.
Bonjour.
Tu as bien compris le but du tutoriel : ton menu est dupliqué ailleurs sur la page, mais seul le sous-menu courant est conservé.
S’ils sont identiques c’est qu’il y a en effet un soucis, mais à moins d’intervenir directement dessus je ne pourrais pas voir ce qui cloche hélas.