Les listbox et la navigation au clavier

Introduction

Pour cet article, nous repartons de l’exemple sur les listbox avec cases à cocher. L’objectif est d’améliorer la navigation au clavier afin de se rapprocher des spécifications design patterns du W3C/WAI.

Les patterns ARIA

Le W3C maintient en ligne des spécifications qui décrivent comment doivent se comporter les composants ARIA : WAI-ARIA Authoring Practices 1.1.

La spécification concernant les listbox nous apprend que :

Exemple

Implémentation

La fonctionnalité permettant de sélectionner automatiquement l’élément dont l’utilisateur tape les premières lettres n’est pas forcément simple à implémenter. Voici un exemple qui utilise XPath.

La première chose à faire est d’écouter le clavier lorsque le focus est positionné sur la liste. En fonction de la touche pressée, on effectue une action (déplacement de l’élément sélectionné, sélection d’un élément…).


    …
    // On keydown
    $("[role=listbox]").on("keydown", function (e) {            
        var currentItem = $(this).find("[aria-selected=true]");                      
        switch (e.keyCode) {
            case 9: // TAB
                break;
            case 36: // home                    
                …
                e.preventDefault();
                break;                                                            
            case 35: // end
                …
                e.preventDefault();
                break;                     
            case 38:  // Up arrow
                …
                e.preventDefault();
                break;
            …
      

Pour les autres touches qui ne servent pas à effectuer une action sur la liste, c’est à dire les lettres et les chiffres, on les mémorise pour former une chaîne de caractère. Lorsque l’utilisateur ne tape pas de touche pendant quelques millisecondes (500 dans notre exemple), on recherche si un élément de liste commence par la chaîne tapée et on sélectionne cet élément de liste.


    …
    case 65: // Ctrl + A
        if (e.ctrlKey) {
            …
        }               
    default:  // Search item starts with  
        // Cancel current timer                                                                  
        clearTimeout(timer);

        // Create search string
        searchString += e.key;
        var self = this;

        // Set a timer to search item after 500ms
        timer = setTimeout(function(){
            // Search item
            var xpath = "li/span[starts-with(translate(text(), ’ABCDEFGHIJKLMNOPQRSTUVWXYZ’, ’abcdefghijklmnopqrstuvwxyz’), ’" + searchString + "’)]";
            var matchingElement = document.evaluate(xpath, self, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;

            // Reset search string
            searchString = "";

            // If an item is found…
            if (matchingElement) {                            
                currentItem.attr("aria-selected", "false");
                $(matchingElement).parent().attr("aria-selected", "true").focus().addClass("active");
            }                        
        }, 500);           

        e.preventDefault();          
      

Pour rechercher l’élément dans la liste, nous utilisons la requête XPath suivante : /li/span[starts-with(text(), "la chaine à rechercher")]. De plus, pour s’affranchir de la casse des caractères, nous utilisons la fonction translate.

Enfin pour la compatibilité avec Internet Explorer, nous utilisons le polyfill Xpath de Google qu’il suffit d’inclure dans la page et de charger à l’aide de la ligne de code wgxpath.install(); au chargement de la page.