7

I have a for loop that runs through a set of elements, removing the 'selected' class from each. However, it skips over every second iteration. I've found that I can get around this by adding j--, which I guess is fine except for lengthening my code. But I wonder if someone could explain why it skips and perhaps suggest a more succinct way of writing this code? (I'm still learning the ropes and want to make sure I understand what's going on.)

var selections = document.getElementsByClassName(name + 'selected');
for (var j = 0; j < selections.length; j++) {
  selections[j].classList.remove('selected');
  j--; // the fix
}

// where name is a present variable

Thanks for your time!

dedaumiersmith
  • 337
  • 4
  • 14
  • 3
    Loop backwards so you don't walk over indexes that have shifted down due to preceding removal – Alex K. Aug 19 '16 at 15:12
  • [Google: site:stackoverflow.com javascript getelementsbyclassname skipping elements](https://www.google.com/search?num=50&q=site%3Astackoverflow.com+javascript+getelementsbyclassname+skipping+elements&oq=site%3Astackoverflow.com+javascript+getelementsbyclassname+skipping+elements&gs_l=serp.3...12697.16068.0.16571.5.5.0.0.0.0.253.595.2j1j1.4.0....0...1c.1.64.serp..2.0.0.QGwqQUnyhVs) –  Aug 19 '16 at 15:24

2 Answers2

13

This is because getElementsByClassName() returns a live HtmlCollection; in other words, the HtmlCollection automatically updates, so as you remove the class "selected" from an element, that element is removed from the collection.

You can simply do;

var selections = document.getElementsByClassName(name + 'selected');
while (selections.length) {
    selections[0].classList.remove('selected');
}

... instead.


Alternatively, as pointed out by Paul Roub in the comments, you can iterate in reverse;

for (var j = selections.length-1; j >= 0; j--) {
  selections[j].classList.remove('selected');
}

Or, you can avoid a live HtmlCollection completely, either by copying the collection to an array;

var selections = Array.prototype.slice.call(document.getElementsByClassName(name + 'selected'));
for (var j = 0; j < selections.length; j++) {
  selections[j].classList.remove('selected');
}

... or, as pointed out by Yury Tarabanko in the comments, using querySelectorAll instead;

var selections = document.querySelectorAll('.' + name + 'selected');
for (var j = 0; j < selections.length; j++) {
  selections[j].classList.remove('selected');
}
Community
  • 1
  • 1
Matt
  • 74,352
  • 26
  • 153
  • 180
1

getElementsByClassName is a live HTML collection so when you remove the class the element, it is updated. So that means what used to be in the index after it is now in the location where you removed the element.

var selections = document.getElementsByClassName('selected');
console.log("before: ", selections.length);
selections[0].classList.remove("selected");
console.log("after: ", selections.length);
<div class="selected"></div>
<div class="selected"></div>
<div class="selected"></div>
<div class="selected"></div>
<div class="selected"></div>

Looping backwards works because the items are not shifting down.

Option option is to do a while loop.

var selections = document.getElementsByClassName('selected');
while(selections.length) {
    selections[0].classList.remove("selected");
}
epascarello
  • 204,599
  • 20
  • 195
  • 236