Foreach on Queryselectorall Not Working in Recent Microsoft Browsers

forEach on querySelectorAll not working in recent Microsoft browsers

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>

phantomjs: document.querySelectorAll() not working for dynamic page

Thanks for the answers Leftium and James, I've tried waitFor.js and other suggestions on Stack Overflow. But none of them worked. Now I am using Nightmare.js and it's working now, using Nighmare.js - Asynchronous operations and loops and Looping through pages when next is available #402

But knowing how to do it with phantom.js will be nice, though

getElementsByClassName().forEach() function in JavaScript not working

The browser is showing an error: document.getElementsByClassName(...).forEach is not a function

That's because getElementsByClassName doesn't return an array, it returns an HTMLCollection. They don't have a forEach method (yet; it may at some point, or not).

You can use the one that arrays have like this:

Array.prototype.forEach.call(document.getElementsByClassName("oneResult"), function(element) {
// Use `element` here
});

Or on modern browsers (or with a polyfill) you can create an array from the collection:

Array.from(document.getElementsByClassName("oneResult")).forEach(function(element) {
// Use `element` here
});

Another option is to add forEach to HTMLCollection, which you can do like this on any vaguely-modern browser (even IE8, if you polyfill Array.prototype.forEach first):

if (typeof HTMLCollection !== "undefined" && HTMLCollection.prototype && !HTMLCollection.prototype.forEach) {
Object.defineProperty(HTMLCollection.prototype, "forEach", {
value: Array.prototype.forEach,
configurable: true,
writable: true
});
}

Finally, note that while HTMLCollection doesn't have forEach, the NodeList returned by querySelectorAll does, though on some older browsers it may need to be polyfilled. See this answer about polyfilling NodeList if necessary.

More:

  • For-each over an array in JavaScript? (some answers there -- including mine -- have a section on "array-like" things, which includes HTMLCollections)
  • What do querySelectorAll, getElementsByClassName and other getElementsBy* methods return?

Why does Javascript collapsible not work in IE?

IE doesn't support arrow function and forEach for HTMLCollection.

You could add the following polyfill in your script to polyfill forEach then it can be used for both NodeList and HTMLCollection in IE:

//polyfill
var ctors = [typeof NodeList !== "undefined" && NodeList, typeof HTMLCollection !== "undefined" && HTMLCollection];
for (var n = 0; n < ctors.length; ++n) {
var ctor = ctors[n];
if (ctor && ctor.prototype && !ctor.prototype.forEach) {
// (Yes, there's really no need for `Object.defineProperty` when doing the `forEach`)
ctor.prototype.forEach = Array.prototype.forEach;
if (typeof Symbol !== "undefined" && Symbol.iterator && !ctor.prototype[Symbol.iterator]) {
Object.defineProperty(ctor.prototype, Symbol.iterator, {
value: Array.prototype[Symbol.itereator],
writable: true,
configurable: true
});
}
}
}

Besides, you should transpile the arrow functions in your code by editing the two functions in your script like below:

function showexplanation() {
document.querySelectorAll('.accordion-content').forEach(function (item) {
item.style.display = "table-row";
});
document.querySelectorAll('.arrow-right').forEach(function (item) {
item.style.transform = "rotate(45deg)";
});
}

function hideAll() {
document.querySelectorAll('.accordion-content').forEach(function (item) {
item.style.display = "none";
});
document.querySelectorAll('.arrow-right').forEach(function (item) {
item.style.transform = "rotate(-45deg)";
});
}

.forEach() on Arrays vs NodeList (JavaScript)

Look

NodeLists and Arrays are two different things because NodeLists are
actually not a JavaScript API, but a browser API.

Things like querySelectorAll() and getElementsByTagName() aren’t JavaScript methods, they’re browser APIs that let you access DOM elements. You can then manipulate them with JavaScript.

NodeLists differ from Arrays in another meaningful way, too.

They are often live lists, meaning that if elements are removed or added to the DOM, the list updates automatically. querySelector() and querySelectorAll() return a static list (one that doesn’t update), but properties like .childNodes are live lists that will change as you manipulate the DOM (which can be a good or bad thing, depending on how you’re using it).

This is all made more confusing because arrays can contain nodes. And, there’s another, older type of list called an HTMLCollection that predates NodeLists, but is functionally similar (another article for another day).

The key way to think about NodeLists vs. Arrays: NodeLists are a
language-agnostic way to access DOM elements, and Arrays are a
JavaScript object you can use to contain collections of stuff.

They each have their own methods and properties, and you can convert a NodeList into an Array if you need to (but not the other way around).

This javascript code doesn't work in Internet Explorer 11

Neither the arrow functions nor the forEach() method are compatible in IE11. You need to rewrite the above using ES5 like this:

<script>
const images = document.querySelectorAll('.view-content img');

for(var i = 0; i < images.length; i++) {
let src = images[i].getAttribute('data-original');
let newSrc = 'https://example.com' + src;
images[i].setAttribute('src', newSrc);
}
</script>

Or if you prefer using ES6 to write your JavaScript, you can use a toolchain like BabelJS to compile your JavaScript ES6 to JavaScript ES5 on production.

Why do we create a variable to reference a class instead of just using document.querySelector(.class)?

Why do we need to define the var first and then run the loop?

You don't.

You might choose to, if you're going to do more than one thing with that set of elements (because unlike getElementsByTagName, querySelectorAll doesn't reuse the collection; every call to it re-queries the DOM). But if you're doing just one thing, there's no reason why you can't do it as shown in your second example.


Side note: The NodeList from querySelectorAll only has forEach on relatively-modern browsers. But you can polyfill it trivially for older ones; this answer shows how.



Related Topics



Leave a reply



Submit