yaharga yaharga - 5 months ago 15
jQuery Question

How to close submenu from the children up to the parent sequentially using jQuery?

First of all, ignore the CSS. It's just there for the sake of styling.

What the JS code does:


  • Activates when you click either "Menu" or one of the "+".

  • Opens up the
    ul
    located immediately after the clicked button ("Menu" or "+") by giving itself the "active" class, and then the CSS does all the rest (opening the menu and transitioning through
    .dropdown-toggle.active + .toggleable
    where
    .toggleable
    is the
    ul
    ).

  • Looks for any open menus on the same level and closes them before opening menu of the clicked button.

  • When the button is clicked to close, it closes all of its children first, sequentially, starting from the deepest open one, and finally closes itself, in order to give the effect of folding back a piece of paper.



The issue is that when I close a menu, the deepset child would close, then the rest would close up simultaneously together. I want them to close one after the other, in sequence. I'm using
transitionend
to check if the child closed before it would close the next parent, and so on.



// Dropdown toggle click event fucntion.
$('.dropdown-toggle').on('click', function() {

if ($(this).is('.top-toggler:not(.active)')) { // If the top toggler "Menu" was clicked, and it wasn't ative, activate it, and return.
$(this).addClass('active');
return;
}

function closeToggleables(button, toggleable) { // Function to process what needs to be closed and close it.
var $activeChildren = toggleable.find('.dropdown-toggle.active'); // Get all active children dropdown buttons.
if ($activeChildren.length) { // If active children dropdown buttons exist, continue.
$.each($activeChildren, function(i, activeChild){ // Iterate through every active child button.
// Hook the ul menu next to the active child with a transitionend to close its parent after the transition ends (to close its parent when it is done closing).
$(activeChild).next('.toggleable').one('transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd', function() { // The transitionend hook.
$(activeChild).parent().parent().prev('.active').removeClass('active'); // Close parent when transition ends.
});
});
$activeChildren.last().removeClass('active'); // Deactivate the last active button to begin closing from the deepest child to the passed parent.
} else {
button.removeClass('active'); // If there are no active children buttons, just deactivate this button to close its menu.
}
}

if ($(this).hasClass('active')) { // If the clicked button is active.
closeToggleables($(this), $(this).next('.toggleable')); // Send element and its menu to process how it will close.
} else { // If this menu button is inactive.
var $activeSibling = $(this).parent().siblings('.parent').children('.active'); // See if any siblings are open.
if ($activeSibling.length) { // If an open sibling exists.
closeToggleables($activeSibling, $activeSibling.next('.toggleable')); // Send the sibling to be processed to close first.
}
$(this).addClass('active'); // Activate this menu.
}

});

