How to use querySelectorAll without having to specify array elements afterwards?

Asked

Viewed 274 times

1

I’m trying to use querySelectorAll to see when <li> is clicked and display the menu of each 1 the problem is having to keep specifying the array, example obj[0], for each 1, I wondered if I could do this more automatically? without having to specify for each <li> different?

html:

<ul class="obj-menu">
        <div class="obj-header">title</div>
        <li class="obj-item">
            <span class="obj-title">text1</span>
            <ul class="obj-menu-sub">
                <li class="obj-item-sub">texto 2</li>
                <li class="obj-item-sub">texto 3</li>
                <!-- <li class="obj-item-sub"></li> -->
            </ul>
        </li>
        <li class="obj-item">
                <span class="obj-title">title 2</span>
                <ul class="obj-menu-sub">
                    <li class="obj-item-sub">texto 1</li>
                    <li class="obj-item-sub">texto 2</li>
                    <!-- <li class="obj-item-sub"></li> -->
                </ul>
            </li>
    </ul>

What I managed to do so far in JS::

abreObj = document.querySelectorAll(".obj-item");
abreObj[1].addEventListener("click",function(event){
    var objSub = abreObj[1].querySelector(".obj-menu-sub");


    if(objSub.style.display == "block"){
        objSub.style.display = 'none';
    }else{
        objSub.style.display = "block";
    }
})

2 answers

5


Unlike jQuery (which some would say is a bit simpler), when you use the method querySelectorAll, an object NodeList is returned, which is similar to a array and containing all the elements selected.

To add a Event Listener each of them, it is ideal that you use a way to iterate over each element. In the following example, we will use the forEach:

abreObjs = document.querySelectorAll('.obj-item')

abreObjs.forEach((abreObj) => {
  abreObj.addEventListener('click', () => {
    const objSub = abreObj.querySelector('.obj-menu-sub')

    // Ao invés de usar um if/else, vamos usar um operador ternário. :)
    objSub.style.display = objSub.style.display === 'none' ? 'block' : 'none'
  })
})
<ul class="obj-menu">
  <div class="obj-header">title</div>
  <li class="obj-item">
    <span class="obj-title">text1</span>
    <ul class="obj-menu-sub">
      <li class="obj-item-sub">texto 2</li>
      <li class="obj-item-sub">texto 3</li>
      <!-- <li class="obj-item-sub"></li> -->
    </ul>
  </li>
  <li class="obj-item">
    <span class="obj-title">title 2</span>
    <ul class="obj-menu-sub">
      <li class="obj-item-sub">texto 1</li>
      <li class="obj-item-sub">texto 2</li>
      <!-- <li class="obj-item-sub"></li> -->
    </ul>
  </li>
</ul>


NodeList === Array // false

Note that although they are similar, the object NodeList is not a array. If you need to turn one NodeList in Array, you can choose to use one of the following two exemplified methods.

const nodeList = document.querySelectorAll('a')

// Primeiro método de transformar um NodeList num Array:
const firstMethod = Array.from(nodeList)

// Segundo método de transformar um NodeList num Array:
const secondMethod = [...nodeList]

// Provando que `nodeList` não é um array:
console.log(nodeList instanceof Array, nodeList instanceof NodeList) // false, true
console.log(firstMethod instanceof Array, firstMethod instanceof NodeList) // true, false
console.log(secondMethod instanceof Array, secondMethod instanceof NodeList) // true, false
<span>1</span>
<span>2</span>
<span>3</span>
<span>4</span>

Reference:

  • Obg, really helped, the explanation was very clear and gave to understand right!

3

The simplest way is to add event listeners within a loop:

for (const element of document.querySelectorAll(".obj-item")) {
    elememt.addEventListener("click",function(event){ ... })
}

But you can add the listener to the container and find out the origin of the click:

document.querySelector(".obj-menu").addEventListener("click", function(event) {
  //O elemento clicado
  let objItem = event.target;
  console.log(objItem);
  
  //Caso clique no elemento com a classe obj-header ou obj-menu pode parar, só precisamos continuar se for em outro elemento
  if (objItem.classList.contains('obj-header') || objItem.classList.contains('obj-menu')) {
     return;
  }

  //Verifica se o elemento clicado possui a classe obj-item
  while (!objItem.classList.contains('obj-item')) {
    //Enquanto não, pega o próximo elemento acima
    objItem = objItem.parentElement;
  }
  
  //Aqui objItem é o elemento com classe obj-item mais próximo
  console.log(objItem);

  const objSub = objItem.querySelector(".obj-menu-sub");

  if (objSub.style.display == "block") {
    objSub.style.display = 'none';
  } else {
    objSub.style.display = "block";
  }
});
<ul class="obj-menu">
  <div class="obj-header">title</div>
  <li class="obj-item">
    <span class="obj-title">text1</span>
    <ul class="obj-menu-sub">
      <li class="obj-item-sub">texto 2</li>
      <li class="obj-item-sub">texto 3</li>
      <!-- <li class="obj-item-sub"></li> -->
    </ul>
  </li>
  <li class="obj-item">
    <span class="obj-title">title 2</span>
    <ul class="obj-menu-sub">
      <li class="obj-item-sub">texto 1</li>
      <li class="obj-item-sub">texto 2</li>
      <!-- <li class="obj-item-sub"></li> -->
    </ul>
  </li>
</ul>

  • 1

    Good response with two suggestions. You could use .closest() than the while.

  • 1

    Really interesting how your answer comes to the solution! + 1 :)

  • 1

    @Sergio didn’t even remember that role, her only problem is that she needs the match, have to add some polyfills

  • @Luizfelipe interesting is, but I would only use if I needed to manipulate this function in a given moment for some reason, the first form is simpler and more performatic

Browser other questions tagged

You are not signed in. Login or sign up in order to post.