Child selector using `querySelectorAll` on a DOM collection
Asked Answered
A

3

25

Let's presume you got a list with nested child lists.

<ul>
    <li></li>
    <li>
        <ul>
            <li></li>
            <li></li>
        </ul>
    </li>
    <li></li>
</ul>

And use document.querySelectorAll() to make a selection:

var ul = document.querySelectorAll("ul");

How can i use the ul collection to get the direct child elements?

ul.querySelectorAll("> li"); 
// Gives 'Error: An invalid or illegal string was specified'

Let's presume ul is cached somehow (otherwise i could have done ul > li directly).

In jQuery this works:

$("ul").find("> li");

But it doesn't in native querySelectorAll. Any solutions?

Araldo answered 17/4, 2012 at 10:58 Comment(3)
That selector doesn't really make sense. With jQuery you'd use .children("li") instead of .find("> li"). I'm surprised that works at all, actually.Veator
@MДΓΓБДLL .find('> li ul span') is useful, but not so much on its own like in the OP's example.Buiron
@MДΓΓБДLL the example is a bit contrived, but it definitely has a real-world use case :)Araldo
K
41

The correct way to write a selector that is "rooted" to the current element is to use :scope.

ul.querySelectorAll(":scope > li");

See my answer here for an explanation and a robust, cross-browser solution: https://mcmap.net/q/107671/-using-queryselectorall-to-retrieve-direct-children

Knopp answered 26/2, 2014 at 0:45 Comment(3)
Good to see this exists! :)Buiron
Keep in mind the browser compatibility: Chrome: yes, FF: 32, IE: no, opera: no, Safari: 7Scissile
in addition to what @Scissile said, to this day it is still also not supported in Microsoft Edge. Trying to use 'scope' in querySelectorAll gives a syntax error. So much for the "you don't need jQuery" camp.Dasya
B
13

Because the ul returned is a NodeList, it doesn't implicitly loop over its contents like a jQuery collection. You'd need to use ul[0].querySelectorAll() or better still select the ul with querySelector().

Besides that, querySelectorAll() won't take a > and work from its current context. However, you can get it to work using lazd's answer (though check the browser compatibility), or any of these workarounds (which should have no browser issues)...

[].filter.call(ul.querySelectorAll("li"), function(element){
     return element.parentNode == ul;
}); 

jsFiddle.

This will select all li elements that are descendants of your ul, and then remove the ones which are not direct descendants.

Alternatively, you could get all childNodes and then filter them...

[].filter.call(ul.childNodes, function(node) {
    return node.nodeType == 1 && node.tagName.toLowerCase() == 'li';
});

jsFiddle.

Buiron answered 17/4, 2012 at 10:59 Comment(2)
Yup, that seems like a workable solution. Too bad it takes three lines of code what could be done in a single character :) Note that filter() won't work because ul is a NodeList, so you need to do Array.prototype.slice.call first.Araldo
@Araldo You can always add it to Element.prototype as a function :)Cockeye
C
2

You need to iterate over the NodeList returned by document.querySelectorAll() and then call element.querySelectorAll() for each element in that list.

Concerning answered 17/4, 2012 at 11:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.