1
votes

I am trying to create a "skip navigation" link without being able to use anchors. The site is built in a peculiar way, where anchor link formatting has been re-purposed. So, I am attempting to allow people to skip the navigation by using focus. However, it isn't working.

HTML code for the skip navigation link itself:

<!-- Start Top Left in Nav Bar -->
<aside>
<a href="" onClick="javascript:skipNav()" tabindex="1" id="skipNav" role="link">Skip Navigation</a>
</aside>
<!-- End Top Left in Nav Bar -->

Code to Change the Focus

var nav = document.getElementById('#skipNav');
nav.onclick=skipNav();

function skipNav(){
     document.activeElement.blur();

    if ($('#linkHome').hasClass('current')==true)
    {
        $('#homeFocus').focus();
    }
    if ($('#linkTeam').hasClass('current')==true)
    {
        $('#teamFocus').focus();
    }
    if ($('#linkTraining').hasClass('current')==true)
    {
        $('#trainingFocus').focus();
    }
    if ($('#linkTesting').hasClass('current')==true)
    {
        $('#testingFocus').focus();
    }
    if ($('#linkRemediation').hasClass('current')==true)
    {
        $('#remediationFocus').focus();
    }
    if ($('#linkContact').hasClass('current')==true)
    {
        $('#contactFocus').focus();
    }

};

Script to Change Pages and Mark Current Page

var FluidNav = {
init: function() {
    $('a[href*="#"]').click(function(e) {
        e.preventDefault();
        if($(this).attr("href").split("#")[1]) {
            FluidNav.goTo($(this).attr("href").split("#")[1]);
        }
    });
    this.goTo("home");
},
goTo: function(page) {
    var next_page = $("#"+page);
    var nav_item = $('nav ul li a[href=#'+page+']');
    $("nav ul li").removeClass("current");
    nav_item.parent().addClass("current");
    FluidNav.resizePage((next_page.height() + 40), true, function() {
         $(".page").removeClass("current"); next_page.addClass("current"); 
    });
    $(".page").fadeOut(500);
    next_page.fadeIn(500);
    document.activeElement.blur();
    $('#'+page+'Focus').focus();

    FluidNav.centerArrow(nav_item);

},
centerArrow: function(nav_item, animate) {
    var left_margin = (nav_item.parent().position().left + nav_item.parent().width()) + 24 - (nav_item.parent().width() / 2);
    if(animate != false) {
        $("nav .arrow").animate({
            left: left_margin - 8
        }, 500, function() { $(this).show(); });
    } else {
        $("nav .arrow").css({ left: left_margin - 8 });
    }
},
resizePage: function(size, animate, callback) {
    if(size) { var new_size = size; } else { var new_size = $(".page.current").height() + 40; }
    if(!callback) { callback = function(){}; }
    if(animate) {
        $("#pages").animate({ height: new_size }, 400, function() { callback.call(); }); 
    } else {
        $("#pages").css({ height: new_size }); 
    }
}
};

 $("nav select").change(function() {
    if(this.options[this.selectedIndex].value != "#") {
        var page = this.options[this.selectedIndex].value.split("#")[1];
        FluidNav.goTo(page);
        $("html,body").animate({ scrollTop:$('#'+page).offset().top }, 700);
    }
});

Any ideas?

1
document.getElementById('#skipNav') isnt going to work... anyways, why are you using the old school when you're using jQuery? And if the question is how to do this without using an anchor tag, why is your initial sample using an a tag? This question is confusing. - Tejs
Did you know that clicking <a href="#someid">skip</a> will take the user to any element with id="someid"? - Blazemonger
Tejs & Blazemonger, the person who created the original code for this website harvested the anchor tag functionality. So, while things look like anchor links, they actually go, as Blazemonger found, to another page. This site is constructed as a single page that scrolls to the 'desired' content (ie, the content on each div defined 'page') and hides all the other content. - Tirithe

1 Answers

2
votes

Sounds like you're trying to do a 'dynamic skiplink', where you determine the target at runtime?

<a href="" onClick="javascript:skipNav()" tabindex="1" id="skipNav" role="link">Skip Navigation</a>

The problem is that when you click a link, navigation happens. A href="" doesn't prevent navigation, it just means that you end up navigating to the current page - and that's going to reset the focus. This happens after your click event handler. So even though you may correctly set the focus where you want to, it ends up being 'lost' when the page reloads.

There's a couple of ways to prevent this: one is to use href="javascript:void(0)" - href specifies where to navigate, and if it evaluates to void, the browser won't navigate at all.

A somewhat cleaner way is to tell the browser not to carry out the default action in the event handler:

function skipNav(e)
{
    e.preventDefault(); // prevent default action - link navigation - from taking place
    ...

--

Couple of other issues with the code:

Don't bother with role="link" on the A - save these attributes for when you are doing something out of the ordinary. The key thing to know is that screenreaders already know how to deal with all the standard HTML elements when they are used in a standard way. So if you are using an as a link, or a as a button, then you don't need to add a role.

But if you are creating a new control of of plain DIVs, or if you are repurposing a HTML element for a different use, then you need a role attribute to tell the screenreader how you are actually using the element.

For example, a DIV that's had an onclick handler and is behaving like a button would need role="button", otherwise a screenreader might ignore it or just say something generic like 'element'. Or if you are creating a button from an A tag, and it ends up behaving like and looking like a button from a user's point of view, then you'd need role="button" so that a screenreader will announce it as a button rather than as a link.

--

Watch for mixing plain DOM vs jQuery conventions - only jQuery uses # to find elements by ID, so use either:

var nav = document.getElementById('skipNav'); // plain DOM way, no #

or

var nav = $('#skipnav'); // jQuery way using selector

--

Watch for functions as values vs calls:

nav.onclick=skipNav();

This will actually call skipNav(), and assign the return value - null! - to onclick. Don't use ()'s when you're setting a callback. You don't need this code anyhow, since you're setting the handler using onClick in the tag anyhow.

Also, note that as your code stands, when skipNav() is called, it tries to call document.activeElement.blur() - but at that point in time - document is still loading - there's no activeElement, so calling blur() on null generates an exception - which you should see in your browser's console/debugging window.

--

Don't use .blur() - there's no need to do this:

 document.activeElement.blur();

Instead, just focus the element you want to have focus. The danger with doing .blur() is that if the code after it fails to set the focus somewhere reasonable, focus will end up getting 'lost', which is very inconvenient for a keyboard user, since they have to tab from the start of the page.

--

Javascript coding practice: don't bother with == true in the if() expressions; and unless you expect you expect more than one of the elements to have the 'current' class, use else if instead of plain if: this makes it clear in the code that you're expecting only one branch to be used.

--

Finally, make friends with the browser's debugger (F12 in most): you'll learn some of the above by putting breakpoints in the event handler and your initialization code, and stepping through it to ensure it's behaving as you expect.