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,
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...?
The return value of querySelectorAll
isn't an array, it's a NodeList. That only recently got forEach
(and compatibility with JavaScript's iteration protocol, letting you use them as the targets of for-of
and spread notation).
You can polyfill forEach
easily:
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, Firefox, Edge, and Safari).
When NodeList
got forEach
, it also became iterable, meaning you could loop through the contents of a NodeList
via for-of
loops, and use a NodeList
in other places where an iterable is expected (for instance, in spread notation in an array initializer).
In practice, a browser that has features that use iterability (like for-of
loops) is also likely to already provide these features of NodeList
, but to ensure that (perhaps you're transpiling and including a polyfill for Symbol
), we'd need to do a second thing: Add a function to its Symbol.iterator
property that creates an iterator:
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.itereator],
writable: true,
configurable: true
});
}
Doing both together:
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;
if (typeof Symbol !== "undefined" && Symbol.iterator && !NodeList.prototype[Symbol.iterator]) {
Object.defineProperty(NodeList.prototype, Symbol.iterator, {
value: Array.prototype[Symbol.itereator],
writable: true,
configurable: true
});
}
}
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
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.itereator],
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>
The HTMLCollection
returned by getElementsByTagName
(and various other older APIs) isn't defined as iterable, but if you like, you can also do this for HTMLCollection
as well. Here's a loop doing boty NodeList
(if necessary) and HTMLCollection
(if necessary):
for (const ctor of [typeof NodeList !== "undefined" && NodeList, typeof HTMLCollection !== "undefined" && HTMLCollection]) {
if (ctor && ctor.prototype && !ctor.prototype.forEach) {
// (Yes, there's really no need for `Object.defineProperty` here)
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
});
}
}
}
Just beware that HTMLCollection
is live, so changes you make to the DOM which affect what's in the collection get reflected in the collection immediately, which could result in surprising behavior. (NodeList
is a disconnected collection, so that behavior doesn't happen.)