myDiv = getElementById("myDiv...">

Using querySelectorAll to Retrieve Direct Children

I can do it:

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

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

The problem is that I need to include #myDiv in the selector, because I run the request in the myDiv element (therefore it is clearly redundant).

I need to leave #myDiv off, but the selector is not legal syntax since it starts with > .

Does anyone know how to write a selector that receives only the direct children of the element on which the selector works?

+81
javascript dom css-selectors
Sep 09 '10 at 21:57
source share
10 answers

Good question. When asked about it, there was no universally implemented way to execute โ€œroot combinator queriesโ€ (as John Rezig called them ).

The pseudo-class : scope has now been introduced. It is not supported in [pre-Chrominum] versions of Edge or IE, but has been supported by Safari for several years. Using this, your code could become:

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

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

Otherwise, if you still cannot rely on :scope , I cannot think of another way to handle your situation without adding additional custom filter logic (for example, find myDiv.getElementsByClassName("foo") whose .parentNode === myDiv ) , and obviously not ideal if you are trying to maintain a single code path that really just wants to take an arbitrary selector string as input and a hit list as output! But if, like me, you ended up asking this question simply because you were stuck thinking that โ€œall that you had was a hammer,โ€ do not forget that there are many other tools that the DOM offers.

+37
Jun 20 '13 at 5:45
source share

Does anyone know how to write a selector that receives only the direct children of the element on which the selector works?

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

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

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

+109
Jan 15 '14 at 0:33
source share

Here is a flexible method written in vanilla JS that allows you to run a CSS select query only on 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; } 
+4
Jul 29 '13 at 18:32
source share

if you know for sure that the element is unique (for example, your case with an identifier):

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

For a more "global" solution: (use matchSelector 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 and sel is your selector. It can be fully used as a prototype.

+2
Dec 07
source share

I created a function to handle this situation, I thought I wanted to 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; } 

Essentially, you create a random string (the randomString function is an imported npm module, but you can make your own.), Then use this random string to ensure that you get the element that you expect in the selector. Then you can use > after that.

The reason I am not using the id attribute is because the id attribute can already be used, and I do not want to override it.

+1
Jun 15 '16 at 21:38
source share

The following solution is different from the one proposed so far and works for me.

The rationale is that you first select all relevant child elements and then filter out those that are not direct children. A child is a direct descendant 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; } 

NTN!

+1
Oct 29 '17 at 18:44
source share

Well, we can easily get all the direct children of an element using childNodes , and we can select ancestors with a specific class using querySelectorAll , so it's easy to imagine that we could create a new function that gets both and compares 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.

Using

  document.getElementById("myDiv").queryDirectChildren(".foo"); 
0
Jun 27 '17 at 20:45
source share

I would like to add that you can extend compatibility : scope by simply 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"); 
0
Aug 29 '18 at 22:56
source share

I just do it without even trying to do it. Will this work?

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

Try it, maybe it works, maybe not. Apolovies, but right now I'm not on the computer to try (answering from my iPhone).

-one
Nov 09 '12 at 22:55
source share

I would go with

 var myFoo = document.querySelectorAll("#myDiv > .foo"); var myDiv = myFoo.parentNode; 
-one
Jul 25 '16 at 12:54
source share



All Articles