0

I have a JS foreach function like below:

Edit: my minimal reproducible example

var liArray = document.getElementsByClassName("listItem");
liArray.forEach(function(item) {
  if (item.getAttribute("data-code").substring(0, 2) == 15) {
    console.log("success");
  }
});
<ul>
  <li class="item>
    <div data-code="151" class="listItem">example</div>
  </li>
  <li class="item>
    <div data-code="152" class="listItem">example</div>
  </li>
  <li class="item>
    <div data-code="153" class="listItem">example</div>
  </li>
  <li class="item>
    <div data-code="154" class="listItem">example</div>
  </li>
  <li class="item>
    <div data-code="155" class="listItem">example</div>
  </li>
  <li class="item>
    <div data-code="156" class="listItem">example</div>
  </li>
</ul>

Than I got 150 times of success in my console. But when I changed console.log to item.className = "disabled". Like:

liArray.forEach(function(item) {
  if (item.getAttribute("data-code").substring(0, 2) == 15) {
    item.className = 'disabled';
  }
});

It turns out that only half of (75) the items have class disabled.

I have no idea what's going wrong with my code.
Hope someone can help, thank you!

syltai
  • 112
  • 1
  • 12
  • 4
    Please provide a [mcve]. – Felix Kling Sep 23 '20 at 07:13
  • 1
    Please show more context. Here are two possibilities that most likely: 1. You're running this function twice; 2. There are duplicates in your `liArray`. – Hao Wu Sep 23 '20 at 07:15
  • FYI: If you have `data-*` attributes you can use the [dataset](https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes) property. For example: `item.dataset.code` is the same as your `item.getAttribute("data-code")`. – Reyno Sep 23 '20 at 07:16
  • @FelixKling Sorry for my neglect, I have added more details about this question. – syltai Sep 23 '20 at 07:28
  • @Reyno Thanks for your information! The ```data-code``` here is self-defined data, so this method can't work. – syltai Sep 23 '20 at 07:30
  • Does this answer your question? [JS: iterating over result of getElementsByClassName using Array.forEach](https://stackoverflow.com/questions/3871547/js-iterating-over-result-of-getelementsbyclassname-using-array-foreach) – Reyno Sep 23 '20 at 07:48
  • @Reyno Thank you, it helps me learn about what ```HTMLCollection``` may cause some problems :) – syltai Sep 23 '20 at 07:55

2 Answers2

1

When you define a new className, existing item is removed from the collection which mutates the array. This causes "confusion" in loop.

const foos = document.getElementsByClassName('foo');

Array.from(foos).forEach((foo, i) => {
  foo.className = 'bar';
  console.log(`Array length: ${foos.length}, current index: ${i}`);
});
<ul>
<li class='foo'>Foo 1</li>
<li class='foo'>Foo 2</li>
<li class='foo'>Foo 3</li>
<li class='foo'>Foo 4</li>
<li class='foo'>Foo 5</li>
</ul>

Therefore you should add a new class instead of replacing existing one with: item.classList.add('disabled');

const foos = document.getElementsByClassName('foo');

Array.from(foos).forEach((foo, i) => {
  foo.classList.add('bar');
  console.log(`Array length: ${foos.length}, current index: ${i}`);
});
<ul>
<li class='foo'>Foo 1</li>
<li class='foo'>Foo 2</li>
<li class='foo'>Foo 3</li>
<li class='foo'>Foo 4</li>
<li class='foo'>Foo 5</li>
</ul>
Samuli Hakoniemi
  • 18,740
  • 1
  • 61
  • 74
  • However, the OP could just do what you are doing and use `Array.from(foos).forEach(...)`. `foos` will change but not the array you are calling `forEach` on. – Felix Kling Sep 23 '20 at 08:16
0

getElementsByClassName returns a HTMLCollection, which has no forEach.

Use the normal for loop:

var list = document.getElementsByClassName("events");
for (let item of list) {
    console.log(item.id);
}
ceving
  • 21,900
  • 13
  • 104
  • 178
  • Thank you for explaining the HTMLCollection for me! In my case, the problem seems to lie in change ```className``` directly, which cause confusion in loop, like Samuli has mentioned. But thank you anyway for sharing your insight :) – syltai Sep 23 '20 at 08:05