Showing subcategories when clicked using only JavaScript

The solution can be found below

I have created a dropdown menu that currently shows the submenu on hover using the following CSS:

.main-menu ul li:hover > ul { display: block; }

However, my design requires the submenu to appear on click and stay visible until the user clicks elsewhere on the page (or on the close button in this case). From what I've gathered from similar questions like css hover to onclick conversation, it seems I need to utilize JavaScript's setAttribute to add the class active on click:

.main-menu ul li.active > ul { display: block; }

I've seen examples where people achieve this using the code snippet below which targets elements by their ID. However, since I cannot use the same ID for all my submenus, I'm looking for a more efficient way to target these elements without duplicating functions for each submenu. I've tried using classes and querySelectorAll() to pinpoint the specific tags but seem to be missing something. Any guidance would be greatly appreciated.

window.onload = init;
function init()
{
    document.getElementById('openSubmenu').onclick   = openSubmenu;
    document.getElementById('closeSubmenu').onclick  = closeSubmenu;
}

function openSubmenu()
{
    document.getElementById('openSubmenu').setAttribute('class','active');
}

function closeSubmenu()
{
    document.getElementById('openSubmenu').removeAttribute('class','active');
}

HTML:

<div id="closeSubmenu">Close submenu</div>
<nav>
  <div class="main-menu">
    <ul>
      <li><a href="#">Home</a></li>
      <li id="openSubmenu"><a href="#">Dropdown 1</a>
        <ul>
          <li><a href="#">First tier</a></li>
          <li><a href="#">First tier</a></li>
        </ul>  
      </li>
      <li><a href="#">Dropdown 2</a>
        <ul>
          <li><a href="#">First tier</a></li>
          <li><a href="#">First tier</a></li>
          <li><a href="#">First tier</a></li>
       </ul>
      </li>
      <li><a href="#">Link</a></li>
      <li><a href="#">Link</a></li>
    </ul>
  </div>
</nav>

CSS:

.main-menu {
  width: 100%;
  left: 0;
}

.main-menu ul {
  position: relative;
  text-align: center;
}

.main-menu ul li { display: inline-block; }

.main-menu li a {
  line-height: 40px;
  padding: 10px 20px;
  text-transform: uppercase;
  text-decoration: none;
  color: #494949;
}

/* Hide dropdowns by default */

.main-menu ul ul {
  width: 100%;
  position: absolute;
  display: none;
  left: 0;
  background-color: #c6c6c6;
}

/* Display Dropdowns on Hover */

.main-menu ul li:hover > ul { display: block; }

/* Display Dropdowns on Click */

.main-menu ul li.active > ul { display: block; }

SOLUTION

Below is the updated code where the relevant submenus now open on click, close when you click again on the submenu name, and also close when you click on a regular "non-submenu" element.

HTML

<nav>
    <ul>
      <li class="menuItem"><a href="#">Home</a></li>
      <li class="submenu"><a href="#">Dropdown 1</a>
        <ul>
          <li><a href="#">First tier</a></li>
          <li><a href="#">First tier</a></li>
        </ul>  
      </li>
      <li class="submenu"><a href="#">Dropdown 2</a>
        <ul>
          <li><a href="#">First tier</a></li>
          <li><a href="#">First tier</a></li>
          <li><a href="#">First tier</a></li>
       </ul>
      </li>
      <li class="menuItem"><a href="#">Link</a></li>
      <li class="menuItem"><a href="#">Link</a></li>
    </ul>
</nav>

CSS

nav {
  width: 100%;
  left: 0;
}

nav ul {
  position: relative;
  text-align: center;
}

nav ul li { display: inline-block; }

nav li a {
  line-height: 40px;
  padding: 10px 20px;
  text-transform: uppercase;
  text-decoration: none;
  color: #494949;
}

/* Hide dropdowns by default */

nav ul ul {
  width: 100%;
  position: absolute;
  display: none;
  left: 0;
  background-color: #c6c6c6;
}

/* Display Dropdowns on Click */

nav ul li.active > ul {
  display: block;
}

JS

window.onload = init;
function init()
{
  // set-up regular buttons
  var menuItemElements = document.getElementsByClassName('menuItem');
  for (var i = 0; i < menuItemElements.length; i++) {
    menuItemElements[i].onclick = resetSubmenus;
  }
  // set-up buttons with submenu
  resetSubmenus();
}

function openSubmenu()
  {
    resetSubmenus();
    this.setAttribute('class','submenu active');
    this.onclick = resetSubmenus;
  }

function resetSubmenus()
  {
    var submenuElements = document.getElementsByClassName('submenu');
    for (var i = 0; i < submenuElements.length; i++) {
      submenuElements[i].setAttribute('class','submenu');
      submenuElements[i].onclick = openSubmenu;
    }
}

Answer №1

To address the situation without revealing the exact code, one potential approach could be as follows:

Assign the class dropdown to both dropdowns and the class submenu to the sub-menus within them.

Utilize a click handler on the dropdown class to detect clicks, using the keyword this to identify which dropdown was clicked and apply the active class to it.

Create a CSS class named active with the necessary styles for displaying an open dropdown menu appropriately.

For instance:

.active .submenu {
  /* submenu styles */
}

