Remove class and change aria-hidden value when clicking on the body

1

There is a project in the code pen called "Flex Priority Menu # 1.2" is a magnificent menu that when it has many links or the "resized" window, it is hiding and putting in a dropdown [More ...]. made with pure javascript, the dropdown opens and closes with a click on itself. But I'd also like this dropdown menu to close when clicked anywhere else in the body.

See: link

Could someone give me a light?

Thank you.

// Variables for the priority nav
const priorityContainer = document.querySelector('.js-priority-menu');
const priorityMenu = priorityContainer.querySelector('.priority');
const overflowMenu = priorityContainer.querySelector('.overflow');
const overflowTrigger = priorityContainer.querySelector('.js-overflow-trigger');
const menuItems = priorityMenu.children;
const delay = 100;
let throttled = false;
let breakout = [];
let timeout = 0;

// Variables for the trigger svg 
const mySvg = document.querySelector('.js-svg-gradient');
const myGradient = mySvg.querySelector('.linearGradient');
const colours = [];

// Helpers could be shipped as part of an Object Literal/Module
const throttle = (callback, delay, scope) => {
  if (!throttled) {
    callback.call(scope);
    throttled = true;

    setTimeout(function() {
      throttled = false;
    }, delay);
  }
};

// Initialise
const initialise = () => {
  checkNav();
  addListeners();
  makeStepGradient(colours);
}

/* return the current width of the Navigation */
const getPriorityWidth = () => {
  return priorityMenu.offsetWidth + 1;
}

/* return the break point of a menu item */
const getItemBreakPoint = item => {
  return item.offsetLeft + item.offsetWidth;
}

/* test breakpoint against menu width */
const itemBreaks = (breakPoint, menuWidth) => {
  return breakPoint > menuWidth;
}

/* test menuWidth against breakOut */
const itemBreaksOut = (index, priorityWidth) => {
  if (breakout[index] < priorityWidth) {
    return true;
  }
}

/* move item to overflow menu */
const addToOverflow = (item, itemBreakPoint) => {
  overflowMenu.insertBefore(item, overflowMenu.firstChild);
  breakout.unshift(itemBreakPoint);
}

/* remove from the overflwo menu */
const removeFromOverflow = (breaksOut) => {
  for (let item of breaksOut) {
    breakout.shift();
    priorityMenu.appendChild(item);
  }
}

/* Set button visibility */
const checkTriggerHidden = (value) => {
  if (value.toString() != overflowTrigger.getAttribute('aria-hidden')) {
    overflowTrigger.setAttribute('aria-hidden', value);
    checkNav();
  }
}

/* Check priority and overflow */
const checkNav = () => {
  /* check priorityMenu */
  let priorityWidth = getPriorityWidth();

  /* Iterate over the priority menu */
  let priorityIndex = menuItems.length;

  while (priorityIndex--) {
    let item = menuItems[priorityIndex];
    let itemBreakPoint = getItemBreakPoint(item);

    if (itemBreaks(itemBreakPoint, priorityWidth)) {
      addToOverflow(item, itemBreakPoint);
      //add colour to svg
      console.log(item);
      console.log(window.getComputedStyle(item).backgroundColor);
      let bgColour = window.getComputedStyle(item).backgroundColor;
      addToSVG(bgColour);
    };
  };

  /* iterate the overflow */
  let overflowIndex = overflowMenu.children.length;
  let breaksOut = [];

  while (overflowIndex--) {
    if (itemBreaksOut(overflowIndex, priorityWidth)) {
      breaksOut.unshift(overflowMenu.children[overflowIndex]);
      // remove colour from svg
      removeFromSVG();
    }
  }

  removeFromOverflow(breaksOut);

  /* check the trigger visibility */
  checkTriggerHidden(breakout.length == 0);
  
  makeStepGradient(colours);
}

/* Add Event listeners */
const addListeners = () => {
  window.addEventListener('resize', () => {
    //throttle
    throttle(checkNav, delay);
    //debounce
    if (timeout) {
      clearTimeout(timeout);
    }

    timeout = setTimeout(() => {
      checkNav();
    }, delay);
  })

  overflowTrigger.addEventListener('click', () => {
    overflowMenu.setAttribute('aria-hidden', overflowMenu.getAttribute('aria-hidden') === 'true' ? 'false' : 'true');
    overflowTrigger.classList.toggle('active');
  })
}

/* SVG indicator on the trigger */
/* Add to colour array */
const addToSVG = bgColour => {
  colours.unshift(bgColour);
}

const removeFromSVG = bgColor => {
  colours.shift();
}

const createStop = (offset, index, colour) => {
  let offsetValue = offset * index;
  let stop = document.createElementNS('http://www.w3.org/2000/svg', 'stop');
  stop.setAttribute('offset', offsetValue + '%');
  stop.setAttribute('stop-color', colour);
  return stop;
}

const makeStepGradient = colourArray => {
  // Calculate offset values (%)
  const colourCount = colourArray.length;
  const offset = 100/colourCount;
  
  myGradient.innerHTML = "";
  // Loop the colours
  colourArray.forEach((colour, index) => {
    let stop1 = createStop(offset, index, colour);
    let stop2 = createStop(offset, index + 1, colour);
    myGradient.appendChild(stop1);
    myGradient.appendChild(stop2);
  });
}
document.addEventListener("click", function(){
   document.querySelector("nav.overflow").setAttribute("aria-hidden", true);
   document.querySelector("button.js-overflow-trigger").classList.remove("active");
});

document.querySelector("button.js-overflow-trigger").addEventListener("click", function(e){
   e.stopPropagation();
});
var sub_a = document.querySelectorAll("nav#overflow a");

