Difference between using nextElementSibling vs getElementsByClassName

Hi guys, so I’m going through How To examples on w3schools and I ran into a problem. They don’t have a community so I figured I could ask here, hope it’s ok.

I’m looking at: w3schools.com/howto/tryit.asp?filename=tryhow_js_accordion

It’s a simple accordion where you click a section and the panel gets displayed (front “none” to “block”). Looking at the section, they used “var panel = this.nextElementSibling” and then “panel.style.display =block” to show or hide the panel. All good there. However, when I tried to recreate it, I used another method and it did not work.
I added variable “var pan = document.getElementsByClassName(“panel”)”, and instead of using “panel.style.display =block”, I used “pan[i].style.display=“block””.

My logic is that the “var pan” is an array of elements with class “panel”, and when I do “pan[i]”, it should be referring to the panel directly below the “acc[i]” element so it’s the same thing.

So my question is: can someone explain to me why getting the panel by using “this.nextElementSibling” and “panel.style.display” works; but getting the panel by “document.getElementsByClassName(“panel”)” and then “pan[i].style.display” doesn’t?

Thanks in advance

Because i was declared globally all of your event callbacks would be using the same value for i - whatever that value of i is when you click.

Try altering the code like below to see why

var acc = document.getElementsByClassName("accordion");
var i;  //declared globally

for (i = 0; i < acc.length; i++) {
    acc[i].addEventListener("click", function() {
        var panel = this.nextElementSibling;
        if (panel.style.display === "block") {
            panel.style.display = "none";
            alert(i)  //here
        } else {
            panel.style.display = "block";
            alert(i)  //here

i = 10;  //add this

You will see that all clicks will alert 10.

There are ways to get around this. Declaring the variable with block scope using let would work.
for (let i = 0; i < acc.length; i++) {
You probably could do something with closures too but their scheme of targeting the sibling is probably easiest and will not break if your page dynamically added more panels later.

Ah I see. So when I did “pan[i]” it was actually trying to access pan[3], which doesn’t exist so nothing is shown…

Although I still have 2 confusions if you don’t mind explaining:

  1. By “event callbacks”, you are referring to the “function()”, that is the second argument of the addEventListener, right? I’m new to the idea of callbacks.
  2. Why DOES making i local work? I understand i is saved to a specific value at the end of the loop if it is global, and making it local resets it after the loop. But does making it local also allow “function()” to remember its value at the moment as it is being added to acc[i]? If so, why doesn’t it remember the global i when it is going through the loop? (Since whether i is reset(local) or not(global), it is always done after having gone through the loop)
    Or perhaps I fundamentally misunderstood event listeners, that it doesn’t remember anything, but it works by going through the loop each time the event is activated?