kennyL
kennyL

Reputation: 150

Javascript trying to iterate over getElementsByClassName

I want to start by saying I am fairly new to Plain JavaScript so I am sorry if it is something obvious.

I am trying to remove errors when someone resubmits the form with the following code.

let div = document.getElementsByClassName('error')
if (div.length > 0 ){
    console.log(div);
    console.log('Length: ' + div.length);
    for (i = 0; i <= div.length; i++){
        console.log('i= ' + i)
        let e = div[i]
        e.innerHTML = ''
        e.classList.remove('error')
    }
}

That works, sorta, I end up getting every other div with the error class being altered instead of all of them.

Developer Console Screenshot

Upvotes: 1

Views: 357

Answers (2)

zer00ne
zer00ne

Reputation: 43880

Live and "Static" HTMLCollections / NodeLists

The older methods .children, .getElementsByTagName(), .getElementsByName(), .getElementsByClassName(), etc. return a Live Collection of DOM Objects. This means if any object (i.e. elements, i.e. <div>, <span>, etc,) in this collection (a.k.a. HTMLCollection, a.k.a. NodeList) is modified, or removed, or if a new object is added, the overall collection will change immediately. So on every loop you actually have a different array (or array-like object), therefore the results are a product of an ever changing group of values and quantities.

  • Use .querySelectorAll() which returns a "static" NodeList.

  • With a for loop, declare your increment var (i) by:

    declaring it outside of the for loop as: var i = 0;

    OR

    declaring within the for loop as: let i = 0;

Demo

var div = document.querySelectorAll('.error');
var qty = div.length;
for (let i = 0; i < qty; i++) {
  console.log('i= ' + i);
  var d = div[i];
  d.innerHTML = 'GOOD';
  d.classList.toggle('error', 'good');
}
.as-console-wrapper.as-console-wrapper {
  transform: translateX(100%);
  max-width: 250px;
  height: 100%;
  color: blue;
  font: 400 15px/1.3 'Consolas';
}
<div class='error'>ERROR</div>
<div class='error'>ERROR</div>
<div class='error'>ERROR</div>
<div class='error'>ERROR</div>
<div class='error'>ERROR</div>
<div class='good'>GOOD</div>
<div class='error'>ERROR</div>
<div class='error'>ERROR</div>
<div class='error'>ERROR</div>
<div class='error'>ERROR</div>

Upvotes: 1

Pointy
Pointy

Reputation: 413717

The .getElementsByClassName() function returns a live NodeList. That means that when you change the class on an entry, it disappears from the list. The length of the list drops by one, so your iteration skips every other entry.

One good way to do it is to just operate on the first thing in the list until the list is empty:

while (div.length) {
    let e = div[0]
    e.innerHTML = ''
    e.classList.remove('error')
}

Upvotes: 5

Related Questions