Deleting a child node stops other siblings from being deleted? - javascript

I'm trying to remove all the child nodes with class .FormErrorMessage from element #LoginFormErrorWrapper. These are XUL "description" elements if that makes a difference.
Using this code:
var Messages = document.getElementById('LoginFormErrorWrapper').getElementsByClassName("FormErrorMessage");
for (var i = 0; i < Messages.length; i++) {
var node = Messages[i];
document.getElementById('LoginFormErrorWrapper').removeChild(node);
}
Only the first element is deleted. Messages contains all of the elements, but once the first child gets deleted, the loop stops. If I comment out the removeChild line and alert the node, it alerts all of the nodes. Why does it stop looping after the first child is deleted? The parent still exists.

getElementsByClassName returns a live NodeList. So while you remove an element from the DOM it is also removed from the NodeList, causing various side effects.
You can avoid them by converting the NodeList to an Array
var Messages = [].slice.call(document.getElementById('LoginFormErrorWrapper').getElementsByClassName("FormErrorMessage"));

Related

JavaScript only affecting every other element in array [duplicate]

I attached a codepen example : http://codepen.io/anon/pen/zpmjJ
I just try to change the classname of an html collection array with a classic loop and after many test i can't figure out whats wrong and if i made a common mistake, i have always one item ignored.
<div class="test">1</div>
<div class="test">2</div>
<div class="test">3</div>
bout.addEventListener("click",function(){
var newtest = document.getElementsByClassName('test');
for (i=0; i < newtest.length; i++)
{
newtest[i].className="bob";
}
});
The problem here is that you are using document.getElementsByClassName which gives you a live NodeList. You should use document.querySelectorAll('.test') instead.
var newtest = document.querySelectorAll('.test');
for (var i=0; i < newtest.length; i++) {
newtest[i].className = "bob";
}
With live NodeList collection newtest is a reference to a dynamically updated collection of elements (indexed by className test). So after the first loop iteration when you overwrite className with bob the whole collection becomes smaller by one (because newtest[0] no longer has class test). It makes indices shift.
One more problem with your code: don't forget var keywords.
Demo: http://codepen.io/anon/pen/BAFyb
getElementsByClassName is live selector, so when you change something that not matching this selector, it will remove element from scope. So at first iteration it has 3 elements, but when you change class, it remove one element and you leave with 2 elements, so on next iteration it will take i element which is i=1 so it will take second element from top. That's why you get always 1 missing. You may use newtest[0].className="bob"; to loop through, but when you will reach last element, it will be single element, not array any more, so you need to use newtest.className="bob"; for last one

Does document.createElement keep triggering DOMContentLoaded event listener?

