Using querySelectorAll to retrieve direct children

JavascriptDomCss Selectors

Javascript Problem Overview


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?

Javascript Solutions


Solution 1 - Javascript

Good question. At the time it was asked, a universally-implemented way to do "combinator rooted queries" (as John Resig called them) did not exist.

Now the :scope pseudo-class has been introduced. It is not supported on [pre-Chrominum] versions of Edge or IE, but has been supported by Safari for a few years already. Using that, your code could become:

let myDiv = getElementById("myDiv");
myDiv.querySelectorAll(":scope > .foo");

Note that in some cases you can also skip .querySelectorAll and use other good old-fashioned DOM API features. For example, instead of myDiv.querySelectorAll(":scope > *") you could just write myDiv.children, for example.

Otherwise if you can't yet rely on :scope, I can't think of another way to handle your situation without adding more custom filter logic (e.g. find myDiv.getElementsByClassName("foo") whose .parentNode === myDiv), and obviously not ideal if you're trying to support one code path that really just wants to take an arbitrary selector string as input and a list of matches as output! But if like me you ended up asking this question simply because you got stuck thinking "all you had was a hammer" don't forget there are a variety of other tools the DOM offers too.

Solution 2 - Javascript

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

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

var myDiv = getElementById("myDiv");
var fooEls = myDiv.querySelectorAll(":scope > .foo");

However, browser support is limited and you'll need a shim if you want to use it. I built scopedQuerySelectorShim for this purpose.

Solution 3 - Javascript

if you know for sure the element is unique (such as your case with the ID):

myDiv.parentElement.querySelectorAll("#myDiv > .foo");

For a more "global" solution: (use a matchesSelector shim)

function getDirectChildren(elm, sel){
    var ret = [], i = 0, l = elm.childNodes.length;
    for (var i; i < l; ++i){
        if (elm.childNodes[i].matchesSelector(sel)){
            ret.push(elm.childNodes[i]);
        }
    }
    return ret;
}

where elm is your parent element, and sel is your selector. Could totally be used as a prototype as well.

Solution 4 - Javascript

Here's a flexible method, written in vanilla JS, that allows you to run a CSS selector query over only the direct children of an element:

var count = 0;
function queryChildren(element, selector) {
  var id = element.id,
      guid = element.id = id || 'query_children_' + count++,
      attr = '#' + guid + ' > ',
      selector = attr + (selector + '').replace(',', ',' + attr, 'g');
  var result = element.parentNode.querySelectorAll(selector);
  if (!id) element.removeAttribute('id');
  return result;
}

Solution 5 - Javascript

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("*");

Solution 6 - Javascript

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!

Solution 7 - Javascript

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

Solution 8 - Javascript

I created a function to handle this situation, thought I would share it.

getDirectDecendent(elem, selector, all){
	const tempID = randomString(10) //use your randomString function here.
	elem.dataset.tempid = tempID;

	let returnObj;
	if(all)
		returnObj = elem.parentElement.querySelectorAll(`[data-tempid="${tempID}"] > ${selector}`);
	else
		returnObj = elem.parentElement.querySelector(`[data-tempid="${tempID}"] > ${selector}`);

	elem.dataset.tempid = '';
	return returnObj;
}

In essence what you are doing is generating a random-string (randomString function here is an imported npm module, but you can make your own.) then using that random string to guarantee that you get the element you are expecting in the selector. Then you are free to use the > after that.

The reason I am not using the id attribute is that the id attribute may already be used and I don't want to override that.

Solution 9 - Javascript

Well we can easily get all the direct children of an element using childNodes and we can select ancestors with a specific class with querySelectorAll, so it's not hard to imagine we could create a new function that gets both and compares the two.

HTMLElement.prototype.queryDirectChildren = function(selector){
  var direct = [].slice.call(this.directNodes || []); // Cast to Array
  var queried = [].slice.call(this.querySelectorAll(selector) || []); // Cast to Array
  var both = [];
  // I choose to loop through the direct children because it is guaranteed to be smaller
  for(var i=0; i<direct.length; i++){
    if(queried.indexOf(direct[i])){
      both.push(direct[i]);
    }
  }
  return both;
}

Note: This will return an Array of Nodes, not a NodeList.

Usage

 document.getElementById("myDiv").queryDirectChildren(".foo");

Solution 10 - Javascript

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");

Solution 11 - Javascript

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>

Solution 12 - Javascript

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%.

Solution 13 - Javascript

I am just doing this without even trying it. Would this work?

myDiv = getElementById("myDiv");
myDiv.querySelectorAll(this.id + " > .foo");

Give it a try, maybe it works maybe not. Apolovies, but I am not on a computer now to try it (responding from my iPhone).

Solution 14 - Javascript

I'd have gone with

var myFoo = document.querySelectorAll("#myDiv > .foo");
var myDiv = myFoo.parentNode;

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionmattshView Question on Stackoverflow
Solution 1 - JavascriptnatevwView Answer on Stackoverflow
Solution 2 - JavascriptlazdView Answer on Stackoverflow
Solution 3 - JavascriptRandy HallView Answer on Stackoverflow
Solution 4 - JavascriptcsuwldcatView Answer on Stackoverflow
Solution 5 - JavascripttheMaxxView Answer on Stackoverflow
Solution 6 - JavascriptMelleView Answer on Stackoverflow
Solution 7 - JavascriptMark KView Answer on Stackoverflow
Solution 8 - JavascriptcpiView Answer on Stackoverflow
Solution 9 - JavascriptDustin PoissantView Answer on Stackoverflow
Solution 10 - JavascriptVasile Alexandru PeşteView Answer on Stackoverflow
Solution 11 - JavascriptNetsi1964View Answer on Stackoverflow
Solution 12 - JavascriptJoshy FrancisView Answer on Stackoverflow
Solution 13 - JavascriptGreesoView Answer on Stackoverflow
Solution 14 - JavascriptLee ChaseView Answer on Stackoverflow