body {
font-family: lato, sans-serif;
font-size: 1em;
line-height: 1.5;
color: #2c3e50;
}
a {
text-decoration: none;
}
.text-center {
text-align: center;
}
.hide {
visibility: hidden;
overflow: hidden;
max-height: 0;
}
.dropdown-toggle {
line-height: inherit;
padding: 12px;
color: #ecf0f1;
outline: 0;
}
.dropdown-toggle.active {
color: #fbfcfc;
background: #ea6153;
}
.toggleable {
-webkit-transition: max-height .75s ease-in-out, -webkit-transform .75s ease-in-out, visibility .75s ease-in-out;
transition: max-height .75s ease-in-out, transform .75s ease-in-out, visibility .75s ease-in-out;
-webkit-transform: scaleY(0);
transform: scaleY(0);
-webkit-transform-origin: top;
transform-origin: top;
}
.toggleable .toggleable {
-webkit-transform: scaleY(1);
transform: scaleY(1);
-webkit-transform-origin: center;
transform-origin: center;
}
.dropdown-toggle.active + .toggleable {
visibility: visible;
max-height: 1200px;
-webkit-transform: scaleY(1);
transform: scaleY(1);
}
ul {
margin: 0;
padding: 0;
list-style-type: none;
}
button {
font-family: lato, sans-serif;
font-size: 1em;
padding: .75em 1.5em;
-webkit-transition: background-color .75s ease;
transition: background-color .75s ease;
vertical-align: middle;
color: #ecf0f1;
border: 0;
border-radius: 3px;
background-color: #e74c3c;
}
button:focus,
button:hover {
color: #fbfcfc;
background-color: #ea6153;
}
#nav-primary {
position: relative;
margin: 30px 10px;
}
#nav-primary .dropdown-toggle.active + .toggleable > .menu-item {
margin: 0;
-webkit-transform: perspective(320px) rotateX(0deg);
transform: perspective(320px) rotateX(0deg);
box-shadow: inset 0 0 20px 0 transparent;
}
#menu-main-toggle {
width: 100%;
border-radius: 0;
}
#menu-main {
position: absolute;
width: 100%;
}
#menu-main .menu-item {
-webkit-transition: -webkit-transform .75s ease-in-out, margin .75s ease-in-out, -webkit-box-shadow .75s ease-in-out;
transition: transform .75s ease-in-out, margin .75s ease-in-out, box-shadow .75s ease-in-out;
background: #e74c3c;
}
#menu-main .menu-item.odd {
margin-bottom: -100px;
-webkit-transform: perspective(320px) rotateX(-90deg);
transform: perspective(320px) rotateX(-90deg);
-webkit-transform-origin: top;
transform-origin: top;
box-shadow: inset 0 -50px 25px 0 rgba(0, 0, 0, .5);
}
#menu-main .menu-item.even {
margin-top: -100px;
-webkit-transform: perspective(320px) rotateX(90deg);
transform: perspective(320px) rotateX(90deg);
-webkit-transform-origin: bottom;
transform-origin: bottom;
box-shadow: inset 0 50px 25px 0 rgba(0, 0, 0, .5);
}
#menu-main .menu-link {
display: inline-block;
width: 100%;
height: 50px;
padding: 12px;
color: #ecf0f1;
border-top: 1px dashed #bf2718;
box-sizing: border-box;
}
#menu-main .parent .dropdown-toggle.active + .toggleable > .menu-item {
margin: 0 5px;
}
#menu-main .parent .dropdown-toggle.active + .toggleable > .menu-item.last {
margin-bottom: 5px;
}
#menu-main .parent > .menu-link {
margin-right: -54px;
}
#menu-main .dropdown-toggle {
width: 50px;
height: 48px;
vertical-align: bottom;
border-radius: 0;
background: transparent;
}
#menu-main .toggleable {
background: #bf2718;
}

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.3/normalize.min.css" rel="stylesheet"/>
<nav id="nav-primary" class="text-center">
<button id="menu-main-toggle" class="top-toggler dropdown-toggle">Menu</button>
<ul id="menu-main" class="toggleable hide">
<li class="menu-item odd">
<a class="menu-link" href="http://example.com/">About</a>
</li>
<li class="menu-item even">
<a class="menu-link" href="http://example.com/">Something</a>
</li>
<li class="menu-item odd parent">
<a class="menu-link" href="http://example.com/">Information Technology</a>
<button class="dropdown-toggle">+</button>
<ul class="toggleable hide">
<li class="menu-item odd parent">
<a class="menu-link" href="http://example.com/">Web Development</a>
<button class="dropdown-toggle">+</button>
<ul class="toggleable hide">
<li class="menu-item odd">
<a class="menu-link" href="http://example.com/">HTML</a>
</li>
<li class="menu-item even">
<a class="menu-link" href="http://example.com/">CSS</a>
</li>
<li class="menu-item odd last">
<a class="menu-link" href="http://example.com/">Javascript</a>
</li>
</ul>
</li>
<li class="menu-item even last">
<a class="menu-link" href="http://example.com/">Unix</a>
</li>
</ul>
</li>
<li class="menu-item even parent">
<a class="menu-link" href="http://example.com/">Level One</a>
<button class="dropdown-toggle">+</button>
<ul class="toggleable hide">
<li class="menu-item odd last parent">
<a class="menu-link" href="http://example.com/">Level Two</a>
<button class="dropdown-toggle">+</button>
<ul class="toggleable hide">
<li class="menu-item odd last parent">
<a class="menu-link" href="http://example.com/">Level Three</a>
<button class="dropdown-toggle">+</button>
<ul class="toggleable hide">
<li class="menu-item odd last parent">
<a class="menu-link" href="http://example.com/">Lorem</a>
<button class="dropdown-toggle script-dependant">+</button>
<ul class="toggleable hide">
<li class="menu-item odd parent">
<a class="menu-link" href="http://example.com/">Ipsum</a>
<button class="dropdown-toggle script-dependant">+</button>
<ul class="toggleable hide">
<li class="menu-item odd last">
<a class="menu-link" href="http://example.com/">Dolor</a>
</li>
</ul>
</li>
<li class="menu-item even last">
<a class="menu-link" href="http://example.com/">Situs</a>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li class="menu-item odd">
<a class="menu-link" href="http://example.com/">Snippets</a>
</li>
<li class="menu-item even last">
<a class="menu-link" href="http://example.com/">Contact</a>
</li>
</ul>
</nav>




