Categories
JavaScript

Quick Button Event Handler

Imagine that in our header, we have a row of buttons in a DIV, and underneath that we have a NAV with three UL-based submenus, each with a class to identify them:

<header>
        <h1>Lorem, ipsum dolor.</h1>

        <div class="buttons">
            <button class="btn1">menu 1</button>
            <button class="btn2">menu 2</button>
            <button class="btn3">menu 3</button>
        </div>

        <nav class="nav-main">
            <ul class="menu1">
                    <li><a href="#">Lorem.</a></li>
                    <li><a href="#">Aut!</a></li>
                    <li><a href="#">Repudiandae.</a></li>
                    <li><a href="#">Quibusdam!</a></li>
            </ul>
            <ul class="menu2">
                
                    <li><a href="#">Dignissimos?</a></li>
                    <li><a href="#">Alias.</a></li>
                    <li><a href="#">Quia?</a></li>
                    <li><a href="#">Quos!</a></li>
                
            </ul>
            <ul class="menu3">
                    <li><a href="#">Atque!</a></li>
                    <li><a href="#">Deleniti!</a></li>
                    <li><a href="#">Provident?</a></li>
                    <li><a href="#">Voluptate.</a></li>   
            </ul>
        </nav>

    </header>

To make a really quick script to hide or reveal a menu on its associated button click, make a new js file and link to it.

If you’re doing this in WordPress, make sure to enqueue the script (and make sure that the wp_footer hook is in the footer.php file so that your script can be written into the document).

Visually Hidden Style

In your stylesheet, add the visually-hidden code we use for accessibly hiding elements:

.visually-hidden {
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  height: 1px;
  overflow: hidden;
  position: absolute;
  white-space: nowrap;
  width: 1px;
}

With this quick technique, we will hide the menus with this visually-hidden class, and reveal menus on button clicks by removing that class from the associated menu.

Apply Visually Hidden Class to Menus

So, in your JavaScript file, select the DIV that holds the buttons, and the submenus:

const btnGroup = document.querySelector('.buttons'); 
const menus = document.querySelectorAll('.nav-main ul');

Now, add the visually-hidden class to each of the selected submenus. A forEach loop is an easy way to do this. We apply that method to the menus nodelist, referencing each individual menu in turn and adding the class via the classList property:

menus.forEach(menu => {
    menu.classList.add('visually-hidden')
});

Now, all our submenus should be hidden.

Add EventListener to Menus Div

Next, let’s add an EventListener to the DIV that is holding our individual buttons. We will pass the click event to the event handler, so that we can use properties of the event to determine which button was clicked. Specifically, we will use the event.target property to indicate which button is clicked on.

btnGroup.addEventListener('click', function(e){ 
    console.log(e.target);
});

Now reload the page, click a button, and look in the console:

In other words, we have determined which button is clicked on.

Next, we will use the number at the end of each clicked button class in order to select the corresponding menu. To do that, we need to get the last character in the classname. With strings, we can do that the same way we get the last character in an array: using the [ ] notation, with an index value of the string’s length minus one. Because arrays’ and strings’ first index is zero, the last index is equal to the length minus one.

btnGroup.addEventListener('click', function(e){ 
    const btn = e.target.className;
    const btnNum = btn[btn.length-1];
    console.log(btnNum);
});

Test the page: click a few buttons and then look in the console:

Next, we need to select the corresponding submenu. To do that, we can construct its class dynamically, passing the class name to the query selector. Here we can use backticks and template literals in the form of ( which take the form ${variable name} ) to make passing the value simpler.

Having selected the corresponding submenu, we can then toggle its visually-hidden class.

btnGroup.addEventListener('click', function(e){ 

 const btn = e.target.className;
 const btnNum = btn[btn.length-1];

 const menuTarget = nav.querySelector(`.menu${btnNum}`);
 menuTarget.classList.toggle('visually-hidden');
});

Test your page or site. You should have a simple working menu.

Of course, what we’ve done here has a few drawbacks:

  • there’s no logic to hide other menus when a new one is show, so all three menus could be active at the same time.
  • there’s no transitions. If we wanted transitions, toggling visually-hidden is not the ideal way to do. Instead we would probably want to use css transforms and possibly z-index to hide elements and reveal them on click.

But for a basic menu, this will work.

Refining the Menu Behaviour

If we want to make sure that there is only one menu showing at any one time, we’ll need to use a simple condition to the EventListener we attached to the DIV holding our buttons.

Remember that inside this event handler, we first find the menu (reqMenu) that corresponds to the button clicked (btn).

btnGroup.addEventListener('click', function(e){ 

const btn = e.target.className;
const btnNum = btn[btn.length-1];
const reqMenu = document.querySelector(`.menu${btnNum}`);

// Select the active menu (if there is one)
const activeMenu = document.querySelector('.active-menu');

// No menu currently active? Set menu to ".active-menu"
if (activeMenu == null)  {
    reqMenu.classList.add('active-menu');
}
// Clicked button corresponds to already-active menu?
// Remove that menu's active-menu class to hide it again. 
else if (reqMenu.classList.contains('active-menu')){
    reqMenu.classList.remove('active-menu');
}

// Otherwise, remove active-menu class from currently
// active menu and add that class to the menu 
// corresponding to the clicked button.
else {
    activeMenu.classList.remove('active-menu');
    reqMenu.classList.add('active-menu');
}

});

Finally, we need to make one additional CSS style to take advantage of our moving active-menu class.

This style, which just resets all the visually-hidden styles back to element defaults, will apply when an element has both the visually-hidden and active-menu styles.

.active-menu.visually-hidden {
    clip: none;
    clip-path: none;
    height: auto;
    overflow: normal;
    position: static;
    width: auto;
}

This, of course, requires us to know what the default values of the visually-hidden properties actually are.

Another approach could be to use the unset value on all the properties:

.active-menu.visually-hidden {
    clip: unset;
    clip-path: unset;
    height: unset;
    overflow: unset;
    position: unset;
    width: unset;
}

Either way, when we attach the active-menu class to the menu, we set visually-hidden (on that element only) to no longer mean anything. Thus no longer hiding it.