Sanjib Adhya
Sanjib Adhya

Reputation: 5

Web accessible navigation

I'm stuck while implementing drop down for each individual list item on focus/hover on any of them, now on hovering over any single list item all dropdowns are getting displayed.

This is my pen: https://codepen.io/apeandme/pen/GRZOxJQ

and the JS:

// main navigation interaction

'use strict';

var navbar;
var triggerContainer;
var triggerLink;
var toggleButton;
var lastMenuItem;

var mouseOutTimer; // timer used to delay hiding of menu after mouse leaves
var mouseOutHideDelay = 0; // time (in ms) before menu is closed after mouse leaves
var menuVisible = false;

var i = 0;

window.addEventListener('DOMContentLoaded', function(e) {
  navbar = document.querySelector('nav');
  triggerContainer = document.querySelectorAll('nav > ul > li.with-dd');
  triggerLink = document.querySelectorAll('nav > ul > li.with-dd > a');
  toggleButton = document.querySelectorAll('nav > ul > li.with-dd > .toggle-button');
  lastMenuItem = document.querySelectorAll('nav > ul > li.with-dd > ul > li:last-of-type > a');

  // Show the menu on mouse hover of the trigger
  triggerContainer.forEach(item => {
    item.addEventListener('mouseover', function(e) {
      showMenu();
      clearTimeout(mouseOutTimer);
    });
  });



  // Hide the menu when mouse hover leaves both the trigger and menu fly-out, but only after a short delay to help people jerky mouse movements (like those using head/eye trackers)
  triggerContainer.forEach(item => {
    item.addEventListener('mouseout', function(e) {
      mouseOutTimer = setTimeout(function() {
        hideMenu();
      }, mouseOutHideDelay);
    });
  });

  // Hide the menu when the user tabs out of it
  triggerContainer.forEach(item => {
    item.addEventListener('keydown', triggerKeydownHandler);
  });

  // Toggle the menu when the trigger is activated
  toggleButton.forEach(item => {
    item.addEventListener('click', toggleMenu);
  });

  // Close the menu when the user activates something outside the navbar.
  document.body.addEventListener('click', handleBodyClick);
});


/**
  Menu visibility
**/
function showMenu() {
  triggerLink.forEach(item => {
    item.setAttribute('aria-expanded', true);
  });

  toggleButton.forEach(item => {
    item.setAttribute('aria-expanded', true);
  });
  menuVisible = true;
}

function hideMenu() {
  triggerLink.forEach(item => {
    item.setAttribute('aria-expanded', false);
  });
  toggleButton.forEach(item => {
    item.setAttribute('aria-expanded', false);
  });
  menuVisible = false;
}

function toggleMenu() {
  if (menuVisible) {
    hideMenu();
  } else {
    showMenu();
  }
}


/**
  Event handlers
*/
function handleBodyClick(e) {
  if (!navbar.contains(e.target)) {
    hideMenu();
  }
}

function triggerKeydownHandler(e) {
  // Hide the menu a keyboard user tabs out of it or presses Escape
  if ((e.key === 'Tab' && !e.shiftKey && e.target === lastMenuItem) || e.key == 'Escape') {
    hideMenu();

    // Move focus back to the menu toggle button if Escape was pressed
    if (e.key == 'Escape') {
      toggleButton.focus();
    }
  }
}

Upvotes: -2

Views: 57

Answers (2)

Sanjib Adhya
Sanjib Adhya

Reputation: 5

Thanks for the help. Have changed the code , for both showMenu and hideMenu and for mouseover and mouseout events, now individual drop downs are showing on hovering over single menu item, but the dropdowns are hiding soon enough I'm reaching to the last menu item inside the dropdown. Am I missing anything and currently this error is in the console whenever hovering over the last menu item inside the drop-down-

Uncaught TypeError: Cannot read property 'setAttribute' of null at hideMenu (main.js:68) at main.js:38

    //main navigation interaction

"use strict";

var navbar;
var triggerContainer;
var triggerLink;
var toggleButton;
var lastMenuItem;

var mouseOutTimer; // timer used to delay hiding of menu after mouse leaves
var mouseOutHideDelay = 1000; // time (in ms) before menu is closed after mouse leaves
var menuVisible = false;

window.addEventListener("DOMContentLoaded", function (e) {
    navbar = document.querySelector("nav");
    triggerContainer = document.querySelectorAll("nav > ul > li.with-dd");
    triggerLink = document.querySelectorAll("nav > ul > li.with-dd > a");
    toggleButton = document.querySelectorAll(
        "nav > ul > li.with-dd > .toggle-button"
    );
    lastMenuItem = document.querySelectorAll(
        "nav > ul > li.with-dd > ul > li:last-of-type > a"
    );

    // Show the menu on mouse hover of the trigger
    triggerContainer.forEach((item) => {
        item.addEventListener("mouseover", function (e) {
            showMenu(e);
            clearTimeout(mouseOutTimer);
        });
    });

    // Hide the menu when mouse hover leaves both the trigger and menu fly-out, but only after a short delay to help people jerky mouse movements (like those using head/eye trackers)
    triggerContainer.forEach((item) => {
        item.addEventListener("mouseout", function (e) {
            mouseOutTimer = setTimeout(function () {
                hideMenu(e);
            }, mouseOutHideDelay);
        });
    });

    // Hide the menu when the user tabs out of it
    triggerContainer.forEach((item) => {
        item.addEventListener("keydown", triggerKeydownHandler);
    });

    // Toggle the menu when the trigger is activated
    toggleButton.forEach((item) => {
        item.addEventListener("click", toggleMenu);
    });

    // Close the menu when the user activates something outside the navbar.
    document.body.addEventListener("click", handleBodyClick);
});

/**
  Menu visibility
**/
function showMenu(e) {  
    e.target.setAttribute("aria-expanded", true);
    e.target.nextElementSibling.setAttribute("aria-expanded", true);    
    menuVisible = true;
}

function hideMenu(e) {    
    e.target.setAttribute("aria-expanded", false);
    e.target.nextElementSibling.setAttribute("aria-expanded", false);    
    menuVisible = false;
}

function toggleMenu() {
    if (menuVisible) {
        hideMenu(e);
    } else {
        showMenu(e);
    }
}

/**
  Event handlers
*/
function handleBodyClick(e) {
    if (!navbar.contains(e.target)) {
        hideMenu();
    }
}

function triggerKeydownHandler(e) {
    // Hide the menu a keyboard user tabs out of it or presses Escape
    if (
        (e.key === "Tab" && !e.shiftKey && e.target === lastMenuItem) ||
        e.key == "Escape"
    ) {
        hideMenu();

        // Move focus back to the menu toggle button if Escape was pressed
        if (e.key == "Escape") {
            toggleButton.focus();
        }
    }
}

Upvotes: 0

Jeremy Thille
Jeremy Thille

Reputation: 26400

Instead of opening all the dropdowns at once when one is hovered, you need to pass the 'hover' event to the function, in order to know which one of the triggers were hovered :

triggerContainer.forEach((item) => {
    item.addEventListener("mouseover", function (event) { // catch the 'hover' event
        showMenu(event); // pass the event to your function
        clearTimeout(mouseOutTimer);
    });
});


function showMenu(event) {
  event.target.setAttribute("aria-expanded", true); // know which element whas hovered
  event.target.nextElementSibling.setAttribute("aria-expanded", true); // open the corresponding dropdown
  menuVisible = true;
}

Upvotes: 0

Related Questions