Ad

Hidden Div Tab-through (accessibility)

- 1 answer

I have a section where I have hidden divs that are being displayed on click. What I am trying to achieve is accessibility compliance where if I tab through and open one of the sections it will tab through the inner content of the section and then return to where it was left off initially.

Eg. If I tab through and open section 1 I would like to be able to tab through the inner contents of section 1 and after that go back to the button that opens up section 2, and so forth...

I have created a fiddle with my html / script https://jsfiddle.net/rjvw915r/10/

HTML Example:

<div id="dropdown-menus">
  <div id="section1-drop" class="drop-section hidden-panel">
    <a target="_blank" rel="nofollow noreferrer" href="#">Link 1</a>
    <a target="_blank" rel="nofollow noreferrer" href="#">Link 2</a>
  </div>

  <div id="section2-drop" class="drop-section hidden-panel">
    <a target="_blank" rel="nofollow noreferrer" href="#">Link 1</a>
    <a target="_blank" rel="nofollow noreferrer" href="#">Link 2</a>
  </div>
</div>

<ul id="dropdown-links" class="menu main-tabs">
  <li>
    <a class="panel-btn" 
       target="_blank" rel="nofollow noreferrer" href="javascript:dropMenu('section1');" 
       id="drop-link-section1">section 1</a>
  </li>
  <li>
    <a class="panel-btn" 
       target="_blank" rel="nofollow noreferrer" href="javascript:dropMenu('section2');" 
       id="drop-link-section2">section 2</a>
  </li>
</ul>

JavaScript:

function dropMenu(menusection) {
    if ( !($('#dropdown-menus #' + menusection + '-drop').is(':hidden')) ) {

        // Select panel is open. Closes the panel.
        $('#dropdown-menus #' + menusection + '-drop').slideUp(500);
        $('#dropdown-menus #' + menusection + '-drop').removeClass('active');
        $('a#drop-link-' + menusection).removeClass('active');

        // Scroll to top of buttons.
        var aid = $("#dropdown-links");
        $('html,body').animate({scrollTop: aid.offset().top-480},400,function(){});

    } else if ( $('#dropdown-menus .drop-section').hasClass('active') 
                && $('#dropdown-menus #' + menusection + '-drop').is(':hidden') ) {

        // Another panel is open. 
        // Closes currently open panel and opens selected panel.
        $('.menu a.active').removeClass('active');
        $('#dropdown-menus .active').slideUp(500,function(){
            $('#dropdown-menus #' + menusection + '-drop').slideDown(500);
            $('#dropdown-menus #' + menusection + '-drop').addClass('active');
            $('a#drop-link-' + menusection).addClass('active');
        }).removeClass('active');

        // Scroll to top of panel.
        var aid = $("#dropdown-menus");
        $('html,body').animate({scrollTop: aid.offset().top-155},400,function(){});

    } else {

        // No panel currently open. Opens selected panel.
        $('#dropdown-menus #' + menusection + '-drop').slideDown(500);
        $('#dropdown-menus #' + menusection + '-drop').addClass('active');
        $('a#drop-link-' + menusection).addClass('active');

        // Scroll to top of panel.
        var aid = $("#dropdown-menus");
        $('html,body').animate({scrollTop: aid.offset().top-155},400,function(){});
    }
}

Thank you!

Ad

Answer

You can use a click event to perform javascript actions when the link is clicked or enter is pressed.

<div id="dropdown-menus">
  <div id="section1-drop" class="drop-section hidden-panel">
    <a target="_blank" rel="nofollow noreferrer" href="#">Link 1</a>
    <a target="_blank" rel="nofollow noreferrer" href="#">Link 2</a>
  </div>

  <div id="section2-drop" class="drop-section hidden-panel">
    <a target="_blank" rel="nofollow noreferrer" href="#">Link 3</a>
    <a target="_blank" rel="nofollow noreferrer" href="#">Link 4</a>
  </div>
</div>

<ul id="dropdown-links" class="menu main-tabs">
  <li>
    <a class="panel-btn" 
       data-section="section1-drop"
       id="drop-link-section1">section 1</a>
  </li>
  <li>
    <a class="panel-btn" 
       data-section="section2-drop"
       id="drop-link-section2">section 2</a>
  </li>
</ul>

Above, I have removed the href attribute and added a data-section element which specifies which section this link activates. This is more semantic and readable, and now we can set up our handlers within jQuery.

Define the focus handler:

// Setup the focus handler for the root links
$('.panel-btn').on('click', function(e) {
    var link = $(this);
    var section = link.attr('data-section');

    // Pass in the current link and the section name to dropMenu
    dropMenu($(this), section);
});

I've cleaned up your drop menu function a bit to clarify what's happening

function dropMenu(link, menusection) {

  // Get the section specified by the menusection id
  var section = $('#' + menusection);

  // Get the open section by checking for the active class
  var openSection = $('#dropdown-menus').find('.drop-section.active');

  // Anonymous function for activating a section and focussing the 
  // first link within the section
  var showSection = function() {
    // Slide the section into view
    section.slideDown(500).addClass('active');

    // Focus the first link in the section.
    section.find('a').first().focus();
  }

  // If there is a section open, we want to slide it up first,
  // then call showSection when that has completed.

  if(openSection.length > 0) {

    // If the open section is the one that was clicked, we want
    // to simply close that section rather than open another
    if(openSecion[0] === section[0]) {
        openSection.slideUp(500).removeClass('active');
    }
    else {
        openSection.slideUp(500, showSection).removeClass('active');
    }
  }

  // Otherwise, we can just show the section straight away.
  else {
    showSection();
  }
}

