2

I want to get all elements which belong to two specific different classes and remove and add these classes seperately from these elements. I tried:

.concealed {
  display: none;
}
.slide {
  background: #333;
  color: #fff;
}

// <div class="slide concealed">myText</div><br> <div class="slide"><div class="concealed">myText</div></div><br> <div class="slide"><div class="concealed">myText_2</div></div><br> <div class="slide"><div class="concealed">myText_3</div></div><br>

    // var slides = document.getElementsByClassName('slide');
    // var slides = document.querySelectorAll('.slide, .concealed');
       var slides = document.getElementsByClassName('slide concealed');

slides[0].classList.remove("concealed");

As you can see I tried several ways to achieve this but I can only remove and add "concealed" when I do var slides = document.getElementsByClassName('concealed'); . When doing getElementsByClassName with multiple classnames it seems to miss out concealed and only get slide. E.g. doing slides[0].classList.remove("concealed"); after document.getElementsByClassName('slide concealed'); has no effect.

I am sure I am missing something, this can't that hard to implement. Any ideas? Thanks.

Rome
  • 432
  • 6
  • 27
  • *"...and remove and add these classes seperately from these elements..."* I'm afraid I don't quite understand what you're trying to do with the elements. Your commented-out code using `querySelectorAll` should work, but it seems like it didn't. Can you give us more information on that? – T.J. Crowder Sep 15 '19 at 16:21
  • after trying `var slides = document.querySelectorAll('.slide, .concealed');` I noticed that `slides.length`was the double count of my elements and assumed that this can't be right. it seems i need to read about NodeList handling ;) – Rome Sep 15 '19 at 16:32

1 Answers1

1

The issue is that getElementsByClassName is a live HTMLCollection. When an element no longer matches (because you've removed the class), it's removed from the collection:

const list = document.getElementsByClassName("example");
console.log(list.length);       // 2
console.log(list[0].innerHTML); // "A"
list[0].classList.remove("example");
console.log(list.length);       // 1
console.log(list[0].innerHTML); // "B"
<div class="example">A</div>
<div class="example">B</div>

This means that if you're doing a loop and operate on the first entry, then increment your index, you'll miss what used to be the second entry because it's the first entry now.

A couple of ways to fix that:

  1. Loop backward, or
  2. Convert the HTMLCollection into an array before starting, or
  3. Use querySelectorAll to get a snapshot NodeList instead, so that the lists contents don't change while you're updating.

Here's an example removing the classes red and bold from a series of elements using querySelectorAll so the NodeList is static:

setTimeout(() => {
    const list = document.querySelectorAll(".red, .bold");
    for (let n = 0; n < list.length; ++n) {
        list[n].classList.remove("red");
        list[n].classList.remove("bold");
    }
}, 800);
.red {
    color: red;
}
.bold {
    font-weight: bold;
}
<div class="red">red 1</div>
<div class="bold">bold 1</div>
<div class="red bold">red 2 and bold 2</div>
<div class="bold">bold 3</div>
<div class="bold">bold 4</div>

See also my answer here: NodeList is now officially iterable, meaning you should be able to use a for-of loop on it. That answer shows how to polyfill that on slightly-older environments that haven't implemented it yet. It also shows how to add iterability to HTMLCollection (but note that HTMLCollection is not specified to be iterable).

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 1
    The problem was indeed that `getElementsByClassName` is a live collection and not a snapshot. Thank you! – Rome Sep 15 '19 at 16:34