NOTE: Before this question is assumed a duplicate, there is a section at the bottom of this question that addresses why a few similar questions do not provide the answer I am looking for.
We all know that it is easy to convert a NodeList to an Array and there are many ways to do it:
[].slice.call(someNodeList)
// or
Array.from(someNodeList)
// etc...
What I am after is the reverse; how can I convert an array of nodes into a static NodeList?
Why do I want to do this?
Without getting too deep into things, I am creating a new method to query elements on the page i.e:
Document.prototype.customQueryMethod = function (...args) {...}
Trying to stay true to how querySelectorAll
works, I want to return a static collection NodeList
instead of an array.
I have approached the problem in three different ways so far:
Attempt 1:
Creating a Document Fragment
function createNodeList(arrayOfNodes) {
let fragment = document.createDocumentFragment();
arrayOfNodes.forEach((node) => {
fragment.appendChild(node);
});
return fragment.childNodes;
}
While this does return a NodeList, this does not work because calling appendChild
removes the node from its current location in the DOM (where it should stay).
Another variation of this involves cloning
the nodes and returning the clones. However, now you are returning the cloned nodes, which have no reference to the actual nodes in the DOM.
Attempt 2:
Attempting to "mock" the NodeList constructor
const FakeNodeList = (() => {
let fragment = document.createDocumentFragment();
fragment.appendChild(document.createComment('create a nodelist'));
function NodeList(nodes) {
let scope = this;
nodes.forEach((node, i) => {
scope[i] = node;
});
}
NodeList.prototype = ((proto) => {
function F() {
}
F.prototype = proto;
return new F();
})(fragment.childNodes);
NodeList.prototype.item = function item(idx) {
return this[idx] || null;
};
return NodeList;
})();
And it would be used in the following manner:
let nodeList = new FakeNodeList(nodes);
// The following tests/uses all work
nodeList instanceOf NodeList // true
nodeList[0] // would return an element
nodeList.item(0) // would return an element
While this particular approach does not remove the elements from the DOM, it causes other errors, such as when converting it to an array:
let arr = [].slice.call(nodeList);
// or
let arr = Array.from(nodeList);
Each of the above produces the following error: Uncaught TypeError: Illegal invocation
I am also trying to avoid "mimicking" a nodeList with a fake nodelist constructor as I believe that will likely have future unintended consequences.
Attempt 3:
Attaching a temporary attribute to elements to re-query them
function createNodeList(arrayOfNodes) {
arrayOfNodes.forEach((node) => {
node.setAttribute('QUERYME', '');
});
let nodeList = document.querySelectorAll('[QUERYME]');
arrayOfNodes.forEach((node) => {
node.removeAttribute('QUERYME');
});
return nodeList;
}
This was working well, until I discovered that it doesn't work for certain elements, like SVG
's. It will not attach the attribute (although I did only test this in Chrome).
It seems this should be an easy thing to do, why can't I use the NodeList constructor to create a NodeList, and why can't I cast an array to a NodeList in a similar fashion that NodeLists are cast to arrays?
How can I convert an array of nodes to a NodeList, the right way?
Similar questions that have answers that don't work for me:
The following questions are similar to this one. Unfortunately, these questions/answers don't solve my particular problem for the following reasons.
How can I convert an Array of elements into a NodeList? The answer in this question uses a method that clones nodes. This will not work because I need to have access to the original nodes.
Create node list from a single node in JavaScript uses the document fragment approach (Attempt 1). The other answers try similar things at Attempts 2, and 3.
Creating a DOM NodeList is using E4X
, and therefore does not apply. And even though it is using that, it still removes the elements from the DOM.
querySelectorAll
to return an array. The only real benefit to aNodeList
is its potential to be live. – Globedocument.getElementsbyTagName
return a NodeList without removing the elements from the document. That's the behaviour that OP tries to mimic – CorporealappendChild
, how does the NodeList keep the elements where they were before the query? My goal is to keep the nodes in the location they were before the query such as howquerySelectorAll
, etc., works. If that makes sense. @Pablo's comment is correct. – Boutte.childNodes
is a live NodeList, not a static one. – QuotidianappendChild
is needed in the first place in that attempt. – Bouttefragment.querySelectorAll(":scope > *")
orfragment.find("> *")
? Those should give you static nodelist, and you can move the nodes to their previous location in the DOM after having selected them. – Quotidian:scope
is not working within the context of a document fragment. I also tried to add thefind
prototypical method to theDocumentFragment
which also did not work. I used some of the polyfill ideas from this answer – Boutte