Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dropdown bug, if clicked too quickly, options in dropdown remain visible

I am having trouble trying to work out why when clicking the dropdown arrow too quickly the options in the dropdown will remain even after it has closed.

HTML

<div class="dropdown-container">
  <div class="dropdown-header">
    <h5 class="js-title-hook dropdown-title">
      Option 1
    </h5>
    <div class="toggle-button">
      <p>
        &darr;    
      </p>
    </div>
  </div>
  <div class="dropdown-options">
    <ul class="dropdown-options-list">
      <li class="js-hook-dropdown-list" data-title="Option 1">Option 1</li>
      <li class="js-hook-dropdown-list" data-title="Option 2">Option 2</li>
      <li class="js-hook-dropdown-list" data-title="Option 3">Option 3</li>
      <li class="js-hook-dropdown-list" data-title="Option 4">Option 4</li>
      <li class="js-hook-dropdown-list" data-title="Option 5">Option 5</li>
    </ul>
  </div>
</div>

CSS

.dropdown-container{
  width: 400px;
  font-family: 'helvetica';
  color: 2b2b2b;
  border-bottom: 1px solid #2b2b2b;
}

.dropdown-title{
  float: left;
  margin-right: 20px;
}

.toggle-button{
  display: inline-block;
  cursor: pointer;
  transform: rotate(0deg);
}

.dropdown-options{
  /* padding-top: 30px; */
}

.dropdown-options{
  visibility: hidden;
    opacity: 0;
    transition: height 1s, visibility 0.5s, opacity 0.5s ease-out;
}

.dropdown-options-list{
  list-style: none;
}

.dropdown-options-list li{
  margin-bottom: 5px;
  cursor: pointer;
}

.dropdown-options-list li:hover{
  color: #D62727;
}

JavaScript

(function(){

  //Incase anything ever changes in the DOM
    var DOMstrings = {
      dropPointer: '.toggle-button',
      pageTitle: '.js-title-hook',
      dropOptions: '.dropdown-options',
      dropTitle: '.project-dropdown-title',
      listItems: '.js-hook-dropdown-list',
    }

    var dropPointer = document.querySelector(DOMstrings.dropPointer);
    var dropOptions = document.querySelector(DOMstrings.dropOptions);
    var dropTitle = document.querySelector(DOMstrings.dropTitle);
    var height = dropOptions.clientHeight;
    var width = dropOptions.clientWidth;

    //initisualise the height of drop options
    dropOptions.style.height = '0';

    // console.log(height + ' x ' + width);

    dropDown();

    function dropDown(){
      dropPointer.addEventListener('click', function(){

        //toggle for the pointer rotating
        if(dropPointer.style.transform !== "rotate(180deg)"){
          dropPointer.style.transform = "rotate(180deg)";

          setTimeout(function(){
            dropOptions.style.height = height + 'px';
            dropOptions.style.marginBottom = '10px';
          }, 150)

          setTimeout(function(){
              dropOptions.style.visibility = 'visible';
              dropOptions.style.opacity = '1';
          }, 700)

          // console.log(height + ' x ' + width);

        } else {
          closeDropdown();
        }
      });
    }

    function closeDropdown() {
      dropPointer.style.transform = "rotate(0deg)";
      dropOptions.style.visibility = 'hidden';
      dropOptions.style.opacity = '0';

      setTimeout(function(){
        dropOptions.style.height = '0';
        dropOptions.style.marginBottom = '0px';
      }, 200);

      setTimeout(function(){
        dropTitle.style.marginBottom = "0px";
      }, 500)

    }

    var elements = document.querySelectorAll(DOMstrings.listItems);
    var title = document.querySelector(DOMstrings.pageTitle);

    // console.log(listProjects);

    for(var i = 0; i < elements.length; i++){
      changeProject(elements[i]);
    }

    function changeProject(el) {
      el.addEventListener('click', function(e){

        var target = e.currentTarget;
        // console.log(target);

        title.innerHTML = target.dataset.title;

        closeDropdown();
      })
    }

})();

The code was a little long so here is the JSfiddle that illustrates the behaviour: https://jsfiddle.net/andylyell/jkjxmLxx/

Thank you!

like image 212
Andy Lyell Avatar asked Apr 25 '17 08:04

Andy Lyell


2 Answers

create a global var for the setTimeout function that make the content visible:

thistimeout = setTimeout(function(){
          dropOptions.style.visibility = 'visible';
          dropOptions.style.opacity = '1';
      }, 700)

Use clearTimeout() inside the closeDropdown() function to stop the setTimeout function

