Is it possible to update/refresh NodeList? - javascript

I have a "Maindiv" (div) with, say, 4 elements in it. Lets consider the elements belong to a class called "Subdiv". When I query the number of "Subdivs" with "Maindiv.getElementsByClassName('Subdiv').length;" , it returns 4, as expected. But if I create a new "Subdiv" and append it to my main "Maindiv" and instantly query for the length, it will return 4 (which is wrong), and until the NodeList is updated (usually 20-50 milliseconds after appending the new element) it returns 4. Finally after this interval it returns the right number (5). My question is, if there's a way to update/refresh the NodeList faster just after I append the new element?
<div>
<div id='Maindiv'>
<div class='Subdiv' id='Subdiv1'></div>
<div class='Subdiv' id='Subdiv2'></div>
<div class='Subdiv' id='Subdiv3'></div>
<div class='Subdiv' id='Subdiv4'></div>
</div>
<button type='button' onclick='CreateNewSubdivs()'>Create Subdiv</button>
</div>
<script>
function CreateNewSubdivs(){
var MainDiv = document.getElementById('Maindiv');
var SubdivsLength= MainDiv.getElementsByClassName('Subdiv').length;
var NewSubDiv = document.createElement('div');
var NewCopyNumber = SubdivsLength+1;
var NewSubDivID = 'Subdiv'+NewCopyNumber;
NewSubDiv.setAttribute('class', 'Subdiv');
NewSubDiv.setAttribute('id', NewSubDivID );
MainDiv.appendChild(NewSubDiv );
var SubdivsLength= MainDiv .getElementsByClassName('Subdiv').length;
console.log(SubdivsLength); /// This number is wrong until 20-50 millisec later
}
</script>

A NodeList can either be a static or "live" collection of nodes.
In some cases, the NodeList is live, which means that changes in the DOM automatically update the collection.
e.g. Node.childNodes
In other cases, the NodeList is static, where any changes in the DOM does not affect the content of the collection.
e.g. a list returned by querySelectorAll()
Source: https://developer.mozilla.org/en-US/docs/Web/API/NodeList
As you can see in this example, I set a reference to childNodes once. It is kept up to date as soon as the DOM changes.
const list = document.querySelector('#main');
const items = list.childNodes;
setInterval(() => {
const item = document.createElement('li');
item.innerHTML = '🌯';
list.appendChild(item);
console.log(items.length);
}, 500);
<ul id="main"></ul>
getElementsByClassName also returns a live collection of nodes:
const list = document.querySelector('#main');
const spans = list.getElementsByClassName('burrito');
setInterval(() => {
const item = document.createElement('li');
item.innerHTML = '<li><span class="burrito">🌯</span></li>';
list.appendChild(item);
console.log(spans.length);
}, 1);
<ul id="main">
</ul>

Related

How to modify multiple element nodes and text nodes in javascript and which of code is more clear to use?

