What is the best way to close a dropdown when clicking away from it on the body?

I wish for the dropdown menu to lose its "show" class when clicked anywhere in the body or window, even if it already has the class "dropdown.menu.show". The show class gets removed and added before utilizing the code `(window.onclick = function(event){})`, however, when I place this code at the end of the JavaScript code, the show class doesn't get added at all. Thank you for your help with this matter.

function toggleClass(elem, className) {
  if (elem.className.indexOf(className) !== -1) {
    elem.className = elem.className.replace(className, '');
  } else {
    elem.className = elem.className.replace(/\s+/g, ' ') + ' ' + className;
  }

  return elem;
}

function toggleDisplay(elem) {
  const curDisplayStyle = elem.style.display;

  if (curDisplayStyle === 'none' || curDisplayStyle === '') {
    elem.style.display = 'block';
  } else {
    elem.style.display = 'none';
  }
}

function toggleMenuDisplay(e) {
  const dropdown = e.currentTarget.parentNode;
  const menu = dropdown.querySelector('.menu');
  toggleClass(menu, 'show');
}

function handleOptionSelected(e) {
  toggleClass(e.target.parentNode, 'show');
  const id = e.target.id;
  const newValue = e.target.textContent + ' ';
  const titleElem = document.querySelector('.dropdown .dropdown--title');
  titleElem.textContent = newValue;
}


const dropdownTitle = document.querySelector('.dropdown .dropdown--title');
const dropdownOptions = document.querySelectorAll('.dropdown .menu.categories a');
dropdownTitle.addEventListener('click', toggleMenuDisplay);
dropdownOptions.forEach(item => item.addEventListener('click', handleOptionSelected));

//this section is not work!!!
window.onclick = function(event) {
  const titleElem = document.querySelector('.dropdown .dropdown--title');

  const menu = document.querySelector('.menu');
  if (!(event.target == titleElem.nextElementSibling.classList.contains('show'))) {
    titleElem.nextElementSibling.classList.remove('show');
  }
}
.dropdown {
  position: relative;
}

.dropdown::before {
  content: "+";
  position: absolute;
  width: 1.5rem;
  height: 1.5rem;
  top: 15px;
  right: 0;
  color: var(--cbl);
}

.dropdown .dropdown--title {
  padding: 0.75rem;
  width: 100%;
  cursor: pointer;
}

.dropdown .menu {
  cursor: pointer;
  max-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  position: absolute;
  z-index: 12;
  width: 100%;
  top: 45px;
  right: 0;
  background-color: var(--cwh);
  transition: max-height 0.3s;
  -webkit-transition: max-height 0.3s;
  -moz-transition: max-height 0.3s;
  -ms-transition: max-height 0.3s;
  -o-transition: max-height 0.3s;
  box-shadow: 0 3px 20px #ccc;
  -webkit-box-shadow: 0 3px 20px #ccc;
  -moz-box-shadow: 0 3px 20px #ccc;
}

.dropdown .menu.show {
  max-height: 20em !important;
}

.dropdown .menu.show a {
  color: var(--cbl);
  opacity: 1;
  transition: all 0.3s;
  -webkit-transition: all 0.3s;
  -moz-transition: all 0.3s;
  -ms-transition: all 0.3s;
  -o-transition: all 0.3s;
  transform: translateX(0);
  -webkit-transform: translateX(0);
  -moz-transform: translateX(0);
  -ms-transform: translateX(0);
  -o-transform: translateX(0);
}

.dropdown .menu a {
  padding: 1rem;
  opacity: 0;
  color: var(--cbl);
  transform: translateX(100%);
  -webkit-transform: translateX(100%);
  -moz-transform: translateX(100%);
  -ms-transform: translateX(100%);
  -o-transform: translateX(100%);
}

.dropdown .menu a:nth-child(1) {
  transition-delay: 0.2s;
}

.dropdown .menu a:nth-child(2) {
  transition-delay: 0.15s;
}

.dropdown .menu a:nth-child(3) {
  transition-delay: 0.1s;
}

