I have a requirement where I have to pickup the last .div
within a container and apply some business logic to it. The selection of the last .div
has to be dynamic because the user has the option to add/remove .div
elements.
Initially I tried with querySelectorAll
but it did not seem to work. So I decided to change it to getElementsByClassName
and surprisingly it worked with the same logic. Can somebody please help me with the reason for why the remove_div
doesn't work while the second one (remove_div_2
) does?
Note: I am not looking for a fix/solution to the issue because I have already proceeded with the second option. I just want to know the reason why the option with querySelectorAll
doesn't work.
Below is my code:
HTML:
<div id='container'>
<div id='div1' class='div'>This is Div 1</div>
<div id='div2' class='div'>This is Div 2</div>
<div id='div3' class='div'>This is Div 3</div>
</div>
<button type='button' id='append_div'>Append Div</button>
<button type='button' id='remove_div'>Remove Div</button>
<button type='button' id='remove_div_2'>Remove Div 2</button>
JavaScript:
window.onload = function () {
var elementToStyle = document.querySelectorAll("#container .div");
elementToStyle[elementToStyle.length - 1].classList.add('red');
document.getElementById('append_div').onclick = function () {
var divToInsert = document.createElement('div');
divToInsert.id = 'new_div';
divToInsert.className = 'div';
divToInsert.innerHTML = 'This is an appended div';
document.getElementById('container').appendChild(divToInsert);
var elToStyle = document.querySelectorAll("#container .div");
for (i = 0; i < elToStyle.length; i++)
elToStyle[i].classList.remove('red');
elToStyle[elToStyle.length - 1].classList.add('red');
};
document.getElementById('remove_div').onclick = function () {
var elToStyle = document.querySelectorAll("#container .div");
document.getElementById('container').removeChild(elToStyle[elToStyle.length - 1]);
elToStyle[elToStyle.length - 1].classList.add('red');
};
document.getElementById('remove_div_2').onclick = function () {
var elToStyle = document.getElementsByClassName('div');
document.getElementById('container').removeChild(elToStyle[elToStyle.length - 1]);
elToStyle[elToStyle.length - 1].classList.add('red');
};
}
The reason is because querySelectorAll
method returns a static list. Any changes made to the document after the querySelectorAll
is used (like removeChild
in this case) will not be reflected in the list of nodes returned. Hence elToStyle[elToStyle.length - 1]
would still point to the node that was removed.
Whereas, getElementsByClassName
on the other hand returns a live list of nodes. This implies that elToStyle[elToStyle.length - 1]
would always point to the last .div
irrespective of any changes were done to the document after the node list was prepared or not.
Below is the extract from the official documentation available here
The NodeList object returned by the querySelectorAll() method must be static, not live ([DOM-LEVEL-3-CORE], section 1.1.1). Subsequent changes to the structure of the underlying document must not be reflected in the NodeList object. This means that the object will instead contain a list of matching Element nodes that were in the document at the time the list was created.
Note: You can see this by doing a console.log(elToStyle);
both before and after the removeChild
.