32

I am trying to concat two nodelists using

var ul   = document.querySelector("ul");
var down = document.getElementsByClassName("mobile")[0];
var ul_child = Array.prototype.concat.call(ul.children,down.children);

But this returns only two nodes from ul nodelist and ignore others. What is the most valid to concat two nodelsits? I would like to avoid brute looping them

Darlyn
  • 4,715
  • 12
  • 40
  • 90
  • 2
    You can't mutate node lists and the parameters to `.concat()` must be Arrays for them to be flattened into the result. What's wrong with a loop? –  May 31 '16 at 18:20
  • 4
    `const ulChild = Array.from(ul.children).concat(Array.from(down.children));` – ndugger May 31 '16 at 18:29
  • 1
    @ndugger: You've got the only correct answer here *(or did until War10ck updated)*. –  May 31 '16 at 18:30
  • Depending on the use case, one could use a fragment and append the elements to still have a nodeList, and not an array of nodes. – adeneo May 31 '16 at 18:39
  • Just found this as well: http://stackoverflow.com/questions/2735067/how-to-convert-a-dom-node-list-to-an-array-in-javascript I think it could be useful in your case... – War10ck May 31 '16 at 18:41

6 Answers6

55

Why don't you use one selector to select them at the same time than you do not need to concat them and you end up with an HTML Collection instead of an Array.

var elems = document.querySelectorAll("ul > li, .mobile > *");
console.log(elems);
<ul><li>x</li></ul>
<div class="mobile">y</div>
epascarello
  • 204,599
  • 20
  • 195
  • 236
  • 2
    That assumes there's only one of each, though you'd need `> *` in each individual selector to get the children, which is the goal in the question. –  May 31 '16 at 18:25
  • 7
    This should be a comment, not an answer. – ndugger May 31 '16 at 18:30
  • 2
    @ndugger still concats them with one step. I think it is an answer. Sure you could use array hacks, but then it is not a HTML Collection which may or may not be a good thing. – epascarello May 31 '16 at 18:32
  • 2
    `Array.from` is not an array hack, and I can't think of a **reasonable** argument as to why keeping it as a NodeList is beneficial. – ndugger May 31 '16 at 18:41
  • He asked for `mobile[0]`, but you provided selector `mobile > *` – YaswanthJg Feb 17 '23 at 07:56
27

Set() ensures items are unique and the ES6 Spread operator makes it neat & tidy:

var elems = new Set([
    ...document.querySelectorAll(query1),
    ...document.querySelectorAll(query2)
]);
Neil
  • 1,928
  • 19
  • 14
  • As he needs to select the first element with the `mobile` class, this won't work since, using the spread operator requires iterable and a single element from the `mobile[0]` doesn't work with Set – YaswanthJg Feb 17 '23 at 08:13
  • 1
    Yes it does, you just use a proper query selector as I showed: `document.querySelectorAll(".mobile:first-of-type")` which returns an iterable `NodeList` with one item in it – Neil Feb 17 '23 at 10:35
11

This is a bit of a different approach, but perhaps you could try combining them in your query selector:

var ul_down   = document.querySelectorAll("ul,.mobile:first-child");
Brian Berneker
  • 571
  • 6
  • 6
7

You can try converting the two NodeList objects to arrays first. Then calling concat on the results:

// Convert the first list to an array
var ul_list = document.querySelector("ul"),
    ul_children_array = Array.prototype.slice.call(ul_list.children);

// Convert the second list to an array
var down_list = document.getElementsByClassName("mobile")[0],
    down_children_array = Array.prototype.slice.call(down_list.children);

var ul_child_array = Array.prototype.concat.call(ul_children_array, down_children_array);
War10ck
  • 12,387
  • 7
  • 41
  • 54
  • 3
    You're slicing an Element, not a list of elements. Put the `.children` in the slice, then just use concat on the Arrays –  May 31 '16 at 18:28
  • 2
    @squint Good catch. Thanks. I didn't notice that in my copy/paste. Also thanks for the optimization suggestion. – War10ck May 31 '16 at 18:30
1

And yet another approach:

document.querySelectorAll([".foo", ".bar"])

Will return a normal NodeList with all of the above matching, in the order in which they are found in the DOM.

Tammy Shipps
  • 865
  • 4
  • 13
0

Ok, I guess that the spread operator was pretty new, when this question was posted. But it is possible to do it this way also

const list1 = document.querySelectorAll(".some-selector")
const list2 = document.querySelectorAll(".some-other-selector")

const filters = Array.prototype.concat.call(...list1 , ...list2 )