.dropdown .menu a:nth-child(4) {
  transition-delay: 0.05s;
}

.dropdown .menu a:nth-child(5) {
  transition-delay: 0s;
}

.dropdown .menu a:not(:last-child) {
  border-bottom: 1px solid var(--cblo40);
}

.dropdown .menu a:hover {
  background: rgba(0, 0, 0, 0.2);
}
<div class="dropdown">
  <div class="dropdown--title">Choose category</div>
  <div class="categories menu">
    <a href="#" data-category="[15,16,26,27]" class="clicked">All</a>
    <a href="http://localhost/discount/product-category/other/" data-category="15">Other</a>
    <a href="http://localhost/discount/product-category/electronics/" data-category="16">Electronics</a>
    <a href="http://localhost/discount/product-category/sports/" data-category="26">Sports</a>
    <a href="http://localhost/discount/product-category/toys/" data-category="27">Toys &amp; Games</a>
  </div>
</div>

Answer №1

Utilizing jQuery has made it much simpler to manipulate the DOM, while everything else (HTML, CSS) remains unchanged.

$(document).ready(function() {
  $(document).on('click', function() {
    $('.categories.menu').toggleClass('show');
  });
});
.dropdown {
  position: relative;
}

.dropdown::before {
  content: "+";
  position: absolute;
  width: 1.5rem;
  height: 1.5rem;
  top: 15px;
  right: 0;
  color: var(--cbl);
}
... (CSS code continues)

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="dropdown">
  <div class="dropdown--title">Choose category</div>
  <div class="categories menu">
    <a href="#" data-category="[15,16,26,27]" class="clicked">All</a>
    <a href="http://localhost/discount/product-category/other/" data-category="15">Other</a>
    <a href="http://localhost/discount/product-category/electronics/" data-category="16">Electronics</a>
    <a href="http://localhost/discount/product-category/sports/" data-category="26">Sports</a>
    <a href="http://localhost/discount/product-category/toys/" data-category="27">Toys &amp; Games</a>
  </div>
</div>

Answer №2

A new attribute called data-before was incorporated into the dropdown element, and it was referenced in the CSS portion using content: attr(data-before);

      function toggleClass(elem, className) {
        if (elem.className.indexOf(className) !== -1) {
          elem.className = elem.className.replace(className, "");
        } else {
          elem.className = elem.className.replace(/\s+/g, " ") + " " + className;
        }
        return elem;
      }

      function toggleDisplay(elem) {
        const curDisplayStyle = elem.style.display;
        if (curDisplayStyle === "none" || curDisplayStyle === "") {
          elem.style.display = "block";
        } else {
          elem.style.display = "none";
        }
      }

      function toggleMenuDisplay(e) {
        const dropdown = e.currentTarget.parentNode;
        const menu = dropdown.querySelector(".menu");
        menu.classList.toggle("show");
        menu.classList.contains("show")
          ? dropdown.setAttribute("data-before", "-")
          : dropdown.setAttribute("data-before", "+");
      }

      function handleOptionSelected(e) {
        //toggleClass(e.target.parentNode, "show");
        const id = e.target.id;
        const newValue = e.target.textContent + " ";
        const titleElem = document.querySelector(".dropdown .dropdown--title");
        titleElem.textContent = newValue;
      }

      const dropdownTitle = document.querySelector(".dropdown .dropdown--title");
      const dropdownOptions = document.querySelectorAll(".dropdown .menu.categories a");
      dropdownTitle.addEventListener("click", toggleMenuDisplay);
      dropdownOptions.forEach((item) => item.addEventListener("click", handleOptionSelected));

      document.onclick = function (event) {
      
        if (document.getElementsByClassName("categories menu show").length == 1 && event.target.localName !== "div") {
          document.querySelector(".categories.menu").classList.toggle("show");
          document.querySelector(".dropdown").setAttribute("data-before", "+");
        }
      };
.dropdown {
  position: relative;
}

