Using querySelectorAll to retrieve direct children

Viewed 170229

I am able to do this:

<div id="myDiv">
   <div class="foo"></div>
</div>
myDiv = getElementById("myDiv");
myDiv.querySelectorAll("#myDiv > .foo");

That is, I can successfully retrieve all the direct children of the myDiv element that have class .foo.

The problem is, it bothers me that I must include the #myDiv in the selector, because I am running the query on the myDiv element (so it is obviously redundant).

I ought to be able to leave the #myDiv off, but then the selector is not legal syntax since it starts with a >.

Does anyone know how to write a selector which gets just the direct children of the element that the selector is running on?

14 Answers

I Use This:

You can avoid typing "myDiv" twice AND using the arrow.
There are of course always more possibilities.
A modern browser is probably required.


<!-- Sample Code -->

<div id="myDiv">
    <div class="foo">foo 1</div>
    <div class="foo">foo 2
        <div class="bar">bar</div>
    </div>
    <div class="foo">foo 3</div>
</div>

// Return HTMLCollection (Matches 3 Elements)

var allMyChildren = document.querySelector("#myDiv").children;

// Return NodeList (Matches 7 Nodes)

var allMyChildren = document.querySelector("#myDiv").childNodes;

// Match All Children With Class Of Foo (Matches 3 Elements)

var myFooChildren = document.querySelector("#myDiv").querySelectorAll(".foo");

// Match Second Child With Class Of Foo (Matches 1 Element)

var mySecondChild = document.querySelector("#myDiv").querySelectorAll(".foo")[1];

// Match All Children With Class Of Bar (Matches 1 Element)

var myBarChild = document.querySelector("#myDiv").querySelector(".bar");

// Match All Elements In "myDiv" (Matches 4 Elements)

var myDescendants = document.querySelector("#myDiv").querySelectorAll("*");

The following solution is different to the ones proposed so far, and works for me.

The rationale is that you select all matching children first, and then filter out the ones which are not direct children. A child is a direct child if it does not have a matching parent with the same selector.

function queryDirectChildren(parent, selector) {
    const nodes = parent.querySelectorAll(selector);
    const filteredNodes = [].slice.call(nodes).filter(n => 
        n.parentNode.closest(selector) === parent.closest(selector)
    );
    return filteredNodes;
}

HTH!

function siblings(el) {
    return el.closest('*:not(:scope)').querySelectorAll(':scope > *');
}

Pure JS Code

el - is your element here we have function which works in next steps:

  1. take any direct parent, but not el which we are passed (closest can take el, so we are ensure el will not be as a result)
  2. take all direct children

I would like to add that you can extend the compatibility of :scope by just assigning a temporary attribute to the current node.

let node = [...];
let result;

node.setAttribute("foo", "");
result = window.document.querySelectorAll("[foo] > .bar");
// And, of course, you can also use other combinators.
result = window.document.querySelectorAll("[foo] + .bar");
result = window.document.querySelectorAll("[foo] ~ .bar");
node.removeAttribute("foo");

You could extend Element to include a method getDirectDesc() like this:

Element.prototype.getDirectDesc = function() {
    const descendants = Array.from(this.querySelectorAll('*'));
    const directDescendants = descendants.filter(ele => ele.parentElement === this)
    return directDescendants
}

const parent = document.querySelector('.parent')
const directDescendants = parent.getDirectDesc();

document.querySelector('h1').innerHTML = `Found ${directDescendants.length} direct descendants`
<ol class="parent">
    <li class="b">child 01</li>
    <li class="b">child 02</li>
    <li class="b">child 03 <ol>
            <li class="c">Not directDescendants 01</li>
            <li class="c">Not directDescendants 02</li>
        </ol>
    </li>
    <li class="b">child 04</li>
    <li class="b">child 05</li>
    </ol>
    <h1></h1>

Single line Version

var direct_children=Array.from(parent_el.querySelectorAll('span')).filter(function(a){return a.parentNode===parent_el;});

I found this is very handy in case parent element is given. I tested it and it worked 100%.

Related