Question

I am trying to loop through all the elements retruned from getElementsByTagName("input") using forEach. Any ideas why this does not work in FF, Chrome or IE?

<html>
    <head>
    </head>
    <body>
        <input type="text" value="" />
        <input type="text" value="" />
        <script>
            function ShowResults(value, index, ar) {
                alert(index);
            }
            var input = document.getElementsByTagName("input");
            alert(input.length);
            input.forEach(ShowResults);
    </script>
    </body>
</html>
Was it helpful?

Solution

You need to convert the nodelist to array with this:

<html>
    <head>
    </head>
    <body>
        <input type="text" value="" />
        <input type="text" value="" />
        <script>
            function ShowResults(value, index, ar) {
                alert(index);
            }
            var input = document.getElementsByTagName("input");
            var inputList = Array.prototype.slice.call(input);
            alert(inputList.length);
            inputList.forEach(ShowResults);
    </script>
    </body>
</html>

or use for loop.

for(let i = 0;i < input.length; i++)
{
    ShowResults(input[i].value);
}

and change ShowResults function to:

function ShowResults(value) {
   alert(value);
}

Why do we need to do that?
Some objects in JavaScript look like an array, but they aren’t one. That usually means that they have indexed access and a length property, but none of the array methods. Examples include the special variable arguments, DOM node lists, and strings. Array-Like Objects and Generic Methods gives tips for working with array-like objects. source

UPDATE for 07.10.2019
Nowdays with ES6 you can use [...inputList].forEach, or Array.from(inputList)

OTHER TIPS

Yay, ES6:

const children = [...parent.getElementsByTagName('tag')];
children.forEach((child) => { /* Do something; */ });

MDN Doc for Spread Operator (...)

getElementsByTagName returns an HTMLCollection, which do not have a forEach method. But, there's a simple tweak that will allow you to iterate with forEach without creating an intermediate array: use querySelectorAll instead. querySelectorAll returns a NodeList, and modern browsers have a NodeList.prototype.forEach method:

document.querySelectorAll('input')
  .forEach((input) => {
    console.log(input.value);
  });
<input type="text" value="foo">
<input type="text" value="bar">

Another benefit to using querySelectorAll is that it accepts comma-separated CSS selectors, which are far more flexible and precise than just tag names. For example, the selector

.container1 > span, .container2 > span

will only match spans which are children of elements with a class of container1 or container2:

document.querySelectorAll('.container1 > span, .container2 > span')
  .forEach((span) => {
    span.classList.add('highlight');
  });
.highlight {
  background-color: yellow;
}
<div class="container1">
  <span>foo</span>
  <span>bar</span>
</div>
<div class="container2">
  <span>baz</span>
</div>
<div class="container3">
  <span>buzz</span>
</div>

If you want to use NodeList.prototype.forEach on ancient browsers that do not have the method built-in, simply add a polyfill. The following snippet will work on IE11:

// Polyfill:
if (window.NodeList && !NodeList.prototype.forEach) {
  NodeList.prototype.forEach = function(callback, thisArg) {
    thisArg = thisArg || window;
    for (var i = 0; i < this.length; i++) {
      callback.call(thisArg, this[i], i, this);
    }
  };
}

// Main code:
document.querySelectorAll('.container1 > span, .container2 > span')
  .forEach(function(span) {
    span.classList.add('highlight');
  });
.highlight {
  background-color: yellow;
}
<div class="container1">
  <span>foo</span>
  <span>bar</span>
</div>
<div class="container2">
  <span>baz</span>
</div>
<div class="container3">
  <span>buzz</span>
</div>

Because input is not an array, it's HTMLCollection Use a for loop would be better.

And since HTMLCollections are array-like objects you can call Array#forEach on it like this

Array.prototype.forEach.call(input, ShowResults);

The reason, this does not work is because 'getElementsByTagName' returns an array - like Object rather than an actual array. In case you are not aware, here's how both of them look like :-

var realArray = ['a', 'b', 'c'];
var arrayLike = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3
};

Thus, since Array-like objects inherit from 'Object.prototype' instead of 'Array.prototype', this means that Array-like Objects can't access common Array prototype methods like forEach(), push(), map(), filter(), and slice().

Hope that helps!

It's becauseinput is html collection. html collection don't have forEach.

you can easily conver it to array by Array.prototype.slice

example:

function ShowResults(value, index, ar) {
            alert(index);
        }
        var input = document.getElementsByTagName("input");
        alert(input.length);
input = Array.prototype.slice.call(input)
        input.forEach(ShowResults);

http://jsfiddle.net/fPuKt/1/

HTMLCollections doesn't have the same methods as arrays. You can check this thing by tiping this in the javascript console of your browser.

var elements = document.getElementsByClassName('some-class');
'forEach' in elements;

And the console will return true if elements (in this case) has a method called forEach to call.

In ES6 you can use the spread operator to convert an HtmlCollection to an Array. see this question Why can't I use Array.forEach on a collection of Javascript elements?

input = [...input]
input.forEach(ShowResults)

If you can use ES2015, you can use Array.from() to convert the HTMLCollection returned by getElementsByTagName() into a real array. If you change line 11 to the following, the rest of the code works as-is:

var input = Array.from(document.getElementsByTagName("input"));

I did this:

HTMLCollection.prototype.map = Array.prototype.map;

You can now use map on every HTMLCollection.

document.getElementsByTagName("input").map(
    input => console.log(input)
);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top