Search code examples
javascripthtmldocumentfragment

DocumentFragment is losing part of my content


So I'm basicaly just trying move element from one node to an other. I create a fragment and then append to it my children elements.

const fragment = document.createDocumentFragment();
let sortedElements = [...document.querySelectorAll('.product')].sort((a,b) => b.dataset.blockSize - a.dataset.blockSize ); //Select elements
sortedElements.forEach((e) => {
    console.log(e) //My 4 children displayed
    fragment.appendChild(e)
});
console.log(fragment.children); //Only Two children in it

fragment.childNodes.forEach((el) => {
    document.querySelector("#appendThere").appendChild(el);
})
<div class="product" data-object-size="4">Product 1</div>
<div class="product" data-object-size="2">Product 2</div>
<div class="product" data-object-size="1">Product 3</div>
<div class="product" data-object-size="1">Product 4</div>

<div id="appendThere"></div>

Am I missunderstanding how does fragments works ?

It's strangely working on snippet... Even partialy working on my computer But its getting stranger.

enter image description here

enter image description here

I think there is a change between the moment I print my variable and I explore it.


Solution

  • You are mutating fragment.childNodes while iterating over it, which is causing the unexpected behavior. You just need to append fragment rather than appending each of it's children.

    For example (fixed the element data attributes to correspond to the sort js in your example):

    const fragment = document.createDocumentFragment();
    const sorted = [...document.querySelectorAll('.product')].sort((a,b) => {
      return b.dataset.blockSize - a.dataset.blockSize;
    });
    
    sorted.forEach((elem) => {
      fragment.appendChild(elem);
    });
    
    document.querySelector('#destination').appendChild(fragment);
    <div class="product" data-block-size="3">Product 2</div>
    <div class="product" data-block-size="1">Product 3</div>
    <div class="product" data-block-size="4">Product 1</div>
    <div class="product" data-block-size="1">Product 4</div>
    <div id="destination"></div>

    Or, without using a document fragment (may not be a big performance difference if you are working with a limited number of elements).

    const destination = document.querySelector('#destination');
    const sorted = [...document.querySelectorAll('.product')].sort((a,b) => {
      return b.dataset.blockSize - a.dataset.blockSize;
    });
    
    sorted.forEach((elem) => {
      destination.appendChild(elem);
    });
    <div class="product" data-block-size="3">Product 2</div>
    <div class="product" data-block-size="1">Product 3</div>
    <div class="product" data-block-size="4">Product 1</div>
    <div class="product" data-block-size="1">Product 4</div>
    <div id="destination"></div>