Lorenzo De Tomasi
Lorenzo De Tomasi

Reputation: 89

Pure Javascript: convert a string to NodeList

Using pure javascript, I'm trying to convert a string to NodeList and to append each NodeListItem to a container element, useinf forEach.

The string list has 6 items, but only 3 are appended. Why?

var controlsString = '<button class="{{namespace}}_prev" onclick="isotype_sliders[{{index}}].slide_to(\'prev\');">Slide to prev</button>'+
                '<button class="{{namespace}}_pause" onclick="isotype_sliders[{{index}}].pause(); this.style.display=\'none\'; this.nextElementSibling.style.display=\'initial\';">Pause</button>'+
                '<button class="{{namespace}}_play" style="display: none;" onclick="isotype_sliders[{{index}}].play(); this.style.display=\'none\'; this.previousElementSibling.style.display = \'initial\';">Play</button>'+
                '<button class="{{namespace}}_next" onclick="isotype_sliders[{{index}}].slide_to(\'next\');">Slide to next</button>'+
                '<button class="{{namespace}}_shuffle" onclick="isotype_sliders[{{index}}].shuffle();">Shuffle</button>'+
                '<button class="{{namespace}}_slide_to" onclick="isotype_sliders[{{index}}].slide_to(2);">Slide to 2</button>';
var container = document.getElementById('container');
var getNodes = str => new DOMParser().parseFromString(str, 'text/html').body.childNodes;
var controlsNodes = getNodes(controlsString);
console.log(controlsNodes);
controlsNodes.forEach(function(controlNode, controls_index, listObj){
    console.log(controlNode);
    container.appendChild(controlNode);
});

Look at my experiment on JSFiddle: https://jsfiddle.net/lorenzodetomasi/kjhac52y/

Thank you.

Upvotes: 2

Views: 1856

Answers (2)

Andrzej Smyk
Andrzej Smyk

Reputation: 1724

Not really sure why forEach does not work on NodeList, but you can iterae over all elements when you convert NodeList to Array.from(controlNodes) or Array.prototype.forEach.call(controlsNodes, function (controlNode) {...}).

Please see corrected code below. For more info any examples refer to https://developer.mozilla.org/en-US/docs/Web/API/NodeList.

var controlsString = '<button class="{{namespace}}_prev" onclick="isotype_sliders[{{index}}].slide_to(\'prev\');">Slide to prev</button>'+
                '<button class="{{namespace}}_pause" onclick="isotype_sliders[{{index}}].pause(); this.style.display=\'none\'; this.nextElementSibling.style.display=\'initial\';">Pause</button>'+
                '<button class="{{namespace}}_play" style="display: none;" onclick="isotype_sliders[{{index}}].play(); this.style.display=\'none\'; this.previousElementSibling.style.display = \'initial\';">Play</button>'+
                '<button class="{{namespace}}_next" onclick="isotype_sliders[{{index}}].slide_to(\'next\');">Slide to next</button>'+
                '<button class="{{namespace}}_shuffle" onclick="isotype_sliders[{{index}}].shuffle();">Shuffle</button>'+
                '<button class="{{namespace}}_slide_to" onclick="isotype_sliders[{{index}}].slide_to(2);">Slide to 2</button>';
var container = document.getElementById('container');
var getNodes = str => new DOMParser().parseFromString(str, 'text/html').body.childNodes;
var controlsNodes = getNodes(controlsString);

Array.from(controlsNodes).forEach(function(controlNode, controls_index, listObj){
    console.log(controlNode);
    container.appendChild(controlNode);
});
<div id='container'></div>

Upvotes: 0

raina77ow
raina77ow

Reputation: 106375

The key problem is using forEach on result of childNodes directly. Quoting the docs:

Node.childNodes read-only property returns a live NodeList of child nodes of the given element [...]

In other words, each time you append a node from controlsNodes, you move it from one place in DOM to another, and the corresponding NodeList is rebuilt - but forEach() just doesn't care and moves forward to the next index of that NodeList. That's why each even element of the original collection is skipped.

There're several workarounds for that. The most straightforward is to convert dynamic NodeList to static Array - with either Array.from() or spread operator:

[...controlsNodes].forEach(function(controlNode, controls_index, listObj){
    console.log(controlNode);
    container.appendChild(controlNode);
});

An alternative approach is biting the bullet - taking into account the dynamic nature of NodeList:

while (controlsNodes.length !== 0) {
  container.appendChild(controlsNodes[0]);
}

Upvotes: 4

Related Questions