AGE
AGE

Reputation: 3792

JQuery - Focus not working when navigating via Tab in Firefox and IE

I have a simple navigation bar which I intend to use above all keyboard accessible navigation. The navigation bar is meant to work like so:

Here is the jsFiddle I developed.

To keep things simple, please focus on the JavaScript code, as the HTML code cannot change due to requirement reasons outside my control. I believe that the issue lies in how Firefox/IE is handling the focus events differently than in Chrome.

$(document).ready(function(){
    var utilitymenu = new menuBar("#utility-nav"),
        navInner = new menuBar("#nav-inner"),
        mainHeader = $(".main-header");

    // Adding tabindex to all clickable anchor links
    mainHeader.find("a").each(function(i, elem){
        $(elem).attr("tabindex", i+1);
    });
});

function menuBar(id){
    var linksBlock = id + " > li",
        linksBlockSelector = " > li",
        submenuHeader = linksBlock + " > a",
        submenu = linksBlock + " div[class*='sub-menu']",
        submenuSelector = "div[class*='sub-menu']",
        textBlock = submenu + " > ul",
        textBlockSelector = submenuSelector + " > ul",
        submenuElem = textBlock + " > li a",
        keepMenuClosed = false;

    // Adding ARIA attributes to the different sections of the navigation menus
    $(linksBlock).attr({
        "role": "navigation",
        "aria-haspopup": "true",
        "aria-labelledby": "menu-title",
        "aria-describedby": "menu-description"
    });
    $(submenuHeader).attr({
        "aria-haspopup": "true",
        "id": "menu-title"
    });
    $(submenuHeader).each(function(i, elem){
        $(elem).attr("aria-label", $(elem).text().trim());
    });
    $(submenu).attr({
        "tabindex": "-1",
        "id": "menu-description"
    });
    $(textBlock).attr({
        "aria-expanded": "false",
        "aria-hidden": "true"
    });

    // Hides visible menus when clicking outside the menu area
    $(document).click(function(event) {
        if(!$(event.target).closest().length){
            $(submenu + ":visible").hide().find(textBlock).attr("aria-expanded", "false");
            $(submenu + ":visible").hide().find(textBlock).attr("aria-hidden", "true");
        }
    });

    // Drop Down Menu event handler (not inner elements)
    $(linksBlock)
    .focus(function(){
        if($(this).find(submenuSelector).is(":hidden") && !keepMenuClosed){
            $(this).find(submenuSelector).toggle();
            $(this).find(textBlockSelector).attr("aria-expanded", "true");
            $(this).find(textBlockSelector).attr("aria-hidden", "false");
            keepDropdownClosed = false;
        }
        else if($(this).find(submenuSelector).is(":visible")){
            $(this).find(submenuSelector).toggle();
            $(this).find(textBlockSelector).attr("aria-expanded", "false");
            $(this).find(textBlockSelector).attr("aria-hidden", "true");
        }
    })
    .mouseover(function(){
        $(this).find(submenuSelector).show();
        $(this).find(textBlockSelector).attr("aria-expanded", "true");
        $(this).siblings().find(submenuSelector).hide();
        $(this).siblings().find(textBlockSelector).attr("aria-expanded", "false");
        $(this).find(textBlockSelector).attr("aria-hidden", "true");
    })
    .mouseout(function(){
        $(this).find(submenuSelector).hide();
        $(this).find(textBlockSelector).attr("aria-expanded", "false");
        $(this).find(textBlockSelector).attr("aria-hidden", "true");
    })
    .keydown(function(event){
        switch(event.keyCode){
            //tab key
            case 9:
                keepMenuClosed = true;
                if($(this).find(submenuSelector).is(":visible")){
                    $(this).find(submenuSelector).toggle();
                    $(this).find(textBlockSelector).attr("aria-expanded", "false");
                    $(this).find(textBlockSelector).attr("aria-hidden", "true");
                }
                else if($(this).find(submenuSelector).is(":hidden")){
                    $(this).find(submenuSelector).toggle();
                    $(this).find(textBlockSelector).attr("aria-expanded", "true");
                    $(this).find(textBlockSelector).attr("aria-hidden", "false");
                }
                break;

            // esc key
            case 27:
                if($(this).find(submenuSelector).is(":visible")){
                    $(this).find(submenuSelector).toggle();
                    $(this).find(textBlockSelector).attr("aria-expanded", "false");
                    $(this).find(textBlockSelector).attr("aria-hidden", "true");
                    $(this).closest(linksBlock).focus();
                }
                break;

            // key left
            case 37:
                if($(this).find(submenuSelector).is(":hidden")){
                    $(this).prev().find(" > a").focus();
                    keepMenuClosed = true;
                }
                break;

            // key right
            case 39:
                if($(this).find(submenuSelector).is(":hidden")){
                    $(this).next().find(" > a").focus();
                    keepMenuClosed = true;
                }
                break;

            // key up/down
            case 38, 40:
                event.preventDefault();
                $(this).find(submenuSelector).show();
                break;
        }
    });
    // Sub Menu Elements keyboard handler
    $(submenuElem)
    .keydown(function(event){
        switch(event.keyCode){
            // tab key
            case 9:
                $(this).parent().next().find("a").focus();
                break;
            // esc key
            case 27:
                if($(this).closest(submenuSelector).is(":visible")){
                    $(this).closest("div").siblings("a ").focus();
                }
                break;
            // key up
            case 38:
                event.preventDefault();
                $(this).parent().prev().find("a").focus();
                break;
            // key down
            case 40:
                event.preventDefault();
                $(this).parent().next().find("a").focus();
                break;
        }
    });
}

TLDR

All I want to do is understand why the navigation works only in Chrome, but not in IE and Firefox. What am I doing wrong/not doing at all here? I have been looking around at known issues with IE/FF focus, preventDefault but to no avail. I do not think my ARIA code is causing the problem, but I am ready to explore all suggestions!

EDIT

After @Adam's suggestion, I added the following code to show the issues I have on Firefox/IE:

$(this).keydown(function(e){
    if(e.keyCode === 9){
        alert($(':focus').toArray());
    }
});

It showed the root of the issues. I am currently working to modify my code to better differentiate how I listen to my keyboard strokes; as well as to show better listen to when my elements are focused on.

Upvotes: 2

Views: 1785

Answers (1)

Adam
Adam

Reputation: 18807

When you hide an element, it losts the focus

When you press the tab key over a submenuElem the event is also intercepted by linksBlock

For that specific reason, when you use the following line

 $(this).parent().next().find("a").focus();

it fires the focus() event and then executes the toggle which will hide the submenu. Here the focus is lost.

$(linksBlock)
.focus(function(){
   [...]

   else if($(this).find(submenuSelector).is(":visible")){
   $(this).find(submenuSelector).toggle();

And then after that, a second event is fired to the main blocks which will show the hidden element without focus set:

$(linksBlock)
.keydown(function(event){
     [...]
            else if($(this).find(submenuSelector).is(":hidden")){
                $(this).find(submenuSelector).toggle();

Upvotes: 1

Related Questions