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 Nis.
Oui, j’avais un problème dans l’appel à la fonction, donc j’ai repris tout ça au calme et maintenant tout fonctionne, merci !
Commentaire de Florian75.
Bonjour!
Tout d’abord merci pour le tutoriel, c’est bien expliqué et j’ai enfin pu faire mon menu de sous-catégories grâce à ça! Le code marche chez moi mais le seul hic c’est que j’ai malheureusement des petits-fils dans les pages et sachant que je ne suis pas une bête en PHP, je me demandais comment pourrait-on l’adapter?
Sur-ce merci de ta compréhension et du tutoriel! ;)
Commentaire de Grégory Viguier.
Salut Florian.
Non testé, remplacer une partie de mon code (ligne 15) par ceci :
1516171819202122232425262728
Ça ne prend en compte que les enfants directs.
Commentaire de polack45.
Bonjour et un grand merci pour ce tuto !
Cela m’a enlevé une grande épine du pied !!
Par contre forcément il me duplique aussi mes styles de mon menu primary…
Alors que je veux qu’il apparaisse différemment…
Je vais encore cogiter…
mille mercis !!!
Commentaire de polack45.
Rebonjour,
Je ne sais comment réglé le souci des styles.
J’utilise ubermenu pour ma navigation principale avec un style customisé et étant donné qu’on clone ce menu pour générer un sous menu à l’intérieur des pages, il me conserve ces mêmes styles…
Est ce possible à votre avis ?
Merci pour votre aide.
Bonne journée.
polack45
Commentaire de Grégory Viguier.
Salut polack.
Je ne sais pas si je vais pouvoir t’aider, je n’ai jamais utilisé ubermenu. Il faudrait l’empêcher d’intervenir sur ce menu, mais je ne sais pas comment.