removeChild skips on odd nodes, why?

V

VK

I must be missing something very obvious, but my nightly head doesn't
work anymore.

Press "Insert" button to add <ins> nodes after each <br>.
Now press "Delete" - only even <ins> are being removed.
ins.length is reported properly, each <ins> has "insert" class name.
What a...?

<html
<head
<title>Demo</title
<meta http-equiv="Content-Type"
content="text/html; charset=iso-8859-1"
..insert {
background-color: #FFFF00;
color: #FF0000;
text-decoration: none;
padding-left: 2px;
padding-right: 2px;
}
</style

function ins() {
var br = document.body.getElementsByTagName('br');
var ms = document.createElement('ins');
ms.appendChild(document.createTextNode('Message'));
ms.className = 'insert';
for (var i=0; i<br.length; i++) {
br.parentNode.insertBefore(ms.cloneNode(true),
br.nextSibling);
}
}


function del() {
var ins = document.body.getElementsByTagName('ins');
for (var i=0; i<ins.length; i++) {
if (ins.className == 'insert') {
alert( ins.parentNode.removeChild(ins) );
}
}
}
</script
</head

<p>Demo <br> text</p
<p>Demo <br> text</p
<p>Demo <br> text</p
<p>Demo <br> text</p
 
E

Elegie

VK wrote:

Hi VK,
Press "Insert" button to add <ins> nodes after each <br>.
Now press "Delete" - only even <ins> are being removed.

The DOM tree is live, when removing some elements from it you have to
make sure that your iterator is updated as well.
alert( ins.parentNode.removeChild(ins) );


alert( ins.parentNode.removeChild(ins[i--]) );


Regards,
Elegie.
 
V

VK

alert( ins.parentNode.removeChild(ins) );
alert( ins.parentNode.removeChild(ins[i--]) );


Thanks for the hint, this brought the code to life:

<html
<head
<title>Demo</title
<meta http-equiv="Content-Type"
content="text/html; charset=iso-8859-1"
..insert {
background-color: #FFFF00;
color: #FF0000;
text-decoration: none;
padding-left: 2px;
padding-right: 2px;
}
</style

function ins() {
var br = document.body.getElementsByTagName('br');
var ms = document.createElement('ins');
ms.appendChild(document.createTextNode('Message'));
ms.className = 'insert';
for (var i=0; i<br.length; i++) {
br.parentNode.insertBefore(ms.cloneNode(true),
br.nextSibling);
}
}


function del() {
var ins = document.body.getElementsByTagName('ins');
for (var i=ins.length-1; i>=0; i--) {
if (ins.className == 'insert') {
ins.parentNode.removeChild(ins);
}
}
}
</script
</head

<p>Demo <br> text</p
<p>Demo <br> text</p
<p>Demo <br> text</p
<p>Demo <br> text</p
<p><button type="button" onclick="ins()">Insert</button>
</body
</html>

I'm still missing I'm affraid how switching iteration direction would
solve the problem - but it obviously did. Maybe I'll get some enlight
after a good sleep... :)
 
E

Elegie

VK said:
I'm still missing I'm affraid how switching iteration direction would
solve the problem - but it obviously did. Maybe I'll get some enlight
after a good sleep... :)

Rest, sport and good meals are three of the key success factors that,
IMHO, help a person get through intense stress situations that tend to
last for many days. Cheers up, things will clear up :)

Consider what removeChild does: it removes one of the object you're
iterating on. Your index, which refers to this object, now points to the
next object. Then, your "for loop" continues and increases the index by
one *while it is already pointing to the next node*, resulting in
skipping the node it had been referring to after the removal.

Ex.:

Given the following nodes: [A] [C] [D]
Given an ii index, with ii==1, i.e. points to
Removing gives: [A] [C] [D]
Your index still equal to 1, ii==1, means it now points to [C]
The "for" statement increases ii to ii==2, i.e. points to [D]
[C] has been skipped in the process.


HTH,
Elegie.

PS : BTW VK your code is not displayed correctly when you post, ">"
marks appear at the beginning of lines, making my newsreader think
you're quoting stuff.
 
R

RobG

function del() {
var ins = document.body.getElementsByTagName('ins');
for (var i=ins.length-1; i>=0; i--) {
if (ins.className == 'insert') {
ins.parentNode.removeChild(ins);
}
}


You might find it easier to iterate backwards using a while loop:

var n, i = ins.length;
while (n = ins[--i]) {
if (n.className == 'insert') {
n.parentNode.removeChild(n);
}
}


If you don't like assignment inside the while test:

while ( i-- ) {
n = ins;
if ...
}

There are many ways to skin a cat...

[...]
I'm still missing I'm affraid how switching iteration direction would
solve the problem - but it obviously did. Maybe I'll get some enlight
after a good sleep... :)

The NodeList returned by getElementsByTagName is live. When you
remove ins then the node that was ins[i+1] is now ins.
Incrementing the counter on then next loop skips that node to the one
that was ins[i+2].

When you work backwards (i.e. from highest to lowest index), inserting
or removing items at or beyond the current index list doesn't affect
iteration over all the original nodes.
 
V

VK

You might find it easier to iterate backwards using a while loop:

var n, i = ins.length;
while (n = ins[--i]) {
if (n.className == 'insert') {
n.parentNode.removeChild(n);
}
}
Agreed.

The NodeList returned by getElementsByTagName is live. When you
remove ins then the node that was ins[i+1] is now ins.
Incrementing the counter on then next loop skips that node to the one
that was ins[i+2].

When you work backwards (i.e. from highest to lowest index), inserting
or removing items at or beyond the current index list doesn't affect
iteration over all the original nodes.


Thanks to everyone, now I am clear on it.

getElementsByName / getelementsByTagName returns NodeList object (also
called HTMLCollection in older docs). In my coding I wrongly assumed
that NodeList is a kind of stripped down Array - without Array methods
and ability to store proprietary data, only with ability to iterate
using positive integer index.

In fact NodeList is Vector data type, so it automatically "shrinks" on
element removal with corresponding index renumbering of remaining
elements.

This way the safe way to remove elements is by going from the top to
bottom: in this case there is no index shift of remaining elements.

What wonders me now: one cannot directly remove elements from
NodeList, and a statement like
elm.parentNode.removeChild(elm)
changes the current NodeList in an indirect way (as a reflection of
DOM Tree change).

Does it mean that NodeList collection re-retrieved over and over
before any involving operation?
 
V

VK

Rest, sport and good meals are three of the key success factors that,
IMHO, help a person get through intense stress situations that tend to
last for many days. Cheers up, things will clear up :)

Thanks :)
PS : BTW VK your code is not displayed correctly when you post, ">"
marks appear at the beginning of lines, making my newsreader think
you're quoting stuff.

Yeah. Normally I shape up my code before "going on public" (Usenet
posting): type="", conventional pretty-print and stuff. Last night I
was too tired for that I guess. This is anti-phantom pretty-print my
authoring software makes. Gives about 10% time gain on client-side
parsing stage for Gecko engines - because it doesn't have to allocate
bogus text nodes for line breaks - and up to 40% for intensive tree
traversal scripting - because of using native DOM methods without
extra checks for phantom nodes. Once I compared the productivity
impact - since then I'm using just that.
But I promise do not confuse in the future news agents and news
readers :)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,432
Messages
2,571,682
Members
48,796
Latest member
Greg L.

Latest Threads

Top