This is all pretty standard, but the you now need to handle moving between sections as you would expect to happen natively using the tab key. The problem is that since your 'drop sections' are above the root links, tabbing from the last link in the drop section will always take you back to section 1 since that is the next tabbable element in the document. To prevent this, we need to work around some of the default functionality and implement our own.

First, you'll need to configure a keydown handler to catch the tabbing before it happens

// Setup a keydown handler to handle tabbing on section links
$('.drop-section').on('keydown', 'a', function(e) {
  if(e.keyCode === TAB_KEY) {
    var link = $(this);

    // Flag determins whether to prevent default tab behaviour
    var preventDef = false;

    // Travel to previous link on SHIFT + TAB
    if(e.shiftKey) {
      preventDef = travelPrevious(link);
    }

    // Travel to next link on TAB
    else {
      preventDef = travelNext(link);
    }

    // Prevent default focus behaviour
    if(preventDef) {
      e.preventDefault();
    }
  }
});

Then we need functions to handle next tab, and previous tab

// Handles travelling to the next link
function travelNext(link) {
  var next = link.next();

  if(next.length > 0) {
    // Continue with default behaviour of moving to next link
    return false;
  }

  // Drop section parent ID
  var parentId = link.parents('.drop-section').attr('id');

  // Root link whose data-section attribute matches parent ID
  var rootLink = $('.panel-btn[data-section=' + parentId + ']').parent();

  // Next root link to move to.
  var nextLink = rootLink.next().find('a');

  // Focus on the next root link, which will fire dropMenu for 
  // the next section.
  nextLink.focus();

  // Prevent default behaviour
  return true;
}

function travelPrevious(link) {
  var prev = link.prev();

  if(prev.length > 0) {
    // Continue with default behaviour of moving to previous link
    return false;
  }

  // Drop section parent ID
  var parentId = link.parents('.drop-section').attr('id');

  // LI container for Root link whose data-section attribute matches parent ID
  var rootLink = $('.panel-btn[data-section=' + parentId + ']').parent();

  // Previous root link to move to.
  var prevLink = rootLink.prev().find('a');

  // Focus on the previous root link, which will fire dropMenu for 
  // the previous section.
  prevLink.focus();

  // Prevent default behaviour
  return true;
}

This is still not a complete solution because it will not handle reaching the end or beginning of all sections. It should be a start and I believe it answers your first question. If you other questions about this, you should ask them seperately.

Here is a fiddle example https://jsfiddle.net/rjvw915r/17/

UPDATE

If your links are contained in a non-standard way within the drop-section, the method above won't quite work, as it uses the next and prev functions which look for direct siblings. If each a tag is contained in a div or li wrapper, then they have no direct siblings.

We can work around this by looking back to a known parent element, and finding all siblings via a selector. In the keydown function, we will add some additional code to retrieve these elements, and pass them into the travel functions.

$('.drop-section').on('keydown', 'a', function(e) {
    if(e.keyCode === TAB_KEY) {
        var link = $(this);

        // Retrieve all available links within the parent.
        var linkCollection = link.parents('.drop-section').find('a');

        ...

In each of the travel functions we pass these links in

preventDef = travelPrevious(link, linkCollection);

... 

preventDef = travelNext(link, linkCollection);

By finding the index of the current link, we can ascertain whether it has any siblings before or after it.

In the travelNext function we check to see if there are any links after it

// Find where the current link sits within the collection
var linkIndex = linkCollection.index(link);
var nextIndex = linkIndex + 1;

if(nextIndex < linkCollection.length) {
  // Continue with default behaviour of moving to next link.
  return false;
}

In the travelPrevious function we check to see if there are any links before it

// Find where the current link sits within the collection
var linkIndex = linkCollection.index(link);
var nextIndex = linkIndex - 1;

if(nextIndex >= 0) {
  // Continue

In each of the travel functions we pass these links in

preventDef = travelPrevious(link, linkCollection);

... 

preventDef = travelNext(link, linkCollection);

By finding the index of the current link, we can ascertain whether it has any siblings before or after it.

In the travelNext function we check to see if there are any links after it

// Find where the current link sits within the collection
var linkIndex = linkCollection.index(link);
var nextIndex = linkIndex + 1;

if(nextIndex < linkCollection.length) {
  // Continue with default behaviour of moving to next link.
  return false;
}

In the travelPrevious function we check to see if there are any links before it

// Find where the current link sits within the collection
var linkIndex = linkCollection.index(link);
var nextIndex = linkIndex - 1;

if(nextIndex >= 0) {
  // Continue with default behaviour of moving to previous link
  return false;
}

See updated fiddle https://jsfiddle.net/rjvw915r/18/

Ad
source: stackoverflow.com
Ad