1

I swear this was just working fine a few days ago...

elm = document.querySelectorAll(selector);
var frag = document.createDocumentFragment();
while (elm[0]){
    frag.appendChild(elm[0]);
}

Right, so, this should append each node from our elm node list. When the first one is appended, the second "moves" to the first position in node list, hence the next one is always elm[0]. It should stop when the elm nodeList is completely appended. However, this is giving me an infinite loop. Thoughts?

EDIT - because I've gotten the same answer several times... A nodeList is not an array, it is a live reference. When a node is "moved" (here, appended) it should be removed automatically from the node list. The answers all saying "you're appending the same element over and over" - this is what's happening, it shouldn't be. A for loop shouldn't work, because when the first node is appended, the next node takes its index.

2nd EDIT

So the question is now "why is the nodeList behaving as an array?". The node list SHOULD be updating every time a node is being appended somewhere. Most peculiar.

Solution (in case someone needs something to handle live + non-live node lists)

elm = (/*however you're getting a node list*/);
var frag = document.createDocumentFragment();
var elength = elm.length;
for (var b = 0; b<elength; b++){
    if (elm.length === elength){
        frag.appendChild(elm[b]);
    } else {
        frag.appendChild(elm[0].cloneNode());
    }
}

Basically, just checking to see if the node list has changed length.

Gagravarr
  • 47,320
  • 10
  • 111
  • 156
Randy Hall
  • 7,716
  • 16
  • 73
  • 151
  • You do not, at any point, modify the `elm` node list, which means that the while loop will continue to append the first element in the list. – zzzzBov Nov 12 '12 at 18:04
  • I think the primary problem here is that JavaScript ain't C. The array is not modified in-place, rather copied, if I recall correctly. –  Nov 12 '12 at 18:04
  • _So the question is now "why is the nodeList behaving as an array?". The node list SHOULD be updating every time a node is being appended somewhere. Most peculiar._ Who taught you that? It is not LIVE. See my edited post below with a link to the docs. – epascarello Nov 12 '12 at 18:44
  • @epascarello https://developer.mozilla.org/en-US/docs/DOM/NodeList that and every time I've used node lists in the past. – Randy Hall Nov 12 '12 at 18:44
  • Just for reference, this is the exact opposite problem, so to speak, as [infinite-loop via prepend element in DOM](/q/9709351/4642212), where a _live_ list caused an infinite loop. – Sebastian Simon Jul 02 '21 at 12:50

6 Answers6

4

From the MDN Docs

Element.querySelectorAll

Summary

Returns a non-live NodeList of all elements descended from the element on which it is invoked that match the specified group of CSS selectors.

Syntax

elementList = baseElement.querySelectorAll(selectors);

where

  • elementList is a non-live list of element objects.
  • baseElement is an element object.
  • selectors is a group of selectors to match on.

From the docs above you can see it does not automatically remove it when you append it to another element since it is non live. Run a demo to show that feature.

var selector = "div";
elm = document.querySelectorAll(selector);
var frag = document.createDocumentFragment();
console.log("before",elm.length);
frag.appendChild(elm[0]);
console.log("after",elm.length);

When the code above runs, in the console you get.

before    3
after     3

If you want to do the while loop, convert to an array and shift() the items off

var selector = "div";
var elmNodeLIst = document.querySelectorAll(selector);
var frag = document.createDocumentFragment();
var elems = Array.prototype.slice.call(elmNodeLIst );
while (elems.length) {
    frag.appendChild(elems.shift());
}
console.log(frag);
epascarello
  • 204,599
  • 20
  • 195
  • 236
  • It would appear your answer holds the most water. This is a problem lol. I sometimes have other ways of retrieving node lists, this is part of a larger system. I will have to create a workaround for live/nonlive node lists being passed into the same function to create a doc frag. Good info though. – Randy Hall Nov 12 '12 at 18:58
1

You are appending the first item in the node list, over and over and over. You never removing any items from the array, but always adding the first one to the fragment. And the first one is always the same.