I have question about this two similar codes , and want to know advantages and disadvantages of using it.or there is no matter which one use?In first version I am using parameters and in second not...
Also interested in all_h1.querySelectorAll(`h1`)
Why is not same as const children =parent.childNodes;
```HTML
<div id="parent">
<h1>We Will Manipulate LIVE nodeList Element nodes and #textNodes</h1>
<h1>We Will Manipulate LIVE nodeList Element nodes and #textNodes</h1>
<h1>We Will Manipulate LIVE nodeList Element nodes and #textNodes</h1>
<h1>We Will Manipulate LIVE nodeList Element nodes and #textNodes</h1>
<h1>We Will Manipulate LIVE nodeList Element nodes and #textNodes</h1>
<h1>We Will Manipulate LIVE nodeList Element nodes and #textNodes</h1>
</div>
```
.addStyle {
width: 1050px;
border-radius: 50px;
padding: 0.5em 0.5em;
}
first version of the code with using Parameters:
const parent = document.querySelector(`#parent`);
//const children = parent.childNodes;
const function_CreateH1 = (parent_container, text_parameter) => {
parent_container.insertAdjacentHTML(`afterbegin`, `<h1>${text_parameter}</h1>`);
};
function_CreateH1(parent, `Add New H1 element Wait (1,2,3...and Go)`);
const replaceAllH1Text = (all_h1, newtext_parameter) => {
all_h1.querySelectorAll(`h1`).forEach((elem) => {
elem.textContent = newtext_parameter;
elem.style.backgroundColor = `brown`;
elem.style.color = `white`;
elem.classList.add(`addStyle`);
//Why isn't working children.forEach and etc.
});
};
setTimeout(() => {
replaceAllH1Text(parent, `We access and Modify H1 elements itselfs and #Text nodes`);
}, 3000);
Second version of the code without using Parameters in function:
const parent = document.querySelector(`#parent`);
//const children = parent.childNodes;
const function_CreateH1 = () => {
parent.insertAdjacentHTML(`afterbegin`, `<h1>Add New H1 element Wait (1,2,3...and Go)</h1>`);
};
function_CreateH1();
const replaceAllH1Text = () => {
parent.querySelectorAll(`h1`).forEach((elem) => {
elem.textContent = `We access and Modify H1 elements itselfs and #Text nodes`;
elem.style.backgroundColor = `brown`;
elem.style.color = `white`;
elem.classList.add(`addStyle`);
//Why isn't working children.forEach and etc.
});
};
setTimeout(() => {
replaceAllH1Text();
}, 3000);
You can simplify the code looping document.querySelectorAll.
For the creation of a new li-element the snippet contains a separate function appendLi. It uses insertAdjacentHTML and a template string to append the new li.
The function replaceAllLiText contains the loop to change the text for all li's in the ul.
const appendLi = (ul, text) =>
ul.insertAdjacentHTML(`beforeend`, `<li>${text}</li>`);
// │ ^┍ the html to append,
// │ │ using a template string
// │ ┕ to inject [text]
// ┕ add the html last within [ul]
const replaceAllLiText = (ul, nwText) =>
ul.querySelectorAll(`li`).forEach(li => li.textContent = nwText);
// │ ┕ set [li] text to [nwText]
// ┕ loop all li's within [ul]
const ul = document.querySelector(`ul`);
// append a new li-element with text to the ul
appendLi(ul, `this is new Li (wait a sec...)`);
// wait two seconds, then change the text of all li's
setTimeout(() => replaceAllLiText(ul, `This is working`), 2000);
<ul>
<li>foo</li>
<li>bar</li>
<li>bar</li>
</ul>`

How to loop through htmlcollection object?

I have looked at almost every question that has been asked here about htmlcollection.
So I have a div and I am fetching data and creating divs inside this div with ajax so they are not hardcoded.
this is how div look like before I fetch the data
<div id="tivvits"></div>
this is how div#tivvits looks like after I call function show_all_tivvits();
show_all_tivvits() is a function where I create a ajax request and create new divs
such as div#tivvit-21, div#tivvit-22, etc.
<div id="tivvits">
<div id="tivvit-19" class="grid-container">...</div>
<div id="tivvit-20" class="grid-container">...</div>
</div>
this is part of the js file
document.addEventListener("DOMContentLoaded", function(){
show_all_tivvits();
var t = document.getElementById('tivvits');
const j = t.getElementsByClassName("grid-container");
const k = Array.prototype.slice.call(j)
console.log(k);
for (var i = 0; i < k.length; i++) {
console.log(k[i]);
}
});
what I wanted to do in show_all_tivvits() function is I want to get the divs that are already in the div#tivvits and that way I am not gonna create them again but the problem is when I use console.log() to print out document.getElementById('tivvits').getElementsByClassName('grid-container') there are items in the htmlcollection but when I print out length it returns 0.
one more thing when I open inspect>source in chrome my index.php doesn't have updated div#tivvits.
I have tried almost every way to loop this htmlcollection but it is not working.
list of things I have tried;
Array.from(links)
Array.prototype.slice.call(links)
[].forEach.call(links, function (el) {...});
HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
HTMLCollection.prototype.forEach = Array.prototype.forEach;
It's not really clear, but are you looking for something like this?
targets = document.querySelectorAll('#tivvits > .grid-container')
for (let target of targets)
{console.log(target.id)}
This should select all <div> nodes which are direct children of the <div id="tivvits"> node and have a class attribute with the value "grid-container", and extract from them the attribute value of the id attribute.
Have a go with this
I use the spread operator to allow the use of map on the HTMLCollection
window.addEventListener("load", function() {
const gridContainers = document.querySelectorAll("#tivvits .grid-container");
const ids = [...gridContainers].map(div => div.id);
console.log(ids)
});
<div id="tivvits">
<div id="tivvit-19" class="grid-container">...</div>
<div id="tivvit-20" class="grid-container">...</div>
</div>
To just display change
const ids = [...gridContainers].map(div => div.id);
to
[...gridContainers].forEach(div => console.log(div.id));

Trouble removing an element from an array

I started self teaching myself JavaScript a couple of weeks ago and have run into a problem I am unable to solve. The program works by allowing a user to enter a list of names which is saved in an array and also shown on the screen as li elements. The program will then randomly select one of the people entered. My problem occurs when trying to remove a person from the list. I am able to remove them from the HTML but not from the array. I have attempted to use the .splice method as shown below but this only removes the last element in the array. I believe this to be due to indexOf(li.value) not being suitable for this use but do not know what else to try. Any help is much appreciated.
<!DOCTYPE html>
<html>
<head>
<title>Student Randomiser</title>
<link rel="stylesheet" href="custom.css">
</head>
<body>
<h1 id="myHeading">Student Randomiser</h1>
<div class="list">
<p class="description">Add Students:</p>
<input type="text" class="studentName" value="Write Students Here">
<button class="addStudent">Add student</button>
<ul id= "listStudentNames">
</ul>
<button class="randomStudent">Select a random student</button>
</div>
<script src="randomNamePicker.js"></script>
</body>
</html>
const addStudent = document.querySelector('button.addStudent');
const studentName = document.querySelector('input.studentName');
const randomStudent = document.querySelector('button.randomStudent');
const listStudentNames = document.querySelector("ul");
let students = [
]
let number
window.onload=function(){
addStudent.addEventListener('click', addStudentToList);
randomStudent.addEventListener('click', selectRandomStudent);
listStudentNames.addEventListener("click", removeStudent);
}
function addButtons(li){
let remove =document.createElement('button');
remove.className= "removeStudent";
remove.textContent = "Remove Student";
li.appendChild(remove)
}
function removeStudent (){
if (event.target.tagName === "BUTTON") {
let li = event.target.parentNode;
let ul = li.parentNode;
let i = students.indexOf(li.value);
students.splice(i,1);
ul.removeChild(li);
}}
function getRandomIntInclusive(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
number = Math.floor(Math.random() * (max - min)) + min;
}
function addStudentToList() {
students.push(studentName.value);
var ul = document.getElementById("listStudentNames");
var li = document.createElement("li");
li.appendChild(document.createTextNode(studentName.value));
addButtons(li);
ul.appendChild(li);
studentName.value = "";
}
function selectRandomStudent(){
getRandomIntInclusive(0, students.length);
alert(students[number]);
}
There are a variety of issues with your code, some are programmatic, some are stylistic and some are your logic.
Your main issue is that the only elements that have a value property are form elements. So, when you write:
let i = students.indexOf(li.value);
You have an issue because you set li up to be the parent node of event.target. event.target is the element that initiated the event, in this case a <button> and the parent of the button is <div>, which (again) doesn't have a value property and is not the correct element anyway.
This value is what you are basing your splice on. Instead, you need to get the index position of the li within the list of li elements or the array (they should be the same).
Next, you don't really have a need for an array in this scenario in the first place since all of the student names will be elements within a <ul> element, this means that they can be accessed via a "node list", which is an "array-like" object that supports a length property, is enumerable and indexable. Keeping the <li> element content in a synchronized array doesn't add any value here and makes the overall task more complex that it need be because you are forced to keep the HTML list in sync with the array.
Having said all of this, here's a working example of what you are attempting, with comments inline to explain why my code differs from yours.
// When the DOM is loaded and all elements are accessible...
window.addEventListener("DOMContentLoaded", function(){
// Get references to the DOM elements needed to solve problem
// Using "var" here is perfectly acceptable as their scope will
// be the entire parent function, which is what we want. Get all
// these references just once so we don't have to keep scanning
// the DOM for them each time we want to work with the list. Also,
// name your variables "noun"-like names when they refer to elements
var btnAdd = document.querySelector(".addStudent");
var btnRandom = document.querySelector(".randomStudent");
var list = document.getElementById("listStudentNames");
var student = document.querySelector("input[type='text']");
var output = document.querySelector(".report");
// Set up an empty array to keep the synchronized student list in
var students = [];
// Set up click event handling functions
btnAdd.addEventListener("click", addStudent);
btnRandom.addEventListener("click", getRandomStudent);
function addStudent(){
// Make sure there was valid input
if(student.value.trim() === ""){ return; }
// Create a new <li> element
var li = document.createElement("li");
// Set new element up with a click event handler that will
// cause the current element to be removed from the list
li.addEventListener("click", removeStudent);
// Populate the element with the text from the <input>
// The element gets raw text set with the .textContent property
// while content of form elements is gotten with the "value"
// property
li.textContent = student.value;
// Update the list id's to match the array indexes
sync();
// Add the element to the end of the <ul>'s list elements
list.appendChild(li);
// Add new student to the array:
students.push(student.value);
// Clear value from input
student.value = "";
logResults();
}
function getRandomStudent(){
console.clear();
if(students.length){
// Use the built-in JavaScript Math object to get a random number
// between 0 (inclusive) and 1 (exclusive) then multiply that
// number by the lenght of the <li> node list to get a random
// number between 0 and the amount of elements in the array
var random = Math.floor(Math.random() * list.children.length);
console.log("Random student is: " + list.children[random].textContent);
} else {
console.log("No students to choose from!");
}
}
function removeStudent (evt){
// Re-sync the indexes of the HTML elements to match the array
sync();
// Remove corresponding student from array first...
console.clear();
console.log("Student " + evt.target.id + " about to be removed");
students.splice(+evt.target.id, 1);
// Every event handling function automatically gets a reference
// to the event that triggered the function sent into it. We can
// access that event to get a reference to the actual DOM object
// the caused the event to be triggered with "event.target"
list.removeChild(evt.target);
logResults();
}
// We have to keep the HTML element indexes in sync with the array indexes
function sync(){
// Loop through the HTML elements and give them an id that corresponds
// to the index position of its counterpart in the array.
Array.prototype.forEach.call(list.children, function(el, index){
el.id = index;
});
}
// This is just a function for updating the display to show the contents
// of the array to confirm that it is in sync with the list
function logResults(){
output.innerHTML = "Array now contains: <br>" + students.join("<br>");
}
});
.list, .report { float:left; }
.report { background-color:aliceblue; }
<div class="list">
<input type="text" class="studentName" placeholder="Enter Student Name Here">
<button class="addStudent">Add student to list</button>
<ul id= "listStudentNames">
</ul>
<p class="description">(Click on a student to remove them from the list.)</p>
<button class="randomStudent">Select a random student</button>
</div>
<div class="report"></div>

Append multiple items in JavaScript

I have the following function and I am trying to figure out a better way to append multiple items using appendChild().
When the user clicks on Add, each item should look like this:
<li>
<input type="checkbox">
<label>Content typed by the user</label>
<input type="text">
<button class="edit">Edit</button>
<button class="delete">Delete</button>
</li>
and I have this function to add these elements:
function addNewItem(listElement, itemInput) {
var listItem = document.createElement("li");
var listItemCheckbox = document.createElement("input");
var listItemLabel = document.createElement("label");
var editableInput = document.createElement("input");
var editButton = document.createElement("button");
var deleteButton = document.createElement("button");
// define types
listItemCheckbox.type = "checkbox";
editableInput.type = "text";
// define content and class for buttons
editButton.innerText = "Edit";
editButton.className = "edit";
deleteButton.innerText = "Delete";
deleteButton.className = "delete";
listItemLabel.innerText = itemText.value;
// appendChild() - append these items to the li
listElement.appendChild(listItem);
listItem.appendChild(listItemCheckbox);
listItem.appendChild(listItemLabel);
listItem.appendChild(editButton);
listItem.appendChild(deleteButton);
if (itemText.value.length > 0) {
itemText.value = "";
inputFocus(itemText);
}
}
But you can notice that I am repeating three times the appendChild() for listItem. Is it possible to add multiple items to the appendChild() ?
You can do it with DocumentFragment.
var documentFragment = document.createDocumentFragment();
documentFragment.appendChild(listItem);
listItem.appendChild(listItemCheckbox);
listItem.appendChild(listItemLabel);
listItem.appendChild(editButton);
listItem.appendChild(deleteButton);
listElement.appendChild(documentFragment);
DocumentFragments allow developers to place child elements onto an
arbitrary node-like parent, allowing for node-like interactions
without a true root node. Doing so allows developers to produce
structure without doing so within the visible DOM
You can use the append method in JavaScript.
This is similar to jQuery's append method but it doesnot support IE and Edge.
You can change this code
listElement.appendChild(listItem);
listItem.appendChild(listItemCheckbox);
listItem.appendChild(listItemLabel);
listItem.appendChild(editButton);
listItem.appendChild(deleteButton);
to
listElement.append(listItem,listItemCheckbox,listItemLabel,editButton,deleteButton);
Personally, I don't see why you would do this.
But if you really need to replace all the appendChild() with one statement, you can assign the outerHTML of the created elements to the innerHTML of the li element.
You just need to replace the following:
listElement.appendChild(listItem);
listItem.appendChild(listItemCheckbox);
listItem.appendChild(listItemLabel);
listItem.appendChild(editButton);
listItem.appendChild(deleteButton);
With the following:
listItem.innerHTML+= listItemCheckbox.outerHTML + listItemLabel.outerHTML + editButton.outerHTML + deleteButton.outerHTML;
listElement.appendChild(listItem);
Explanation:
The outerHTML attribute of the element DOM interface gets the serialized HTML fragment describing the element including its descendants. So assigning the outerHTML of the created elements to the innerHTML of the li element is similar to appending them to it.
Merging the answers by #Atrahasis and #Slavik:
if (Node.prototype.appendChildren === undefined) {
Node.prototype.appendChildren = function() {
let children = [...arguments];
if (
children.length == 1 &&
Object.prototype.toString.call(children[0]) === "[object Array]"
) {
children = children[0];
}
const documentFragment = document.createDocumentFragment();
children.forEach(c => documentFragment.appendChild(c));
this.appendChild(documentFragment);
};
}
This accepts children as multiple arguments, or as a single array argument:
foo.appendChildren(bar1, bar2, bar3);
bar.appendChildren([bar1, bar2, bar3]);
Update – June 2020
Most all current browsers support append and the "spread operator" now.
The calls above can be re-written as:
foo.append(bar1, bar2, bar3);
bar.append(...[bar1, bar2, bar3]);
Let's try this:
let parentNode = document.createElement('div');
parentNode.append(...[
document.createElement('div'),
document.createElement('div'),
document.createElement('div'),
document.createElement('div'),
document.createElement('div')
]);
console.log(parentNode);
You need to append several children ? Just make it plural with appendChildren !
First things first :
HTMLLIElement.prototype.appendChildren = function () {
for ( var i = 0 ; i < arguments.length ; i++ )
this.appendChild( arguments[ i ] );
};
Then for any list element :
listElement.appendChildren( a, b, c, ... );
//check :
listElement.childNodes;//a, b, c, ...
Works with every element that has the appendChild method of course ! Like HTMLDivElement.
You can use createContextualFragment, it return a documentFragment created from a string.
It is perfect if you have to build and append more than one Nodes to an existing Element all together, because you can add it all without the cons of innerHTML
https://developer.mozilla.org/en-US/docs/Web/API/Range/createContextualFragment
// ...
var listItem = document.createElement("li");
var documentFragment = document.createRange().createContextualFragment(`
<input type="checkbox">
<label>Content typed by the user</label>
<input type="text">
<button class="edit">Edit</button>
<button class="delete">Delete</button>
`)
listItem.appendChild(documentFragment)
// ...
You could just group the elements into a single innerHTML group like this:
let node = document.createElement('li');
node.innerHTML = '<input type="checkbox"><label>Content typed by the user</label> <input type="text"><button class="edit">Edit</button><button class="delete">Delete</button>';
document.getElementById('orderedList').appendChild(node);
then appendChild() is only used once.
It's possible to write your own function if you use the built in arguments object
function appendMultipleNodes(){
var args = [].slice.call(arguments);
for (var x = 1; x < args.length; x++){
args[0].appendChild(args[x])
}
return args[0]
}
Then you would call the function as such:
appendMultipleNodes(parent, nodeOne, nodeTwo, nodeThree)
Why isn't anybody mentioning the element.append() function ?!
you can simply use it to append multiple items respectively as so:
listItem.append(listItemCheckbox, listItemLabel, editButton, deleteButton);
This is a quick fix
document.querySelector("#parentid .parenClass").insertAdjacentHTML('afterend', yourChildElement.outerHTML);
Guys I really recommend you to use this one.
[listItemCheckbox, listItemLabel, editButton, deleteButton]
.forEach((item) => listItem.appendChild(item));
Since you can't append multiple children at once. I think this one looks better.
Also here's a helper function that uses the fragment technique as introduced in the #Slavik's answer and merges it with DOMParser API:
function createHtmlFromString(stringHtml) {
const parser = new DOMParser();
const htmlFragment = document.createDocumentFragment();
const children = parser.parseFromString(stringHtml, "text/html").body
.children;
htmlFragment.replaceChildren(...children);
return htmlFragment;
}
Now to append multiple children with this, you can make the code much more readable and brief, e.g.:
const htmlFragment = createHtmlFromString(`<div class="info">
<span></span>
<h2></h2>
<p></p>
<button></button>
</div>
<div class="cover">
<img />
</div>
`);
Here's also a working example of these used in action: example link.
Note1: You could add text content in the above tags too and it works, but if it's data from user (or fetched from API), you'd better not trust it for better security. Instead, first make the fragment using the above function and then do something like this:
htmlFragment.querySelector(".info > span").textContent = game.name;
Note2: Don't use innerHTML to insert HTML, it is unsecure.
Great way to dynamically add elements to a webpage. This function takes 3 arguments, 1 is optional. The wrapper will wrap the parent element and it's elements inside another element. Useful when creating tables dynamically.
function append(parent, child, wrapper="") {
if (typeof child == 'object' && child.length > 1) {
child.forEach(c => {
parent.appendChild(c);
});
} else {
parent.appendChild(child);
}
if (typeof wrapper == 'object') {
wrapper.appendChild(parent);
}
}
I would like to add that if you want to add some variability to your html, you can also add variables like this:
let node = document.createElement('div');
node.classList.add("some-class");
node.innerHTML = `<div class="list">
<div class="title">${myObject.title}</div>
<div class="subtitle">${myObject.subtitle}
</div>`;

querySelectorAll not working

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.
If you want to reference the last division element just do the following...
var id = 'container';
var d = document.getElementById(id).getElementsByTagName('div')
var lastDiv = d[d.length - 1];
..and then apply your querySelector.

Categories