Looping through an array

If you go to
http://stevenh.fatcow.com/test/index.html
click on the top-right box (GPS TIMING) and look at the console,


I dont see why the looping through the array only happens 9 tomes, the array holds 20

That can happen when you remove items from what you are looping through.

Let’s take a look at what’s happening:

var cables = document.getElementsByClassName("cable");
console.log(cables);
console.log(cables.length);
for (i = 0; i < cables.length; i++) {
    cables[i].classList.remove( 'cable' );
	console.log(i);
    cables[i].classList.add( 'inactive-cable' );
}

The getElementsByClassName documentation page says: elements is a live HTMLCollection of found elements.

So when you remove a class name, the cables collection also updates. That’s not good when you’re using a for loop. You can fix that problem by looping backwards through the collection instead.

A better solution is to use querySelectorAll instead, which gives you a non-live nodelist instead.

// var cables = document.getElementsByClassName("cable");
var cables = document.querySelectorAll(".cable");

You can also use the replace method, to replace an old class with a new class.

    // cables[i].classList.remove( 'cable' );
    // cables[i].classList.add( 'inactive-cable' );
    cables[i].classList.replace( 'cable', 'inactive-cable' );
2 Likes

querySelectorAll also has a forEach method, letting you easily loop through each of the items.

var cables = document.querySelectorAll(".cable");
// for (i = 0; i < cables.length; i++) {
cables.forEach(function (cable) {
  // cables[i].classList.replace( 'cable', 'inactive-cable' ));
  cable.classList.replace( 'cable', 'inactive-cable' ));
});

And now that the loop statement is simple enough, you could even use arrow notation there instead.

var cables = document.querySelectorAll(".cable");
cables.forEach(
  (cable) => cable.classList.replace( 'cable', 'inactive-cable' )
);

And if you don’t need the cables variable for anything else, you could condense that to:

document.querySelectorAll(".cable").forEach(
  (cable) => cable.classList.replace( 'cable', 'inactive-cable' )
);
2 Likes

Another Grace Hopper Building question. I once was in Grace Hopper’s office in the Pentagon; I should visit the Grace Hopper Building next time I am in San Diego.

Try using try/catch around the code. I think what is happening is that there is an error causing the loop to be terminated prematurely. I don’t see items being added to or removed from the cables array so I think that is not the cause.

Do you know how to use breakpoints in the debugger and single-step through the debugger? You could break at the beginning of the loop then single-step until you get to the item where the loop ends and try to see a problem there.

Well @Paul_Wilkins is right that cables.length is changing during the loop. I do not know why.

So I would log more data. I would at least log cables[i].classList at the beginning of the loop to see what it is before the remove and add. Other things like i and cables.length at the top might help. Yes i does not change during the loop but it helps to have it so it is easier to match with the other data.

Because cables is a live collection, thanks to getElementsByTagName, every time you change the document that collection is updated too. That results in a shorter cables array length each time you remove one of the cable class names.

All of the code examples in my previous post fix that problem. I recommend the last example I gave.

1 Like