.dropdown::before {
  content: attr(data-before);
  position: absolute;
  width: 1.5rem;
  height: 1.5rem;
  top: 15px;
  right: 0;
  color: var(--cbl);
}

.dropdown .dropdown--title {
  padding: 0.75rem;
  width: 100%;
  cursor: pointer;
}

.dropdown .menu {
  cursor: pointer;
  max-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  position: absolute;
  z-index: 12;
  width: 100%;
  top: 45px;
  right: 0;
  background-color: var(--cwh);
  transition: max-height 0.3s;
  -webkit-transition: max-height 0.3s;
  -moz-transition: max-height 0.3s;
  -ms-transition: max-height 0.3s;
  -o-transition: max-height 0.3s;
  box-shadow: 0 3px 20px #ccc;
  -webkit-box-shadow: 0 3px 20px #ccc;
  -moz-box-shadow: 0 3px 20px #ccc;
}

.dropdown .menu.show {
  max-height: 20em !important;
}

.dropdown .menu.show a {
  color: var(--cbl);
  opacity: 1;
  transition: all 0.3s;
  -webkit-transition: all 0.3s;
  -moz-transition: all 0.3s;
  -ms-transition: all 0.3s;
  -o-transition: all 0.3s;
  transform: translateX(0);
  -webkit-transform: translateX(0);
  -moz-transform: translateX(0);
  -ms-transform: translateX(0);
  -o-transform: translateX(0);
}

.dropdown .menu a {
  padding: 1rem;
  opacity: 0;
  color: var(--cbl);
  transform: translateX(100%);
  -webkit-transform: translateX(100%);
  -moz-transform: translateX(100%);
  -ms-transform: translateX(100%);
  -o-transform: translateX(100%);
}

.dropdown .menu a:nth-child(1) {
  transition-delay: 0.2s;
}

.dropdown .menu a:nth-child(2) {
  transition-delay: 0.15s;
}

.dropdown .menu a:nth-child(3) {
  transition-delay: 0.1s;
}

.dropdown .menu a:nth-child(4) {
  transition-delay: 0.05s;
}

.dropdown .menu a:nth-child(5) {
  transition-delay: 0s;
}

.dropdown .menu a:not(:last-child) {
  border-bottom: 1px solid var(--cblo40);
}

.dropdown .menu a:hover {
  background: rgba(0, 0, 0, 0.2);
}
<div class="dropdown" data-before="+">
  <div class="dropdown--title">Choose category</div>
  <div class="categories menu">
    <a href="#" data-category="[15,16,26,27]" class="clicked">All</a>
    <a href="http://localhost/discount/product-category/other/" data-category="15">Other</a>
    <a href="http://localhost/discount/product-category/electronics/" data-category="16">Electronics</a>
    <a href="http://localhost/discount/product-category/sports/" data-category="26">Sports</a>
    <a href="http://localhost/discount/product-category/toys/" data-category="27">Toys &amp; Games</a>
  </div>
</div>

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 is the best way to incorporate an image sprite within table data?

Utilizing the Instant Sprite tool, I successfully created my own unique sprite image. With the help of a repeater control, I assigned the <td> class to be arg16 Private Sub cdcatalog_ItemDataBound(sender As Object, e As RepeaterItemEventArgs) Handl ...

The contact form fails to send even after passing the validation process

I am currently working on a contact form backend using node.js and nodemailer for sending messages. However, I'm encountering an issue where even after correctly filling out the inputs, it still displays an error message stating that the inputs are in ...

Is there a way to find the magnitude of a whole number without relying on the Math.abs function?

Is there a method to obtain the absolute value of a number without utilizing math.abs? Here is my current approach: function absValue(number) { var abs = number * number; return Math.sqrt(abs); } ...

Express.js - Unfulfilled promises cause blocks and slow down subsequent GET requests in express (Node.js)

I currently have an active express application that listens for GET requests. Whenever a GET request is made, it promptly returns a success response and triggers the function asyncForLoop(), which resolves a promise after 5 seconds. Issue: The problem ari ...

Add the html tag in front of the existing html tag

