Search code examples
javascripthtmlnavigation

How can i collapse Vanilla JS Multi level Menu when other menu is Opened


I created a multi level basic navigation menu using pure JS and trying to find a logic to collapse a submenu when other submenu is opened. I used foreach to loop all menus which has submenu. But don't know how to detect other menu while a menu is clicked. Can anyone help me on this. Thanks in Advance!

/* toggle click function for submenus */
slideToggle=(el)=> {
let cs = window.getComputedStyle(el).display;
if(cs==="none") {
    el.style.display="block";
}
else {
    el.style.display="none";
}
}

/* detecting menus which has submenus */
submenuDetect=()=> {
    let li = document.querySelectorAll(".main-navigation-content > ul li");

    /* adding a class "has-submenu" for menu which has sublevel menu  */
    li.forEach((item)=>{
        let ul = item.querySelector("ul");
        if(ul){
            item.classList.add("has-submenu");
            ul.classList.add("submenu-content");

            /* adding icon */
            let iconElement = document.createElement("span");
            iconElement.setAttribute("class","down-arrow-icon submenu-trigger");
            item.querySelector("a").insertAdjacentElement("afterend", iconElement);
        }
    });
}

/* assign toggle function to menu which has submenus */
submenuToggle=()=>{
   /* find all menu which has submenus and making toggle */ 
   let submenuTrigger = document.querySelectorAll(".has-submenu > a");
   submenuTrigger.forEach((item)=>{
       item.addEventListener("click",(e)=>{
           e.preventDefault();
        let submenuContent = e.target.parentElement.querySelector('.submenu-content');
        slideToggle(submenuContent);
       })
   }); 
}