When I run this script, the page is continuously loading and eventually freezes. Is this because everytime I create an element, the main DOMContentLoaded listener is being called?
If so, how can I stop this recursive behaviour and just add one node to every pre existing node?
//Waits for page to load
document.addEventListener('DOMContentLoaded', function() {
//Get all elements
var items = document.getElementsByTagName("*");
//Loop through entire DOM
for (var i = 0; i < items.length; i++) {
//If it is not a text node
if (!(items[i].nodeType == 3)){
//Create a div
var newDiv = document.createElement("div");
//Add div to current object
items[i].appendChild(newDiv);
}
}
});
It's because items is referencing a "live list". This means that any updates to the DOM are going to be reflected in your list if they match the original selector.
Because you're appending a div, and your selector selects all elements, it gets added to the list, pushing any subsequent members up an index, and so the iteration continues.
To avoid this, make a non-live copy of the collection before iterating.
var items = Array.from(document.getElementsByTagName("*"));
And FYI, the if (!(items[i].nodeType == 3)){ can be removed because getElementsByTagName will never return text nodes.
If you're supporting very old versions of IE, you may want to check that the .nodeType === 1, since some of those old versions included comment nodes when using "*".
Lastly, you can use modern features to clean this up a bit.
document.addEventListener('DOMContentLoaded', () => {
for (const el of [...document.getElementsByTagName("*")]) {
var newDiv = el.appendChild(document.createElement("div"));
// Work with newDiv
}
});
You get a continuously loading page because you have this:
var items = document.getElementsByTagName("*");
Which returns a "live" node list - meaning a list that can/will change as the matched elements change. Since your selector is for everything and then you create new elements, the set of matched elements changes (it gets bigger). Which, in turn causes the length of the node list to change, which keeps your loop running.
You should not try to match all the elements or you should use another method for getting the elements that doesn't return a live node list, like querySelectoAll()

Why and when for loop ignore some item with html collection

I attached a codepen example : http://codepen.io/anon/pen/zpmjJ
I just try to change the classname of an html collection array with a classic loop and after many test i can't figure out whats wrong and if i made a common mistake, i have always one item ignored.
<div class="test">1</div>
<div class="test">2</div>
<div class="test">3</div>
bout.addEventListener("click",function(){
var newtest = document.getElementsByClassName('test');
for (i=0; i < newtest.length; i++)
{
newtest[i].className="bob";
}
});
The problem here is that you are using document.getElementsByClassName which gives you a live NodeList. You should use document.querySelectorAll('.test') instead.
var newtest = document.querySelectorAll('.test');
for (var i=0; i < newtest.length; i++) {
newtest[i].className = "bob";
}
With live NodeList collection newtest is a reference to a dynamically updated collection of elements (indexed by className test). So after the first loop iteration when you overwrite className with bob the whole collection becomes smaller by one (because newtest[0] no longer has class test). It makes indices shift.
One more problem with your code: don't forget var keywords.
Demo: http://codepen.io/anon/pen/BAFyb
getElementsByClassName is live selector, so when you change something that not matching this selector, it will remove element from scope. So at first iteration it has 3 elements, but when you change class, it remove one element and you leave with 2 elements, so on next iteration it will take i element which is i=1 so it will take second element from top. That's why you get always 1 missing. You may use newtest[0].className="bob"; to loop through, but when you will reach last element, it will be single element, not array any more, so you need to use newtest.className="bob"; for last one

Get a text from HTML using javascript

I am a beginner in JavaScript.
I'm trying to understand how can I work with DOM in js...
I would like to get a text from some website, from every DIV no matter how complex the structure is.
If I run my code below it will give me the text but:
patern div give me his text and text from child div... then child div give me his text...
So a get a lot of repeated text.
var items = document.body.getElementsByTagName('*');
for(var i=0; i<items.length; i++)
{
document.write(items[i].textContent);
}
It's important to me to know the node of current text So I can't use this because I get the text but I don't know the nodes of text:
var body = document.body, textContent = 'textContent' in body ? body.textContent : body.innerText;
document.write(textContent);
I know the resolution is jQuery., but I'm trying to understand how to do this in JS.
You can try getting all the matching elements using the jQuery .get() function.
Example:
var elements = [];
elements = $('div').get();
Once you have all the elements you can then grab the text, if any from each element and store it in another array like so:
var textStrings = [];
var len = elements.length;
for(var a = 0; a < len; a++)
textStrings[a] = elements[a].text();
The second code block will run through the first array of elements and pull the text from each one, saving it in a second array called textStrings. The 'len' variable is used so to optimize the javascript code and prevent the browser from evaluating 'elements.length' each time through the loop.
Hope this helps.
The usual textContent or innerHTML approach fails in this situation, as the .innerHTML contains the HTML of the child nodes.
But there is another property you can use: childNodes. This list does not only contain the child elements, but all child nodes, including text nodes:
var items = document.body.getElementsByTagName('*');
for(var i=0; i<items.length; i++)
{
var currItem = items[i];
for(var j = 0; j < currItem.childNodes.length; ++j)
{
if(currItem.childNodes[j].nodeName === "#text")
{
// the current child node is a text node
document.write(items[i].textContent);
}
}
}
Since every node has a unique position in the DOM this will ensure that every node is written exactly once.
References:
W3C: DOM Level 3 (Official documents)
Node.nodeName
Node.childNodes
Mozilla Developer Network (Easier to understand)
Node.nodeName
Node.childNodes

Trouble Replacing Multiple Links With iFrame Via Javascript

I'm trying to parse a page with javascript to replace links belonging to a specific class with an iframe to open a corresponding wikipedia page [so that rather than having a link you have an embedded result]. The function detects links properly but something about the replaceChild() action causes it to skip the next instance... as if it does the first replace and then thinks the next link is the one it just worked on, probably as a result of the loop.
For example, if there's 2 links on the page, the first will parse and the second will not even be seen but if there's 3, the first two will be parsed using the attributes from the first and third.
Can anyone suggest an alternative way of looping through the links that doesn't rely on a count function? Perhaps adding them to an array?
Sample Links
wiki it
Sample Javascript
(function(){
var lnks = document.getElementsByTagName("a");
for (var i = 0; i < lnks.length; i++) {
lnk = lnks[i]; if(lnk.className == "myspeciallinks"){
newif=document.createElement("iframe");
newif.setAttribute("src",'http://www.wikipedia.com');
newif.style.width="500px";
newif.style.height="100px";
newif.style.border="none";
newif.setAttribute("allowtransparency","true");
lnk.parentNode.replaceChild(newif,lnk);
}
}
})();
The problem here is that document.getElementsByTagName returns a NodeList and not an array. A NodeList is still connected to the actual DOM, you cannot safely iterate over its entries and at the same time remove the entries from the DOM (as you do when you replace the links).
You will need to convert the NodeList into an array and use the array for iteration:
(function(){
var lnksNodeList = document.getElementsByTagName("a");
// create an array from the above NodeList and use for iteration:
var lnks = Array.prototype.slice.call(lnksNodeList);
for (var i = 0; i < lnks.length; i++) {
var lnk = lnks[i];
if (lnk.className == "myspeciallinks") {
var newif = document.createElement("iframe");
newif.setAttribute("src", 'http://www.wikipedia.com');
newif.style.width = "500px";
newif.style.height = "100px";
newif.style.border = "none";
newif.setAttribute("allowtransparency", "true");
lnk.parentNode.replaceChild(newif, lnk);
}
}
})();
According to the MDN documentation:
Returns a list of elements with the given tag name. The subtree underneath the specified element is searched, excluding the element itself. The returned list is live, meaning that it updates itself with the DOM tree automatically. Consequently, there is no need to call several times element.getElementsByTagName with the same element and arguments.
Therefore, the collection shrinks every time you replace an a. You could change your loop to decrement i whenever you do a replace.

Categories