Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

forEach on querySelectorAll not working in recent Microsoft browsers

I am making a script for choices about a product (colors etc), which works in every browser except for Internet Explorer (11) & Edge.

I put the choices of each parameter in a array and apply a function to them with the array.forEach() method.

Example for the color parameter:

var color_btns = document.querySelectorAll('#color > p');
color_btns.forEach(function(color) {
    color.onclick = function () {
        color_btns.forEach(function(element) {
            if (element.classList.contains('selected')) {
                element.classList.remove('selected');
            }
        });
        color.classList.add('selected');
        document.querySelector('#f_color').value = color.dataset.id;
    };
});

I get the following output in the console of both IE & Edge:

Object doesn't support property or method 'forEach'

After searching about the issue, I learnt that this function should be supported by IE 9 and newer. I tried to define the function by myself without success. When I log the function it is defined as a function (with "[native code]" inside).

I replaced every .forEach by a for and it's working pretty well,

  • but how can I make it work ?
  • Is there a specific usage of the forEach() for Internet Explorer & Edge ?

I thought it was Array.prototype.forEach and that recent versions of IE (and all versions of Edge) had it...?

like image 957
AymDev Avatar asked Oct 25 '17 09:10

AymDev


People also ask

Can you use forEach on querySelectorAll?

Since nodeList selected by querySelectorAll has an array-like structure so you can directly apply forEach method with it and pass element as the first element in the callback function.

Why is my querySelectorAll not working?

The "querySelectorAll is not a function" error occurs for multiple reasons: calling the method on an object that is not a valid DOM element or the document object. placing the JS script tag above the code that declares the DOM elements. misspelling the querySelectorAll method (it's case sensitive).

Can I use forEach on NodeList?

forEach() The forEach() method of the NodeList interface calls the callback given in parameter once for each value pair in the list, in insertion order.

Does querySelector work in IE?

querySelectorAll works fine in IE9. It even works in IE8.

What is the difference between foreach and queryselectorall?

The querySelectorAll method selects all matching element from a document specified by a CSS selector, whereas the forEach method is the advanced way to loop through the array elements. In this section, we selected the DOM element using querySelectorAll and looped it using the forEach method.

How to use foreach method with queryselectorall nodelist?

Since nodeList selected by querySelectorAll has an array-like structure so you can directly apply forEach method with it and pass element as the first element in the callback function. In the previous example, you have looped through all the elements and alerted a message, but in a real-world we have to do something with the selected elements.

How to avoid looping in queryselectorall ()?

By simply avoiding looping directly over the return value of the querySelectorAll () method. This is a screen shot of the result on Chrome DevTools console: Another alternative is to use Array.from () method to convert the NodeList object to a JavaScript Array but it's only available on modern browsers.

What does queryselectorall return?

querySelectorAllreturns a static NodeList(a snapshot of matching elements as of when you call it). getElementsByTagName, getElementsByTagNameNS, getElementsByClassName, and the childrenproperty on a ParentNode(Elements are parent nodes) return liveHTMLCollectioninstances (if you change the DOM, that change is reflected live in the collection).


1 Answers

Most DOM methods and collection properties aren't actually arrays, they're collections:

  • querySelectorAll returns a static NodeList (a snapshot of matching elements as of when you call it).
  • getElementsByTagName, getElementsByTagNameNS, getElementsByClassName, and the children property on a ParentNode (Elements are parent nodes) return live HTMLCollection instances (if you change the DOM, that change is reflected live in the collection).
  • getElementsByName returns a live NodeList (not a snapshot).

NodeList only recently got forEach (and keys and a couple of other array methods). HTMLCollection didn't and won't; it turned out adding them would break too much code on the web.

Both NodeList and HTMLCollection are iterable, though, meaning that you can loop through them with for-of, expand them into an array via spread ([...theCollection]), etc. But if you're running on a browser where NodeList doesn't have forEach, it's probably too old to have any ES2015+ features like for-of or iteration.

Since NodeList is specified to have forEach, you can safely polyfill it, and it's really easy to do:

if (typeof NodeList !== "undefined" && NodeList.prototype && !NodeList.prototype.forEach) {
    // Yes, there's really no need for `Object.defineProperty` here
    NodeList.prototype.forEach = Array.prototype.forEach;
}

Direct assignment is fine in this case, because enumerable, configurable, and writable should all be true and it's a value property. (enumerable being true surprised me, but that's how it's defined natively on Chrome/Chromium/Edge/Etc., Firefox, the old Legacy Edge, and Safari).

In your own code, you can do that with HTMLCollection as well if you want, just beware that if you're using some old DOM libs like MooTools or YUI or some such, they may be confused if you add forEach to HTMLCollection.


As I said before, NodeList and HTMLCollection are both specified to be iterable (because of this Web IDL rule¹). If you run into a browser that has ES2015+ features but doesn't make the collections iterable for some reason, you can polyfill that, too:

if (typeof Symbol !== "undefined" && Symbol.iterator && typeof NodeList !== "undefined" && NodeList.prototype && !NodeList.prototype[Symbol.iterator]) {
    Object.defineProperty(NodeList.prototype, Symbol.iterator, {
        value: Array.prototype[Symbol.iterator],
        writable: true,
        configurable: true
    });
}

(And the same for HTMLCollection.)

Here's a live example using both, try this on (for instance) IE11 (although it will only demonstrate forEach), on which NodeList doesn't have these features natively:

// Using only ES5 features so this runs on IE11
function log() {
    if (typeof console !== "undefined" && console.log) {
        console.log.apply(console, arguments);
    }
}
if (typeof NodeList !== "undefined" && NodeList.prototype) {
    // forEach
    if (!NodeList.prototype.forEach) {
        // Yes, there's really no need for `Object.defineProperty` here
        console.log("Added forEach");
        NodeList.prototype.forEach = Array.prototype.forEach;
    }
    // Iterability - won't happen on IE11 because it doesn't have Symbol
    if (typeof Symbol !== "undefined" && Symbol.iterator && !NodeList.prototype[Symbol.iterator]) {
        console.log("Added Symbol.iterator");
        Object.defineProperty(NodeList.prototype, Symbol.iterator, {
            value: Array.prototype[Symbol.iterator],
            writable: true,
            configurable: true
        });
    }
}

log("Testing forEach");
document.querySelectorAll(".container div").forEach(function(div) {
    var html = div.innerHTML;
    div.innerHTML = html[0].toUpperCase() + html.substring(1).toLowerCase();
});

// Iterable
if (typeof Symbol !== "undefined" && Symbol.iterator) {
    // Using eval here to avoid causing syntax errors on IE11
    log("Testing iterability");
    eval(
        'for (const div of document.querySelectorAll(".container div")) { ' +
        '    div.style.color = "blue"; ' +
        '}'
    );
}
<div class="container">
  <div>one</div>
  <div>two</div>
  <div>three</div>
  <div>four</div>
</div>

¹ It's confusing, because HTMLCollection is iterable but it isn't marked with the iterable declaration, which bizarrely in the JavaScript DOM bindings doesn't mean that something is iterable, it means that it has forEach, entries, keys, values, and it's iterable. But HTMLCollection, which isn't marked with the iterable declaration, is still iterable. Instead, it's iterable because of this Web IDL rule as mentioned earlier.

like image 110
T.J. Crowder Avatar answered Sep 21 '22 14:09

T.J. Crowder