/* calling submenu detect function and slidetoggle function */
submenuDetect();
submenuToggle();
body {font-family: Arial, Helvetica, sans-serif;;}
.main-navigation-content ul {list-style: none;margin:0;padding:0;transition:all 0.35s ease;}
.main-navigation-content ul li {border:1px solid #cdcdcd;margin-bottom: -1px;/* margin-left: -1px; *//* margin-right: -1px; */background: #f5f5f5;/* margin: -1px; *//* height: 2.5rem; */}
.main-navigation-content ul li a {text-decoration: none;line-height: 2.5rem;padding-left: .5rem;color: #555;display: flex;align-items: center;justify-content: space-between;position: relative;z-index: 1;padding-right: .5rem;transition:all 0.35s ease;/* width: 100%; */height: 2.5rem;flex-grow: 1;}
.main-navigation-content ul li ul li {margin-left: -1px;margin-right: -1px;}
.down-arrow-icon {width: 2.5rem;display: flex;height: 2.5rem;align-items: center;justify-content: center;position: absolute;z-index: 0;right: 0;top: 0;cursor: pointer;margin-right: 0;line-height: 2.5rem;/* background: red; */}
.down-arrow-icon:after { content:"";    width: .03rem;    height: .03rem;    background-color: transparent;    border: solid black;    border-width: 0 2px 2px 0;    display: inline-block;    padding: 3px;    transform: rotate(45deg);    -webkit-transform: rotate(45deg);    position: absolute;    top: 35%;}    
.navigation-master-wrapper {border: 1px solid #cdcdcd;   width: auto;background: #f9f9f9;height: 500px;overflow-y: auto;}
.main-navigation {position: relative; height: auto;}
.main-navigation-trigger {margin-left: auto;position: relative;cursor: pointer;left: auto;right: .25rem;display: inline-block;padding: 0;font-size: 12px;top: 0;z-index: 100;margin-bottom: .5rem;display: none;}
.main-navigation-content {    display:none;}
.main-navigation-content > ul { list-style: none;padding: 0;margin: 0;}
.main-navigation-content > ul li { position: relative;}
.main-navigation-content .submenu-content {display: none;}
@media only screen and (min-width:800px) {
.main-navigation-content > ul li .submenu-content li a {padding-left:2rem;}
.main-navigation-content > ul li .submenu-content li ul li a {padding-left:3rem}
.main-navigation-content > ul li  .submenu-content {margin-top:1px;background: #fff;}
.main-navigation-content > ul li  .submenu-content li ul {margin-top:0;}
.main-navigation-content {display:block !important;width: 20rem;}
.main-navigation-content > ul li > ul {top:100%;}   
.main-navigation-content > ul li {position: initial;}
.main-navigation-content > ul > li {position: relative;}
.main-navigation-content > ul > li a {/* padding-left: 1rem; *//* padding-right: 1rem; */}
.main-navigation-content > ul li > ul li {position:relative;background: #fff;}
.main-navigation-content > ul li ul {transition:all 0.35s ease;}
}
<div class="main-navigation">
    <div class="main-navigation-trigger">
        <div class="main-navigation-trigger-inner"></div>
    </div>
    <div class="main-navigation-content">
        <ul>
            <li><a href="">Home</a></li>
            <li><a href="">About Us</a>
                <ul>
                    <li><a href="">History and Foundation</a></li>
                    <li><a href="">Company Overview</a></li>
                    <li><a href="">Working Strategy</a>
                        <ul>
                            <li><a href="">Employer Profile</a></li>
                            <li><a href="">Manpower Allocation</a></li>
                            <li><a href="">Marketing Strategy</a></li>
                        </ul>
                    </li>
                </ul>
            </li>
            <li><a href="">Services</a>
                <ul>
                    <li><a href="">Graphic Designing</a></li>
                    <li><a href="">Web Designing</a></li>
                    <li><a href="">App Development</a>
                        <ul>
                            <li><a href="">Android</a></li>
                            <li><a href="">IOS</a></li>
                            <li><a href="">Hybrid</a></li>
                        </ul>
                    </li>
                </ul>
            </li>
            <li><a href="">Contact</a></li>
        </ul>
    </div>
</div>


Solution

  • I tried to understand your code to improve it, but gave up on the idea after several hours. However, in order not to give up on yourself, here is a workaround you can use...
    I hope at least that my code will help you in the future to simplify yours.

    const menu = 
      [ { lib: 'Home',                        link: '#Home'      } 
      , { lib: 'About Us',                    link: '#About',    sub: 
          [ { lib: 'History and Foundation',  link: '#History'   } 
          , { lib: 'Company Overview',        link: '#Company'   } 
          , { lib: 'Working Strategy',        link: '#Working',  sub: 
              [ { lib: 'Employer Profile',    link: '#Employer'  } 
              , { lib: 'Manpower Allocation', link: '#Manpower'  } 
              , { lib: 'Marketing Strategy',  link: '#Marketing' } 
        ] } ] } 
      , { lib: 'Services',                    link: '#Services', sub: 
          [ { lib: 'Graphic Designing',       link: '#Graphic'   } 
          , { lib: 'Web Designing',           link: '#Web'       } 
          , { lib: 'App Development',         link: '#App',      sub: 
              [ { lib: 'Android',             link: '#Android'   } 
              , { lib: 'IOS',                 link: '#IOS'       } 
              , { lib: 'Hybrid',              link: '#Hybrid'    } 
        ] } ] } 
      , { lib: 'Contact',                     link: '#Contact'   } 
      ]
    
    const navMenu = document.querySelector('div.main-nav')
    
    const OpenMenus = [] // to memorize DOM (li.show) menus opened
    
    function makeMenu( nav, jso )
      {
      let eUL = nav.appendChild( document.createElement('ul') )
    
      for (const row of jso) 
        {
        let eLI = eUL.appendChild( document.createElement('li') )
          , lnk = eLI.appendChild( document.createElement('a') )
          ;
        lnk.textContent = row.lib 
        lnk.href        = row.link 
        if (!!row.sub)
          {
          eLI.className = 'submenu'
          makeMenu(eLI,row.sub)
          }
        }
      }
    makeMenu( navMenu, menu )
    
    navMenu.addEventListener('click', (e)=>
      {
      if (!e.target.matches('li.submenu > a')) return
      e.preventDefault()
    
      let level = 0
        , eLI   = e.target.closest('li')
        , eUL   = e.target.closest('ul')
        , mOpen = eLI.classList.toggle('show')
        ;
      for(;;level++) // get menu Level
        {
        eUL = eUL.parentElement.closest('ul')
        if (!eUL || !eUL.matches('div.main-nav ul')) break
        }
      for(let i = OpenMenus.length; (--i) > level;) // close all < sub levels
        {
        OpenMenus.pop().classList.remove('show')
        }
      if (OpenMenus[level] && OpenMenus[level] != eLI )
        OpenMenus.pop().classList.remove('show')
    
      if (mOpen)  OpenMenus.push(eLI)  // memorize adding menu opened
      else        OpenMenus.pop()     // or remove it. 
      })
    body { background: steelblue; font-family: Arial, Helvetica, sans-serif; }
    
    .main-nav * { 
      box-sizing : border-box;
      }
    .main-nav {
      width         : 20rem;
      border-bottom : 1px solid #fbfbfb;
      }
    .main-nav ul { 
      list-style : none;
      margin     : 0;
      padding    : 0;
      }
    .main-nav a { 
      display         : block;
      width           : 100%;
      border          : 1px solid #fbfbfb;
      border-bottom   : none;
      background      : #c7c7c7;
      text-decoration : none;
      line-height     : 2.5rem;
      padding         : 0 .5rem;
      color           : #2a2a2a;
      }
    .main-nav a:hover {
      background: #ebebeb;
      }
    .main-nav ul ul a    { padding-left:2rem; background: #d5d5d5; }
    .main-nav ul ul ul a { padding-left:3rem; background: #e1e1e1; }
    
    .main-nav li.submenu > ul { 
      max-height : 0;
      transition : max-height 0.25s ease-out;
      overflow   : hidden;
      }
    .main-nav li.submenu > a::after {
      display    : block;
      float      : right;
      content    : '\276F'; /*'\1405'; */
      transition : 180ms;
      transform  : rotate(90deg);
      }
    .main-nav li.submenu.show > ul {
      max-height : 500px;
      transition : max-height 0.35s ease-in;
      }
    .main-nav li.submenu.show > a  {
      padding-right    : .7rem; /* because '\276F' is not symetric ! */
      }
    .main-nav li.submenu.show > a::after {
      transform  : rotate(-90deg);
      }
    <div class="main-nav"></div>