Search code examples
cssqueryselector

How to Query Select all children of a class, but no grandsons of that class


I have the css class 'control', and I want to get all the 'control' children elements from my container, but I dont want to get grandsons 'control'.

<div id='mydiv'>
    <div id='div1' class='control'>
        <div id='div2' class='control'></div>
        <div id='div3' class='control'></div>
    </div>
    <div class='other'>
        <div id='div4' class='control'></div>
        <div id='div5' class='control'>
            <div id='div6' class='control'></div>
            <div id='div7' class='control'></div>
        </div>
    </div>
</div>

The call below will get me all the controls inside 'mydiv'.

document.querySelectorAll('#mydiv .control');

I want a query that brings me only #div1, #div4 and #div5; Is that possible?


Solution

  • With only querySelector* it will not work at the moment.
    In the future, browsers will support the :has() pseudo-selector.

    Then you can select only elements which has or not has elements inside them like this:

    #mydiv .control:not(:has(.control))

    See the current browser support for this: https://caniuse.com/css-has


    For now you need following sulution:

    const elements = [...document.querySelectorAll('#mydiv .control')].filter(element => {
       return element.parentNode.closest('.control') == null
    });
    
    console.log(elements);
    <div id='mydiv'>
        <div id='div1' class='control'>
            <div id='div2' class='control'></div>
            <div id='div3' class='control'></div>
        </div>
        <div class='other'>
            <div id='div4' class='control'></div>
            <div id='div5' class='control'>
                <div id='div6' class='control'></div>
                <div id='div7' class='control'></div>
            </div>
        </div>
    </div>

    Explanation:

    Get at first all .control elements into an array (querySelectorAll returns a NodeList and NodeList does'nt have methods like filter. So we extract it with the spread syntax [...variable]

    Then filter all elements which has no parent elements named .control.
    .closest() can return itself, so we need to make sure we call parentNode at first.


    UPDATE End September 2022

    Browser are starting to implement :has()