I am encountering an issue with my html template (which includes jquery, js and all necessary imports in the head section). Specifically, I am attempting to prepend new paragraphs before an existing paragraph with the id "board-page": <div class="conta ...

The method .replace() is used to substitute instances within a string

I have set up input fields for users to enter tags. For example, if a user enters "xyz_DTL_D, John_D" it will be stored in the tagArr[] array. My goal is to replace the input "_D" with an empty string. So I created the following code: var dailycheck = ...

Utilizing a function as a value in React state (setState) compared to defining state with constructor in a class and utilizing a state object

Can someone help me understand the concept of state in React better? I'm confused about the differences between these two implementations: This: class Todo extends ... { constructor (){ super() this.state = { ... } } } And This: class Todo extend ...

Use regular expressions to exclude occurrences of the character 'n' from your text

Looking for a regular expression to validate input, specifically filtering out all special characters except "underscore". All characters within the range [a-zA-Z0-9\underscore] are permitted and can appear multiple times. However, my expression shoul ...

Using Javascript to Retrieve "Images" from the "Resources" Directory

I typically use Google Chrome as my browser of choice. Unfortunately, the images on certain websites are causing lagging issues. I am looking for a solution to access and clean up these problematic images in order to improve browsing performance. ...

The following code automatically triggers the loading of a modal, which contains a checkbox upon loading

What is the best way to prevent the modal from showing again if the user checks the checkbox and clicks the close button? function popUp() { $("#LoadModal").show() var modal = document.getElementById('LoadModal') var closeBtn = document ...

Tips for controlling the upload of a .exe.png file or converting a .exe file to a png file for uploading in angular 8

I had originally set up restrictions to only allow image file types such as JPG, JPEG, PNG, and TIFF. However, I discovered that users were able to upload .exe files simply by renaming them. For example, changing dell.exe.png or dell.exe to dell.png allo ...

Javascript - Unable to update button text

I am encountering a problem with updating the text of a Bootstrap button when a collapsed element is opened or closed. The icon part is updating successfully, but I am struggling to get the button text to update and I cannot figure out why. My knowledge o ...

Invoke a node.js function from within an HTML document

Seems like a recurring question. The closest solution I found was on this page: execute a Nodejs script from an html page? However, I'm still having trouble grasping it. Here's the scenario: I have an express server set up with the following ...

The date-fns parse function will retrieve the value from the previous day

When attempting to parse a date using the date-fns library, I am encountering an issue where the resulting date is one day prior. How can this be resolved in order to obtain the correct result? start = '2021-08-16' const parseStart = parse(start, ...

I recently designed a form using a combination of HTML and JavaScript in order to display different dialogues depending on the number of selections made. Unfortunately, the alert function does not seem to be functioning

The concept is to have users select options and then receive employment sector suggestions based on their selections. <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF ...

The dynamic table is too large for the bootstrap card

My issue involves datatables within bootstrap cards. When the table exceeds the height of the card, it extends outside the boundaries. Even with overflow: auto on a table-wrapper in place, the scroll does not trigger when exceeding the card's height. ...

Incorporate visual elements such as images that resemble checkboxes

I'm searching for an innovative approach to replace the traditional checkbox, incorporating images instead. My idea is to have users click on an image, which will then fade out and reveal a tick box overlay. My inspiration comes from Recaptcha 2, whe ...

How to exclude specific descendants and their children using the jquery find() method?

My code structure appears as follows: <div class='wrapper plugin'> //some content <div class='child-wrapper plugin'> //some more content <div> <ul> <li><div cl ...

PHP Multiple Filter Options

Currently, I am developing a filtering system for a mySQL database that utilizes dropdown menus. While the system functions correctly when using one filter, I am encountering difficulties in stacking multiple filters. To achieve this, I am retrieving the c ...

Shifting text higher to the <h1> tag using CSS

I'm currently attempting to move the paragraph header closer to my h1 title. I'm struggling with incorporating the position function and have tried using padding without success. On the image, I am aiming to position the text "Where every connec ...