Dans la précédente partie nous avions vu la mise en place d’un menu simple. Qu’en est-il de notre menu s’il possède plusieurs niveaux ? C’est ce que nous allons voir !
Préciser la page parente de la page courante
Pour rappel, dans le cas où notre menu possède plusieurs niveaux, la page parente de la page courante doit posséder l’attribut aria-current="true"
.
<a href="#" aria-current="true">Actualités</a>
<ul class="sub-menu">
<li>
<a href="#" aria-current="page">Sous-actualité</a>
</li>
<li>
<a href="#">Sous-actualité</a>
</li>
</ul>
Adapter sur WordPress
Nous l’avions vu dans la partie 1, notre objet $menu_item
regroupe plusieurs clés. Ici, deux vont nous intéresser :
current_item_parent
= le parent direct de la page courante.current_item_ancestor
= tous les ancêtres de la page courante.
Nous allons vérifier si l'élément possède la classe current_item_parent
. Si la condition est respectée nous ajouterons l'attribut aria-current
avec pour valeur true
. Puis $atts
se chargera d'ajouter l'attribut grâce au filtre nav_menu_link_attributes
.
Reprenons donc notre fichier menus.php
et ajoutons ceci à la fonction start_el()
:
if ( $menu_item->current_item_parent ) {
$atts['aria-current'] = $menu_item->current_item_parent ? 'true' : '';
}
Et voilà, l’attribut aria-current
est ajouté dans le code lorsque $menu_item
possède la clé current_item_parent
.
Signaler qu’un élément possède un sous-menu
L’indication d’un sous-menu est souvent indiquée via une icône (flèche descendante ou "+", par exemple).
Ok, visuellement c’est compréhensible, mais comment restituer cette information aux personnes utilisant un lecteur d’écran ? Il faut une alternative HTML en plus de l’indication visuelle.
La combinaison aria-expanded
/aria-controls
arrive à la rescousse. Je vous invite à lire l'article sur le modèle de conception "disclosure" pour plus d'explications.
Attention à utiliser l’attribut aria-expanded
sur un bouton, et non sur un lien a
. Comme le dit Adrian Roselli, il n’est pas interdit de l’ajouter sur un lien mais est à éviter car est contraire à sa fonction première qui est de renvoyer vers une page.
Adapter ce comportement dans un menu WordPress
Sur WordPress nous allons cibler tous les éléments possédants un sous-menu. Pour cela, comme expliqué dans la partie 1, nos modifications seront faites au niveau de la fonction start_el()
.
Étape 1
Si vous avez regardé en détail les clés de $menu_item
vous remarquerez qu’il n’en existe pas si l’élément possède un sous-menu.
Néanmoins, la clé classes
va nous aider dans ce cas de figure.
Rappel, retrouvez dans le codex toutes les clés de $menu_item
.
WordPress génère des classes CSS dont .menu-item-has-children
qui, comme son nom l’indique, existe lorsque l’élément possède des enfants. On pourra vérifier l’existence de cette classe pour ajouter nos correctifs.
Étape 2
Pour indiquer que le lien possède un sous-menu nous allons donc ajouter un bouton <button>
, adjacent au lien <a>
. Donc, après la balise fermante </a>
.
Dans notre walker, il faut modifier la fonction start_el()
comme ceci :
// On vérifie si l'élément possède un sous-menu via la classe CSS "menu-item-has-children"
$item_has_children = in_array( 'menu-item-has-children', $menu_item->classes );
if ( $item_has_children ) {
// On ajoute un bouton d'ouverture/fermeture du sous-menu
$item_output .= '<button type="button" aria-expanded="false" aria-controls="sub-menu-item-' . $menu_item->ID . '">
<span aria-hidden="true">+</span>
<span class="sr-only">'. __('Ouvrir le sous-menu', 'text-domain') .'</span>
</button>';
// On transmet l'id de l'élément dans la fonction start_lvl()
apply_filters( 'walker_nav_menu_start_lvl', $item_output, $menu_item, $depth, $args->parent_item=$menu_item->ID );
}
Décryptons le code du bouton :
- On lui attribue un
aria-expanded="false"
par défaut. Nous verrons ensuite le code JavaScript à ajouter pour passer la valeur àtrue
lorsque le menu sera ouvert. - L’attribut
aria-controls
correspond à la valeur de l’id
du sous-menu à afficher. Mais, aucun id n’est actuellement défini. Pas de panique c’est ce que nous allons faire juste après. Pour le moment nous allons faire en sorte que l’id soit sous la forme suivante :sub-menu-item-ID
oùID
est la valeur générée par$menu_item->ID
. - Le contenu du bouton possède un texte caché (via la classe
.sr-only
) destiné aux lecteurs d'écran afin de restituer l'action du bouton. L’icône peut ainsi être masquée aux lecteurs d’écran (aria-hidden=”true”
) car elle n’est plus que décorative.
Explication de la dernière ligne apply_filters()
:
C’est ici que nous allons transmettre à la fonction start_lvl()
l’id
du sous-menu à afficher. Et oui, rappelez-vous c’est cette fonction qui correspond à générer le début d’un sous-menu <ul>
et son contenu.
Nous allons utiliser un filtre, ici walker_nav_menu_start_lvl
, qui nous permet de transmettre un argument parent_item
avec pour valeur $menu_item->ID
. Nous pourrons alors récupérer cet argument dans la fonction start_lvl()
.
Étape 3
Récupérons donc l’id
dans la fonction start_lvl()
et retournons le dans le code HTML :
$id = 'id="sub-menu-item-' . $args->parent_item .'"';
$output .= "{$n}{$indent}<ul$class_names{$id}>{$n}";
L’attribut aria-controls
du bouton est maintenant lié à son sous-menu !
Ajouter du JavaScript pour modifier la valeur d’aria-expanded
Pour cela W3C propose un script JS pour son tutoriel “Fly-out Menus”. Nous allons utiliser ce code comme base et l'adapter pour :
- Modifier la valeur d’
aria-expanded
au clic sur le bouton :true
lorsque le sous-menu est ouvert,false
lorsqu’il est fermé. - Ajouter une classe CSS
open
qui servira à afficher, ou non, le menu via un fichier de style. - Ajouter de la documentation.
/** @type {HTMLElement[]} */
const menuItems = Array.from( document.querySelectorAll( 'li.menu-item-has-children' ) );
/**
* @param {HTMLElement} el
* @param {string} attr
* @param {any} value
*/
const setAttr = ( el, attr, value ) => el.setAttribute( attr, value );
menuItems.forEach( ( el ) => {
const button = el.querySelector( 'button' );
if ( button ) {
button
.addEventListener( 'click', function( event ) {
const parent = this.parentNode;
/**
* Si le sous-menu est ouvert :
* - on retire la classe "open" au sous-menu
* - on passe l'attribut "aria-expanded" à false
* - On indique visuellement que le bouton est fermé
*/
if ( parent && parent.classList.contains( 'open' ) ) {
parent.classList.remove( 'open' );
setAttr( parent.querySelector( 'button' ), 'aria-expanded', 'false' );
parent.querySelector( 'span[aria-hidden="true"]' ).innerHTML = '+';
} else if ( parent ) {
/**
* Si le sous-menu est fermé :
* - on ajoute la classe "open" au sous-menu
* - on passe l'attribut "aria-expanded" à true
* - On indique visuellement que le bouton est ouvert
*/
parent.classList.add( 'open' );
setAttr( parent.querySelector( 'button' ), 'aria-expanded', 'true' );
parent.querySelector( 'span[aria-hidden="true"]' ).innerHTML = '-';
}
event.preventDefault();
} );
}
} );
Commentaires
Et la touche 'esc' pour refermer le menu ?
Merci @Alyssa!
Ton tuto m'est très utile. Je reviens sur Wordpress après presqu'une décennie d'absence.
Je dois me remettre un peu à jour, il y a eu quelques changements, et surtout, j'ai oublié beaucoup de choses.
^^