function closeDropdown() {
  clearTimeout(thistimeout);
  dropPointer.style.transform = "rotate(0deg)";
  dropOptions.style.visibility = 'hidden';

WORKING FIDDLE: https://jsfiddle.net/VLK_STUDIO/jkjxmLxx/2/

(function(){

  //Incase anything ever changes in the DOM
    var DOMstrings = {
      dropPointer: '.toggle-button',
      pageTitle: '.js-title-hook',
      dropOptions: '.dropdown-options',
      dropTitle: '.project-dropdown-title',
      listItems: '.js-hook-dropdown-list',
    }

    var dropPointer = document.querySelector(DOMstrings.dropPointer);
    var dropOptions = document.querySelector(DOMstrings.dropOptions);
    var dropTitle = document.querySelector(DOMstrings.dropTitle);
    var height = dropOptions.clientHeight;
    var width = dropOptions.clientWidth;

    //initisualise the height of drop options
    dropOptions.style.height = '0';

    // console.log(height + ' x ' + width);

    dropDown();

    function dropDown(){
      dropPointer.addEventListener('click', function(){

        //toggle for the pointer rotating
        if(dropPointer.style.transform !== "rotate(180deg)"){
          dropPointer.style.transform = "rotate(180deg)";

          setTimeout(function(){
            dropOptions.style.height = height + 'px';
            dropOptions.style.marginBottom = '10px';
          }, 150)

          thistimeout = setTimeout(function(){
              dropOptions.style.visibility = 'visible';
              dropOptions.style.opacity = '1';
          }, 700)

          // console.log(height + ' x ' + width);

        } else {
          closeDropdown();
        }
      });
    }

    function closeDropdown() {
      clearTimeout(thistimeout);
      dropPointer.style.transform = "rotate(0deg)";
      dropOptions.style.visibility = 'hidden';
      dropOptions.style.opacity = '0';

      setTimeout(function(){
        dropOptions.style.height = '0';
        dropOptions.style.marginBottom = '0px';
      }, 200);

      setTimeout(function(){
        dropTitle.style.marginBottom = "0px";
      }, 500)

    }

    var elements = document.querySelectorAll(DOMstrings.listItems);
    var title = document.querySelector(DOMstrings.pageTitle);

    // console.log(listProjects);

    for(var i = 0; i < elements.length; i++){
      changeProject(elements[i]);
    }

    function changeProject(el) {
      el.addEventListener('click', function(e){

        var target = e.currentTarget;
        // console.log(target);

        title.innerHTML = target.dataset.title;

        closeDropdown();
      })
    }

})();
like image 79
vlk Avatar answered Sep 27 '22 23:09

vlk


The issue is due to your setTimeout in function dropDown().

If you click the dropdown button again within 700ms of opening it, the dropOptions.style.visibility = 'hidden'; line will be executed first, then the 700ms timeout will expire, so dropOptions.style.visibility = 'visible'; will then be executed – thus leaving the dropdown visible on the screen.

To fix it, inside each setTimeout handler, check if the dropdown is still hidden/shown before modifying the properties of dragOptions.

This is what I mean:

function dropDown(){
  dropPointer.addEventListener('click', function(){

    //toggle for the pointer rotating
    if(dropPointer.style.transform !== "rotate(180deg)"){
      dropPointer.style.transform = "rotate(180deg)";

      setTimeout(function(){
        if(dropPointer.style.transform !== "rotate(180deg)")return;
        dropOptions.style.height = height + 'px';
        dropOptions.style.marginBottom = '10px';
      }, 150)

      setTimeout(function(){
          if(dropPointer.style.transform !== "rotate(180deg)")return;
          dropOptions.style.visibility = 'visible';
          dropOptions.style.opacity = '1';
      }, 700)

      // console.log(height + ' x ' + width);

    } else {
      closeDropdown();
    }
  });
}

function closeDropdown() {
  dropPointer.style.transform = "rotate(0deg)";
  dropOptions.style.visibility = 'hidden';
  dropOptions.style.opacity = '0';

  setTimeout(function(){
    if(dropPointer.style.transform === "rotate(180deg)")return;
    dropOptions.style.height = '0';
    dropOptions.style.marginBottom = '0px';
  }, 200);

  setTimeout(function(){
    if(dropPointer.style.transform === "rotate(180deg)")return;
    dropTitle.style.marginBottom = "0px";
  }, 500);
}

Working JSFiddle.

like image 32
Bernard Avatar answered Sep 27 '22 23:09

Bernard