I'm trying to replace some placeholders in my HTML that look like <span id="Something" />. Using the following JavaScript code only replaced the first occurrence, and I wonder why:
function set_placeholder(cls, txt)
{
txt = document.createTextNode(txt);
for (var e of document.getElementsByClassName(cls)) {
e.parentNode.replaceChild(txt, e);
}
}
It seems the collection is unable to locate the next match after the span has been replaced.
So I tried to insert the text instead, using this variant:
function set_placeholder(cls, txt)
{
for (var e of document.getElementsByClassName(cls)) {
e.innerText = txt;
}
}
Now all occurrences are replaced, making me wonder whether it is my fault, or the browser's (Firefox 102) when the first variant fails.
The actual HTML is much more complex, but here's some sample:
<html>
<p><span class="ph-customer" /> bestellte am <span class="ph-customer-date" /> folgende Artikel:</p>
<!-- ... -->
<table>
<tr>
<td><span class="ph-customer-date" />,
<span class="ph-customer-name" /></td>
</tr>
</table>
<!-- ... -->
</html>
So for example the ph-customer-date occurs twice, but calling set_placeholder('ph-customer-date', '30.12.2023') would only replace the first occurrence.
The implementation is correct, and has been the same for 2 decades. What you are observing is that you are dealing with a live HTMLCollection and for will use the HTMLCollection.prototype[Symbol.iterator] to get the members. As others pointed correctly, replaceChild will pop the 0th item, and 1st will be 0th and 2nd will be first. Instead, cast it to something static:
Array.from(...)
Your second example works because it modifies the contents of object reference (in this case the span node) and not remove the pointer to the object from the HTMLCollection; such 'safe' methods are innerHTML, textContent, innerText. You will have the same problem with outerHTML, which essentially removes the pointer.
Keeping in mind HTMLCollections being live, you also need to watch for NodeList that can exhibit similar behavior based on live or not. Node.childNodes returns a live one, but everything else with document.querySelector and document.querySelectorAll will return static NodeList
It is generally safe to cast them to regular arrays via Array.from
OR use document.querySelectorAll(".whateverclass")
Array.from(document.getElementsByTagName("DIV")).forEach((div, i) => {
if(!i){
//method1 - DOES NOT WORK
for (const span of div.getElementsByClassName("a")) {
div.replaceChild(document.createTextNode("!"), span)
}
} else {
//method2 - WORKS
for (const span of Array.from(div.getElementsByClassName("b"))) {
div.replaceChild(document.createTextNode("!"), span)
}
}
})
<div>
<span class="a">x</span>
<span class="a">y</span>
<span class="a">z</span>
</div>
<div>
<span class="b">u</span>
<span class="b">v</span>
<span class="b">w</span>
</div>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With