elm = document.querySelectorAll(selector);
var frag = document.createDocumentFragment();
while (elm.length){
    frag.appendChild(elm.shift());
}

This may be closer to what you meant to do. We can use while (elm.length) because as items get removed form the array, eventually length will be zero which is a flasy value and the loop will stop.

And we use elm.shift() to fetch the item from the array because that method will return the item at index zero and remove it from the array, which gives us the mutation of the original array we need.


I think you thought this might work because a node can only have one parent. Meaning adding somewhere removes it from the previous parent. However, elm is not a DOM fragment. It's just a aray (or perhaps a NodeList) that holds references to element. The array is not the parent node of these elements, it just holds references.

Your loop might work if you had it like this, since you are query the parent node each time for its children, a list of node that will actually change as you move around:

elm = document.getElementById(id);
var frag = document.createDocumentFragment();
while (elm.children[0]){
    frag.appendChild(elm.children[0]);
}
Alex Wayne
  • 178,991
  • 47
  • 309
  • 337
  • That's the problem though.... if elm is a `nodeList`, a selection of `nodes` (here, `elements`), when they are appended to the `documentFragment`, they are removed from the dom, and should no longer exist in the `nodeList`. – Randy Hall Nov 12 '12 at 18:20
0

I wouldn't have expected it to work in the first place.

Your elm array is initialized, and never updated. Even if the result from running document.querySelectorAll(selector); would return something different, this doesn't change your current references in the array.

You would either need to rerun the selector, or manually remove the first element in the array after appending it.

Sean Adkinson
  • 8,425
  • 3
  • 45
  • 64
  • A node list is not a normal array. A node list is supposed to "live-update" with the changes made to the nodes. So when a node in the list is appended, the current node list should be re-written. Perhaps the reference to the original node is persevering though... interesting thought. I shall look into it more. – Randy Hall Nov 12 '12 at 18:07
0

It's an infinite loop as it's written right now because elm[0] always refers to the same element, and that element is never null (any non-null/non-zero result would be true). You also don't do anything with the elements themselves to make it iterate across the list. You should be using a for loop instead of a while or at least having some kind of indexer to try to traverse the collection.

elm = document.querySelectorAll(selector);
var frag = document.createDocumentFragment();
for (i= 0; i < elm.length; i++)
{
    frag.appendChild(elm[i]);
}

Edit:

From the documentation:

A "live" collection

In most cases, the NodeList is a live collection. This means that changes on the DOM tree >are going to be reflected on the collection.

var links = document.getElementsByTagName('a'); // links.length === 2 for instance

document.body.appendChild( links[0].cloneNode(true) ); // another link is added to the document
// the 'links' NodeList is automatically updated
// links.length === 3 now. If the NodeList is the return value of document.querySelectorAll, it is NOT live.

Going on this documentation, your current usage of the method indicates you do not have a live NodeList. Thus appending will never modify the original list. You will either need to modify your usage within the loop to mirror this usage of .cloneNode(true) or iterate manually.

Joel Etherton
  • 37,325
  • 10
  • 89
  • 104
  • @RandyHall: Yah, I got called away while I was posting an edit to my question that shows from the documentation why I put the answer I did and possibly a different method of achieving the result you're looking for. – Joel Etherton Nov 12 '12 at 18:29
0

elm[0] is static and unchanging in above code fix is as below

elm = document.querySelectorAll(".container");
var frag = document.createDocumentFragment();
console.log(elm);
var i=0;
while (elm[i]){
    frag.appendChild(elm[i++]);
}
Hemen Ashodia
  • 202
  • 2
  • 4
  • 21
0

I didn't actually focus much on the code (and if it made sense -judging from the comments- or not); but if this worked a few days ago then the problem is in the input you are giving to your code selector.

That's when Unit Testing comes in handy. If you can remember the input with which the code worked, then you can make it work again and start debugging from there.

Otherwise, you are just lying to yourself.

Omar Abid
  • 15,753
  • 28
  • 77
  • 108