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
You can use outerHTML
property of each element, and add it to a parent element (that will create by document.createElement()
, the element type doesn't matter).
For example, in ES6:
function getNodeList(elements) {
const parentElement = document.createElement('div');
// This can be a differnet element type, too (but only block (display: block;) element, because it impossible to put block element in inline element, and maybe 'elements' array contains a block element).
let HTMLString = '';
for (let element of elements) {
HTMLString += element.outerHTML;
}
parentElement.innerHTML = HTMLString;
return parentElement.childNodes;
}
Here are my two cents:
item
method that works just like using square brackets, with the exception of returning null
instead of undefined
when you are out of range. You can just return an array with the item method implemented:myArray.item= function (e) { return this[e] || null; }
PS: Maybe you are taking the wrong approach and your custom query method could just wrap a document.querySelectorAll
call that returns what you are looking for.
Since it seems that creating a real NodeList from an array is having severe fallbacks, maybe you could use a regular JS object with a self-made prototype to emulate a NodeList instead. Like so:
var nodeListProto = Object.create({}, {
item: {
value: function(x) {
return (Object.getOwnPropertyNames(this).indexOf(x.toString()) > -1) ? this[x] : null;
},
enumerable: true
},
length: {
get: function() {
return Object.getOwnPropertyNames(this).length;
},
enumerable: true
}
}),
getNodeList = function(nodes) {
var n, eN = nodes.length,
list = Object.create(nodeListProto);
for (n = 0; n < eN; n++) { // *
Object.defineProperty(list, n.toString(), {
value: nodes[n],
enumerable: true
});
}
return list;
};
// Usage:
var nodeListFromArray = getNodeList(arrayOfNodes);
There are still some fallbacks with this solution. instanceof
operator can't recognize the returned object as a NodeList. Also, console loggings and dirrings are shown differently from a NodeList.
(* = A for
loop is used to iterate the passed array, so that the function can accept a passed NodeList too. If you prefer a forEach
loop, that can be used as well, as long as an array only will be passed.)
A live demo at jsFiddle.
why can't I use the NodeList constructor to create a NodeList
Because the DOM specification for the NodeList interface does not specify the WebIDL [Constructor] attribute, so it cannot be created directly in user scripts.
why can't I cast an array to a NodeList in a similar fashion that NodeLists are cast to arrays?
This would certainly be a helpful function to have in your case, but no such function is specified to exist in the DOM specification. Thus, it is not possible to directly populate a NodeList
from an array of Node
s.
While I seriously doubt you would call this "the right way" to go about things, one ugly solution is find CSS selectors that uniquely select your desired elements, and pass all of those paths into querySelectorAll
as a comma-separated selector:
// find a CSS path that uniquely selects this element
function buildIndexCSSPath(elem) {
var parent = elem.parentNode;
// if this is the root node, include its tag name the start of the string
if(parent == document) { return elem.tagName; }
// find this element's index as a child, and recursively ascend
return buildIndexCSSPath(parent) + " > :nth-child(" + (Array.prototype.indexOf.call(parent.children, elem)+1) + ")";
}
function toNodeList(list) {
// map all elements to CSS paths
var names = list.map(function(elem) { return buildIndexCSSPath(elem); });
// join all paths by commas
var superSelector = names.join(",");
// query with comma-joined mega-selector
return document.querySelectorAll(superSelector);
}
toNodeList([elem1, elem2, ...]);
This works by finding CSS strings to uniquely select each element, where each selector is of the form html > :nth-child(x) > :nth-child(y) > :nth-child(z) ...
. That is, each element can be understood to exist as a child of a child of a child (etc.) all the way up the root element. By finding the index of each child in the node's ancestor path, we can uniquely identify it.
Note that this will not preserve Text
-type nodes, because querySelectorAll
(and CSS paths in general) cannot select text nodes.
I have no idea if this will be sufficiently performant for your purposes, though.