In addition, for closing the dropdowns, remove the active class from all dropdown elements when any dropdown is clicked again (similar to the above procedure), or potentially when selecting anywhere outside of the dropdowns on the page. Then, add the class only to the newly clicked dropdown.

This explanation may offer some guidance for your task. Good luck! :)

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What could be causing the callback function to not function correctly within the HOC component?

I am encountering an issue while passing the state (setActive) to the ButtonsOur component and then trying to pass it to the HOC through a callback. The error message "Uncaught TypeError: setActive is not a function" keeps popping up. Could you please help ...

Achieving CSS centering with translate and avoiding content overflow

I have a code snippet for centering content both horizontally and vertically. It functions properly unless there is too much content, in which case it gets cut off. Is there a CSS solution to prevent this overflow issue without involving JavaScript? .b ...

The PHP query does not display any information when using JSON

I am fairly new to this, but I am attempting to tackle this issue. I find myself having to run similar queries on an SQL server multiple times. The problem arises when I invoke the PHP from HTML, as the getJson function or any other method does not return ...

Tips for resolving the issue: React is unable to recognize the X prop on a DOM element

I have been experimenting with a library known as react-firebase-js for managing firebase authentication. However, my grasp of react and the concept of provider-consumer is somewhat limited. Initially, I created a large JSX construct at the top level, whi ...

Achieve perfect alignment both vertically and horizontally within a container-fluid using Bootstrap

As the heading suggests, I'm having trouble centering content inside a column within .container-fluid in Bootstrap 4. Below is the code I'm using, but I can't figure out what's wrong. Can someone please assist me? <link href="htt ...

How to position an absolute element beneath a fixed element

My website is experiencing a problem where the fixed header is overlapping an absolute paragraph on this page. Does anyone know how to resolve this issue? ...

Unlimited Caching: A Guide to Storing HTTP Responses permanently

Is there a way to send an HTTP response that will be cached by any client indefinitely, so that the browser can retrieve it from the local file system without making any new HTTP requests when needed? Just imagine using this for versioned client code in a ...

The validation process fails when the button is clicked for the second time

When adding a username and email to the userlist, I am able to validate the email on initial page load. However, if I try to enter an invalid email for the second time and click the add button, it does not work. <form id="myform"> <h2>Ad ...

Testing the functionality of an event that is not linked to an event emitter

Below is the code snippet I'm working with: private client: any; this.client = mqtt.connect(url, mqttOptions); this.client.on('message', (topic: string, message: string) => { console.log('came inside onMessage'); ...

Scripts are only able to close the windows that they themselves have opened as a way to work around

I have been utilizing the reactjs popup library and incorporated the following code: <Popup trigger={<a href="javascript:void(0)">bobby</a>} modal> <button className="close" onClick={close}> &time ...

VueJS functions properly on Google Chrome, however it may encounter compatibility issues when using

I am currently working on a VueJs app resembling an auction, with the backend powered by Laravel. Everything runs smoothly when I test it on Chrome, but for some reason, Safari seems to be giving me trouble. The app consists of two main components: Deale ...

Filling a container div to its limit horizontally without exceeding its vertical boundaries

I'm currently developing a website that features a layout with two columns inside a container. The challenge I am facing is ensuring that the container's white background stretches to the bottom of the tallest column. To achieve this, I have impl ...

Utilizing Angular 2 for Integration of Google Calendar API

I recently attempted to integrate the Google Calendar API with Angular 2 in order to display upcoming events on a web application I am developing. Following the Google Calendar JavaScript quick-start tutorial, I successfully managed to set up the API, inse ...

Is it mandatory for the npm install command to only be compatible with Node.js for the script

I've encountered an API that provides a JS SDK, and the instructions on its GitHub page suggest using npm install. My question is whether I need to have Node.js in order to use this SDK since it seems to be designed for it, or if I can simply work wit ...

Error 404: Django is unable to locate the static/css files

This particular issue appears to be quite widespread, but existing Q&A resources are outdated and do not address my specific problem. Currently, with Django 2.0.2, my basic webpage is failing to render the bootstrap.css file simply because it cannot locat ...

The functionality to hide one div and display another in its place is disabled for popups and is not currently working as

For easy access, feel free to download my project files here. (Size: 2mb) In my webpage, there is a popup containing two images and a heading inside a div. I want to implement a functionality where upon hovering over the div, it will hide and show another ...

Getting the ajax response by utilizing a custom attribute within a php loop is definitely a handy trick

Currently working on integrating ajax with php. I have a set of buttons within a loop, and I am trying to display or fetch data (number) in the correct place/div using the "attr" method in jQuery. However, it seems that it is not functioning as expected on ...

"Learn the simple trick to quickly deactivate an anchor tag in just one easy

I have a navigation bar with items listed in an unordered list: <ul id="mainN" class="nanGroup" ng-show="lCntr.only == 'mainNav'"> <li > <a class="btn btn-small btn-revert" ng-click="lCntr.profile()"></a> &l ...

Preserve page configurations upon page refresh

I'm currently in the process of creating a 'user settings' form. Each list item represents a different section, such as Updating username, Updating email, and Changing password. It looks something like this: <li> <header>Ti ...

Only scroll the div if it is not within the visible window

I've been looking at this snippet: The sidebar division is what I'm focusing on right now. I want the div to scroll along as I scroll down the page, but I need it to stop scrolling when its bottom is in view. The same should apply when scrollin ...