Answer

Since I had more than one property transition on the element, I just checked for the height of the element whenever transitionend was called. Once the height was zero, I unbound the event and toggled the parent button.

// Dropdown toggle click event fucntion.
$('.dropdown-toggle').on('click', function() {

  // If dropdown-toggle is an inactive top toggler, activate.
  if ($(this).is('.top-toggler:not(.active)')) {
    $(this).addClass('active');
    return;
  }

  // Function to process how to close toggleables; either children first, or just the clicked toggleable.
  function closeToggleables(button, toggleable) {
    // Find all active children and turn them into an array.
    var $activeChildren = toggleable.find('.dropdown-toggle.active');
    // If active children exist.
    if ($activeChildren.length) {
      // Iterate through each active child.
      $activeChildren.each( function(){
        // Bind child's menu to activate function when CSS transition ends.
        $(this).next('.toggleable').on('transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd', function(event) {
          // Since many transitions occur, we check if the height transition ended by checking the height of the child's menu.
          if ($(this).height() === 0) {
            // Unbind event and deactivate parent of child.
            $(this).off(event).parent().parent().prev('.active').removeClass('active');
          }
        });
      });
      // Deactivate last child to start deactivating all parents up to clicked element.
      $activeChildren.last().removeClass('active');
    } else {
      // Deactivate clicked button.
      button.removeClass('active');
    }
  }

  // If button is active.
  if ($(this).hasClass('active')) {
    // Send button and its menu to be processed for closing.
    closeToggleables($(this), $(this).next('.toggleable'));
  } else {
    // Check for an open sibling.
    var $activeSibling = $(this).parent().siblings('.parent').children('.active');
    // If an open sibling exists.
    if ($activeSibling.length) {
      // Send sibling and its menu to be processed for closing.
      closeToggleables($activeSibling, $activeSibling.next('.toggleable'));
    }
    // Activate inactive button.
    $(this).addClass('active');
  }
  
});
body {
    font-family: lato, sans-serif;
    font-size: 1em;
    line-height: 1.5;
    color: #2c3e50;
}
a {
    text-decoration: none;
}
.text-center {
    text-align: center;
}
.hide {
    visibility: hidden;
    overflow: hidden;
    max-height: 0;
}
.dropdown-toggle {
    line-height: inherit;
    padding: 12px;
    color: #ecf0f1;
    outline: 0;
}
.dropdown-toggle.active {
    color: #fbfcfc;
    background: #ea6153;
}
.toggleable {
    -webkit-transition: max-height .75s ease-in-out, -webkit-transform .75s ease-in-out, visibility .75s ease-in-out;
    transition: max-height .75s ease-in-out, transform .75s ease-in-out, visibility .75s ease-in-out;
    -webkit-transform: scaleY(0);
    transform: scaleY(0);
    -webkit-transform-origin: top;
    transform-origin: top;
}
.toggleable .toggleable {
    -webkit-transform: scaleY(1);
    transform: scaleY(1);
    -webkit-transform-origin: center;
    transform-origin: center;
}
.dropdown-toggle.active + .toggleable {
    visibility: visible;
    max-height: 1200px;
    -webkit-transform: scaleY(1);
    transform: scaleY(1);
}
ul {
    margin: 0;
    padding: 0;
    list-style-type: none;
}
button {
    font-family: lato, sans-serif;
    font-size: 1em;
    padding: .75em 1.5em;
    -webkit-transition: background-color .75s ease;
    transition: background-color .75s ease;
    vertical-align: middle;
    color: #ecf0f1;
    border: 0;
    border-radius: 3px;
    background-color: #e74c3c;
}
button:focus,
button:hover {
    color: #fbfcfc;
    background-color: #ea6153;
}
#nav-primary {
    position: relative;
    margin: 30px 10px;
}
#nav-primary .dropdown-toggle.active + .toggleable > .menu-item {
    margin: 0;
    -webkit-transform: perspective(320px) rotateX(0deg);
    transform: perspective(320px) rotateX(0deg);
    box-shadow: inset 0 0 20px 0 transparent;
}
#menu-main-toggle {
    width: 100%;
    border-radius: 0;
}
#menu-main {
    position: absolute;
    width: 100%;
}
#menu-main .menu-item {
    -webkit-transition: -webkit-transform .75s ease-in-out, margin .75s ease-in-out, -webkit-box-shadow .75s ease-in-out;
    transition: transform .75s ease-in-out, margin .75s ease-in-out, box-shadow .75s ease-in-out;
    background: #e74c3c;
}
#menu-main .menu-item.odd {
    margin-bottom: -100px;
    -webkit-transform: perspective(320px) rotateX(-90deg);
    transform: perspective(320px) rotateX(-90deg);
    -webkit-transform-origin: top;
    transform-origin: top;
    box-shadow: inset 0 -50px 25px 0 rgba(0, 0, 0, .5);
}
#menu-main .menu-item.even {
    margin-top: -100px;
    -webkit-transform: perspective(320px) rotateX(90deg);
    transform: perspective(320px) rotateX(90deg);
    -webkit-transform-origin: bottom;
    transform-origin: bottom;
    box-shadow: inset 0 50px 25px 0 rgba(0, 0, 0, .5);
}
#menu-main .menu-link {
    display: inline-block;
    width: 100%;
    height: 50px;
    padding: 12px;
    color: #ecf0f1;
    border-top: 1px dashed #bf2718;
    box-sizing: border-box;
}
#menu-main .parent .dropdown-toggle.active + .toggleable > .menu-item {
    margin: 0 5px;
}
#menu-main .parent .dropdown-toggle.active + .toggleable > .menu-item.last {
    margin-bottom: 5px;
}
#menu-main .parent > .menu-link {
    margin-right: -54px;
}
#menu-main .dropdown-toggle {
    width: 50px;
    height: 48px;
    vertical-align: bottom;
    border-radius: 0;
    background: transparent;
}
#menu-main .toggleable {
    background: #bf2718;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.3/normalize.min.css" rel="stylesheet"/>