for(let item of sub_a){
    item.addEventListener("click", function(e){
       e.stopPropagation();
    });
}
/* Initilaise the menu */
initialise();
/* Structural */
.priority-menu {
  display: flex;
  flex-flow: row nowrap;
  position: relative;
}
.priority-menu nav {
  flex-basis: 100%;
  display: flex;
  text-align: center;
}
.priority-menu nav a {
  white-space: nowrap;
  text-align: center;
}
.priority-menu .priority {
  overflow: hidden;
}
.priority-menu .priority a {
  flex-basis: 100%;
  display: block;
}
.priority-menu .overflow {
  position: absolute;
  top: 100%;
  right: 0;
  display: block;
}
.priority-menu .overflow a {
  display: block;
}
.priority-menu [aria-hidden="true"] {
  display: none;
}
.priority-menu [aria-hiiden="false"] {
  display: true;
}

/* Decorative */
body {
  padding: 12px;
  background: #2B3440;
  font-family: Arial, Helvetica, sans-serif;
  color: #cbe4ed;
}

a {
  color: white;
}

:focus {
  outline: none;
}

.priority-menu nav a {
  text-decoration: none;
  padding: 15px 30px;
  color: #2B3440;
}
.priority-menu nav a.menu10 {
  background-color: #A8E6CE;
}
.priority-menu nav a.menu09 {
  background-color: #FFD3B5;
}
.priority-menu nav a.menu08 {
  background-color: #f7e651;
}
.priority-menu nav a.menu07 {
  background-color: #DCEDC2;
}
.priority-menu nav a.menu06 {
  background-color: #A8E6CE;
}
.priority-menu nav a.menu05 {
  background-color: #FF8C94;
}
.priority-menu nav a.menu04 {
  background-color: #FFAAA6;
}
.priority-menu nav a.menu03 {
  background-color: #FFD3B5;
}
.priority-menu nav a.menu02 {
  background-color: #DCEDC2;
}
.priority-menu nav a.menu01 {
  background-color: #A8E6CE;
}
.priority-menu .overflow {
  border-top: 2px solid #2B3440;
}
.priority-menu .overflow-trigger {
  position: relative;
  background: #2B3440;
  padding: 12px 10px;
  border: none;
  border-left: 2px solid #2B3440;
  font-size: 16px;
}
.priority-menu .overflow-trigger .svg-gradient {
  width: 73px;
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  fill: url(#linearGradient);
}
.priority-menu .overflow-trigger span {
  position: relative;
}
.priority-menu .overflow-trigger.active {
  color: #cbe4ed;
}
.priority-menu .overflow-trigger.active .svg-gradient {
  fill: #2B3440;
  height: 46px;
  border: 1px solid #cbe4ed;
}

.blurb {
  margin: 50px auto;
  width: 400px;
}
<div class="js-priority-menu priority-menu">
  <nav class="priority">
    <a href="#" class="menu01">Menu Item 1</a>
    <a href="#" class="menu02">Menu Item 2</a>
    <a href="#" class="menu03">Menu Item 3</a>
    <a href="#" class="menu04">Item Item 4</a>
    <a href="#" class="menu05">Menu Item 5</a>
    <a href="#" class="menu06">Item Item 6</a>
    <a href="#" class="menu07">Menu Item 7</a>
  </nav>
  <button class="js-overflow-trigger overflow-trigger" aria-hidden="true">
    <svg class="js-svg-gradient svg-gradient" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
      <defs>
      <linearGradient id="linearGradient" class="linearGradient"
        x1="0" y1="0" x2="100%" y2="0" spreadMethod="pad">
        <stop offset="50%" stop-color="#000"></stop>
      </linearGradient>
     </defs>
      <rect x="0" y="0" width="100%" height="100%" rx="0" ry="0"/>
    </svg>
    <span>More...</span>
  </button>
  <nav class="overflow" id="overflow" aria-hidden="true" data-behaviour="menu"></nav>
</div>

<div class="blurb">
  <h1>Flex Priority Menu #1.2</h1>
  <p> Resize window to trigger the overflow nav drop down</p>
  <p>Pretty much the same as <a href='https://codepen.io/kungfuyou/pen/RaJaNZ'>Flex Priority Nav #1.1</a> but with a dynamic svg on as a menu indicator.</p>
  <p>To do: open / close state for the Menu trigger.</p>
</div>
    
asked by anonymous 22.01.2018 / 21:57

1 answer

2

Add two event handlers at the end of your JS code:

This will change the submenu class to true (hiding) and remove the .active class from the More button when you click anywhere on the page:

document.addEventListener("click", function(){
   document.querySelector("nav#overflow").setAttribute("aria-hidden", true);
   document.querySelector("button.js-overflow-trigger").classList.remove("active");
});
This will delete the More button from the event handler scope above:

document.querySelector("button.js-overflow-trigger").addEventListener("click", function(e){
   e.stopPropagation();
});

EDIT 1

To include submenu links to click exceptions, add this code further:

var sub_a = document.querySelectorAll("nav#overflow a");

for(let item of sub_a){
    item.addEventListener("click", function(e){
       e.stopPropagation();
    });
}

EDIT 2

Firefox Quantum 57: Prevent the submenu from closing when you right-click the page. Replace the 1st code with this:

document.addEventListener("click", function(e){
   if(e.button != 2){
      document.querySelector("nav#overflow").setAttribute("aria-hidden", true);
      document.querySelector("button.js-overflow-trigger").classList.remove("active");
   }
});

The e.button returns 2 to the right click of the mouse. It includes a if to avoid his action.

    
23.01.2018 / 19:29