18

This is one of those it seems so simple, but I cannot come up with a good way to go about it.

I have a node, maybe nodelist = document.getElementById("mydiv"); - I need to normalize this to a node list. And not an array either: an actual, bona-fide nodeList object.

Not nodelist = [document.getElementById("mydiv")];

No libraries, please.

3
  • possible duplicate of Creating a DOM NodeList Commented Nov 12, 2012 at 21:41
  • Very similar... slightly different implementation. Good to have that reference here though. Commented Nov 12, 2012 at 21:48
  • Given the issue's with my answer, could you please un-accept my answer in order that I might delete it? Commented Jul 28, 2016 at 18:49

6 Answers 6

10

Take any element already referenced in JavaScript, give it an attribute we can find using a selector, find it as a list, remove the attribute, return the list.

function toNodeList(elm){
    var list;
    elm.setAttribute('wrapNodeList','');
    list = document.querySelectorAll('[wrapNodeList]');
    elm.removeAttribute('wrapNodeList');
    return list;
}

Extended from bfavaretto's answer.


function toNodeList(elm, context){
    var list, df;
    context = context // context provided
           || elm.parentNode; // element's parent
    if(!context && elm.ownerDocument){ // is part of a document
        if(elm === elm.ownerDocument.documentElement || elm.ownerDocument.constructor.name === 'DocumentFragment'){ // is <html> or in a fragment
            context = elm.ownerDocument;
        }
    }
    if(!context){ // still no context? do David Thomas' method
        df = document.createDocumentFragment();
        df.appendChild(elm);
        list = df.childNodes;
        // df.removeChild(elm); // NodeList is live, removeChild empties it
        return list;
    }
    // selector method
    elm.setAttribute('wrapNodeList','');
    list = context.querySelectorAll('[wrapNodeList]');
    elm.removeAttribute('wrapNodeList');
    return list;
}

There is another way to do this I thought of recently

var _NodeList = (function () {
    var fragment = document.createDocumentFragment();
    fragment.appendChild(document.createComment('node shadows me'));
    function NodeList (node) {
        this[0] = node;
    };
    NodeList.prototype = (function (proto) {
        function F() {} // Object.create shim
        F.prototype = proto;
        return new F();
    }(fragment.childNodes));
    NodeList.prototype.item = function item(i) {
        return this[+i || 0];
    };
    return NodeList;
}());

Now

var list = new _NodeList(document.body); // note **new**
list.constructor === NodeList; // all these are true
list instanceof NodeList;
list.length === 1;
list[0] === document.body;
list.item(0) === document.body;
Sign up to request clarification or add additional context in comments.

9 Comments

You're saying you can't necessarily access the element you want with any CSS selector?
Yes. The node could have been dynamically created or selected from the document.
@RandyHall edit should work in almost any situation (with the exception of elm.nodeType !== 1)
So basically, if it's in the document or a context container, do your method, if it's not, David Thomas' method would be safe. Nice.
In most cases, you'd be completely correct. But there's functions written by others that I have to utilize but cannot affect that require and check for a nodelist specifically.
|
8

If you're targeting browsers that support document.querySelectorAll, it will always return a NodeList. So:

var nodelist = document.querySelectorAll("#mydiv");

1 Comment

Good thought as well, however that's not ALWAYS how I'm getting my node. +1 for idea other may find relevant and useful!
7

Reviving this because I recently remembered something about JavaScript. This depends on how the NodeList is being checked, but..

const singleNode = ((nodeList) => (node) => {
  const layer = { // define our specific case
    0: { value: node, enumerable: true },
    length: { value: 1 },
    item: {
      value(i) {
        return this[+i || 0];
      }, 
      enumerable: true,
    },
  };
  return Object.create(nodeList, layer); // put our case on top of true NodeList
})(document.createDocumentFragment().childNodes); // scope a true NodeList

Now, if you do

const list = singleNode(document.body); // for example

list instanceof NodeList; // true
list.constructor === NodeList; // true

and list has properties length 1 and 0 as your node, as well as anything inherited from NodeList.

If you can't use Object.create, you could do the same except as a constructor with prototype nodelist and set this['0'] = node;, this['length'] = 1; and create with new.


ES5 version

var singleNode = (function () {
    // make an empty node list to inherit from
    var nodelist = document.createDocumentFragment().childNodes;
    // return a function to create object formed as desired
    return function (node) {
        return Object.create(nodelist, {
            '0': {value: node, enumerable: true},
            'length': {value: 1},
            'item': {
                "value": function (i) {
                    return this[+i || 0];
                }, 
                enumerable: true
            }
        }); // return an object pretending to be a NodeList
    };
}());

4 Comments

Also going to point out that if you want to use list.item, you'll have to shadow it to avoid an illegal invocation (perhaps use two-level prototyping).
I appreciate that it's been several years, by why does singleNode return a self-executing function? It seems to work fine by just returning the Object.create... after creating the empty DocumentFragment? Is it just so the document fragment is created once, no matter how many times it's called?
@RegularJo yep, as we're putting the document fragment in the prototype and not modifying it we can re-use the same fragment as many times as we'd like if we capture it in a scope
Updated for ES6
7

Yet another way to do this based on Reflect.construct: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/construct

As in other cases it requires patching NodeList.prototype.item to make calls to this function work.

NodeList.prototype.item = function item(i) {
    return this[+i || 0];
};
let nl = Reflect.construct(Array, [], NodeList);

To create it with nodes pass array of nodes as second argument. This method passes checks:

list instanceof NodeList; // true
list.constructor === NodeList; // true

Array, created with it, is iterable with for..of, forEach and other standard methods and you can add elements into it with simple nl[n] = node;.

4 Comments

I'm not sure about the item declaration and +i syntax (never seen that before) but Reflect.construct() works well.
I take it back... in Safari 12 we get "TypeError: Reflect.construct requires the third argument to be a constructor if present".
Well, you can try to play with this then: div.parentNode.querySelectorAll(:scope > :nth-child(${Array.from(div.parentNode.children).indexOf(div)+1})) Not sure about compatibility with Safari 12 and it won't work in IE11 for sure, but it returns actual NodeList. Element must have a parent, though. Could be solved by temporarily adding it into another element in case parentNode is null.
You don't even need to patch the prototype anymore!
3
var nodeList = document.createDocumentFragment();
nodeList.appendChild(document.getElementById("myDiv"));

4 Comments

But nodeList instanceof NodeList returns false.
You can return the nodeList after appending like such: console.log(nodeList.childNodes instanceof NodeList); // returns true
The problem with this is that adding the node to the fragment removes it from the document DOM.
This is handy for writing unit tests
1

Thanks @lao

var nodeList = document.createDocumentFragment();
nodeList.appendChild(document.getElementById("myDiv"));
nodeList = nodeList.childNodes

console.log(nodeList instanceof NodeList);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.