<nav id="nav-primary" class="text-center">
  <button id="menu-main-toggle" class="top-toggler dropdown-toggle">Menu</button>
  <ul id="menu-main" class="toggleable hide">
    <li class="menu-item odd">
      <a class="menu-link" href="http://example.com/">About</a>
    </li>
    <li class="menu-item even">
      <a class="menu-link" href="http://example.com/">Something</a>
    </li>
    <li class="menu-item odd parent">
      <a class="menu-link" href="http://example.com/">Information Technology</a>
      <button class="dropdown-toggle">+</button>
      <ul class="toggleable hide">
        <li class="menu-item odd parent">
          <a class="menu-link" href="http://example.com/">Web Development</a>
          <button class="dropdown-toggle">+</button>
          <ul class="toggleable hide">
            <li class="menu-item odd">
              <a class="menu-link" href="http://example.com/">HTML</a>
            </li>
            <li class="menu-item even">
              <a class="menu-link" href="http://example.com/">CSS</a>
            </li>
            <li class="menu-item odd last">
              <a class="menu-link" href="http://example.com/">Javascript</a>
            </li>
          </ul>
        </li>
        <li class="menu-item even last">
          <a class="menu-link" href="http://example.com/">Unix</a>
        </li>
      </ul>
    </li>
    <li class="menu-item even parent">
      <a class="menu-link" href="http://example.com/">Level One</a>
      <button class="dropdown-toggle">+</button>
      <ul class="toggleable hide">
        <li class="menu-item odd last parent">
          <a class="menu-link" href="http://example.com/">Level Two</a>
          <button class="dropdown-toggle">+</button>
          <ul class="toggleable hide">
            <li class="menu-item odd last parent">
              <a class="menu-link" href="http://example.com/">Level Three</a>
              <button class="dropdown-toggle">+</button>
              <ul class="toggleable hide">
                <li class="menu-item odd last parent">
                  <a class="menu-link" href="http://example.com/">Lorem</a>
                  <button class="dropdown-toggle script-dependant">+</button>
                  <ul class="toggleable hide">
                    <li class="menu-item odd parent">
                      <a class="menu-link" href="http://example.com/">Ipsum</a>
                      <button class="dropdown-toggle script-dependant">+</button>
                      <ul class="toggleable hide">
                        <li class="menu-item odd last">
                          <a class="menu-link" href="http://example.com/">Dolor</a>
                        </li>
                      </ul>
                    </li>
                    <li class="menu-item even last">
                      <a class="menu-link" href="http://example.com/">Situs</a>
                    </li>
                  </ul>
                </li>
              </ul>
            </li>
          </ul>
        </li>
      </ul>
    </li>
    <li class="menu-item odd">
      <a class="menu-link" href="http://example.com/">Snippets</a>
    </li>
    <li class="menu-item even last">
      <a class="menu-link" href="http://example.com/">Contact</a>
    </li>
  </ul>
</nav>