2

I am trying to create a dropdown menu in javascript (vanilla). I know how to do it in CSS, but i will never really understand Javascript if i don't try to do things in Javascript, hence the question.

My issue is that i cannot work out how to use the array from getElementsByClassName and loop it to target only the <li> that was clicked. I have googled until my head hurts, This is due to a lack of understanding/knowledge on how to get/use arrays with an onclick event with a loop. If someone could kindly help me out with this

function startUp() {
    subMenu();
}

function subMenu() {
    var menu = document.getElementById('Menu').addEventListener('click', subMenu);
    var drop = document.getElementsByClassName('sub');
        for (i = 0; i < drop.length; i++){
            drop += drop.length;
            if (drop.style.display === 'none') {
                drop.style.display = 'inline-block';
            }else {
                drop.style.display = 'none';
            }
        }
}
window.onload = startUp;
<nav id="Menu">
    <ul>
        <li>Home</li>
        <li>Members</li>
        <li>Gallery
            <ul class="sub" style="display: none;">
                <li>link</li>
                <li>link 1</li>
                <li>link 2</li>
                <li>link 3</li>
            </ul>
        </li>
        <li>Contact
            <ul class="sub" style="display: none;">
                <li>E-Mail</li>     
            </ul>
        </li>
        <li>Steam Community</li>
        <li>Youtube</li>
    </ul>
</nav>

I have tried multiple different variations of code all afternoon, I just cannot seem to get it all to work together, without writing multiple functions for ID's instead of using classes.

Adam Azad
  • 11,171
  • 5
  • 29
  • 70
Ricky
  • 763
  • 3
  • 7
  • 29

2 Answers2

2

drop is a nodeList which represents elements with sub class.

Every element from drop can be accessed using its index. => drop[i]

for (i = 0; i < drop.length; i++){
        //process drop[i]
}

I recommend you to use follow method:

You need to attach a click event for every li with sub class. The problem is that when subMenu function is invoked, it is created a single context. If you don't use let keywork, you'll see that you receive error because when you click li item, last value of i in for loop will be 2 in this case. This is an usual problem. Please take a look here : closures inside loops. For this type of problem, ECMS6 offers let keyword.

function startUp() {
    subMenu();
}

function subMenu() {
    var drop = document.getElementsByClassName('sub');
    for(let i=0;i<drop.length;i++){
        drop[i].addEventListener('click', function(){
          if(drop[i].querySelector('ul').style.display==='none'){
            drop[i].querySelector('ul').style.display='block';
          }
          else
            drop[i].querySelector('ul').style.display='none';
        });
    };
}
window.onload = startUp;
<nav id="Menu">
    <ul>
        <li>Home</li>
        <li>Members</li>
        <li class="sub">Gallery
            <ul style="display: none;">
                <li>link</li>
                <li>link 1</li>
                <li>link 2</li>
                <li>link 3</li>
            </ul>
        </li>
        <li class="sub">Contact
            <ul style="display: none;">
                <li>E-Mail</li>     
            </ul>
        </li>
        <li>Steam Community</li>
        <li>Youtube</li>
    </ul>
</nav>

If you don't want to use let keyword, you should use Immediately-invoked function expression.

If you want to select the links without the whole menu closing again, you have to use event.target property, which indicates the DOM element that initiated the event. Thus, you apply toggle event only when you are clicking the li with sub class and not ul childrens.

function startUp() {
    subMenu();
}

function subMenu() {
   var drop = document.getElementsByClassName('sub');
   for(var i=0;i<drop.length;i++){
     (function(index){
         drop[index].addEventListener('click', function(e){
            if(e.target.className=="sub"){
                if(drop[index].querySelector('ul').style.display==='none'){
                   drop[index].querySelector('ul').style.display='block';
                }
                else
                   drop[index].querySelector('ul').style.display='none';
            }
         });
     })(i);   
    };
}
window.onload = startUp;
<nav id="Menu">
    <ul>
        <li>Home</li>
        <li>Members</li>
        <li class="sub">Gallery
            <ul style="display: none;">
                <li>link</li>
                <li>link 1</li>
                <li>link 2</li>
                <li>link 3</li>
            </ul>
        </li>
        <li class="sub">Contact
            <ul style="display: none;">
                <li>E-Mail</li>     
            </ul>
        </li>
        <li>Steam Community</li>
        <li>Youtube</li>
    </ul>
</nav>
Mihai Alexandru-Ionut
  • 47,092
  • 13
  • 101
  • 128
  • I have edited my code to follow this, However it works wherever i click on the Nav bar, and it drops both sub-menus at the same time, Is there a way to do this with only one displaying at a time? – Ricky Jan 21 '17 at 19:31
0

You have some errors!

function startUp() {
    var menu = document.getElementById('Menu');//Set handler here
    menu.addEventListener('click', subMenu);
    subMenu();
}

function subMenu() {

    var drop = document.getElementsByClassName('sub');
        for (i = 0; i < drop.length; i++){
            drop += drop.length; //???? drop it is array of htmlElements if you want get one elemnt use drop[i];
            if (drop.style.display === 'none') {
                drop.style.display = 'inline-block';
            }else {
                drop.style.display = 'none';
            }
        }
}
window.onload = startUp;
Smiranin
  • 738
  • 1
  • 5
  • 14