I have trouble understanding why adding TEXT_NODE-s (document.createTextNode('some string')
) into an existing parent element does not update the number of elements when using a for loop.
Don’t get me wrong, it makes modifying the the parent element easier, I just don’t understand why the length of array isn’t being updated.
I though that when working with this kind of elements, they are ‘alive’ ie. updating with changes made.
Edit #1:
Why isn’t const words = topNode.textContent.split(" ");
being updated when new strings are added to the topNode
-s element. ie: at the beggining the length is 2 (0 – one, 1 – two, 2 – three), when adding ‘some string’, I would expect the words
to be length 4 (0 – one, 1 – two, 2 – some, 3 – string, 4 – three).
Edit #2:
I would like to know why the array length isn’t being updated, as I know that in some cases (I don’t know which cases…) the length of the array is being updated when changes are made. Why in some cases and not in this case?
Example:
const textarea = document.querySelector("#textarea");
const topNodes = textarea.childNodes;
for (const topNode of topNodes){
if (topNode.nodeType === 3){
let charCount = 0; //character count includes the number of letters and spaces - used for appending a word - function appendString
const words = topNode.textContent.split(" ");
for (let i = 0; i < words.length - 1; i++) {
const word = words[i];
if (typeof word !== 'undefined' && !word.length){
charCount++; //in case of double space (currently irrelevant)
continue;
}
charCount += word.length + 1; //adding + 1 as words are split by space
if (word === 'two'){
appendString(charCount, topNode, 'some string');
}
console.log("+-------------------------+");
console.log("| current word count: ", words.length - 1, " |")
console.log("| actual word count: ", topNode.textContent.split(" ").length - 1, " |")
}
}
}
function appendString(charCount, thisParentNode, string){
const nodeText = thisParentNode.textContent;
const before = nodeText.slice(0, charCount);
const after = nodeText.slice(charCount);
thisParentNode.textContent = before;
if (string.charAt(string.length - 1) !== " "){
string = string + " ";
}
const textNodeAfter = document.createTextNode(after);
const textNodeAppend = document.createTextNode(string);
thisParentNode.parentNode.insertBefore(textNodeAppend, thisParentNode.nextSibling);
thisParentNode.parentNode.insertBefore(textNodeAfter, textNodeAppend.nextSibling);
//and normalize all the way up to textarea
const textarea = document.querySelector("#textarea");
normalizeAll(thisParentNode, textarea);
function normalizeAll(thisNode, topNode){
if (thisNode !== topNode){
thisNode.normalize();
normalizeAll(thisNode.parentNode, topNode);
} else {
thisNode.normalize();
}
}
}
#body{
height: 100%
}
#textarea {
min-height: 30%;
border: 1px solid black;
}
<html>
<body>
<div id="textarea" contenteditable>one two three</div>
</body>
</html>
To further explain why this is actually an ‘issue’, take the example below. If I wish to append some strings to the element, I need a character count (so they are added at the correct location).
If we take the variable topNode
– which is a TEXT_NODE element; and we wish to add multiple strings (textNodes) to the element, I would expect that adding a string after the first time (2cnd, 3rd, 4th etc.) would be added at an incorrect place.
Example:
one two three two four two five
- add ‘something’ after string ‘two’ is found.
- use charCount to determine where to split the current TEXT_NODE, so ‘something’ can be inserted at the correct place.
loop nr. 2
charCount should be -> 7 (one two)
charCount is actually -> 7
insert ‘something’
loop nr. 4
charCount should be -> 27 (one two something three two)
charCount is actually -> 17 (one two three two)
when inserting something using charCount 17, it inserts it in the correct place (ie. one two something three two something)
if we would be using charCount 27, it would insert it at the wrong location.
Why is using charCount 17 correct?
I would expect that using 17 when inserting a string, it would be inserted at the wrong place (ie: one two somethingsomething three two)
I would expect that using 27 would place it in the correct location, but it does not.
2
Answers
They do.
is not an array but a live
NodeList
of the children of that element. When you add more nodes totextArea
, the collection will reflect that.Because it’s an array created from a string (
topNode.textContent
). Neither is the string "live" – it’s just a value -, nor does thesplit()
method know how to build a "live array". This code runs once, and unless you update the array yourself, it will keep the state that it was initialised to.Because there’s no mechanism in place to do that, and never has been. (I don’t know of any language which does this. If it’s ever implemented, it would either have to be in a new language or a new explicit syntax just for this functionality, as such as thing would fundamentally break close to all existing code.)
topNode
may indeed be a reference to an element on the page. Butwords
most certainly is not. It’s just an array of strings derived from information on that element. To get the updated data, you’d need to query that element again.To simplify the same operation, consider the following:
After these four lines of code, what value do you expect
z
to have and why?z
will equal 5, even thoughx
has been modified. This is because the value ofz
is not a live reference to the variables used to calculate it. It’s the value resulting from that calculation. Updating the value means performing the calculation again.If you want to approximate this functionality, that’s on some level what functions are for:
Now
z
doesn’t have a numeric value, its value is a function which can be invoked to produce the value you want. That function does have a "live" reference to thex
andy
variables.