Reputation: 16974
What's the modern, concise, and fast way to test whether a node has any child that matches a given selector?
By "concise" I mean something similar to jQuery or functional-style, such as avoiding loops. I know native selectors are getting more and more of this type of thing but have not kept up with developments. If such does not yet exist across browsers then I also want to know.
I expected it to be straightforward but searching Google and SO find many false hits using jQuery or finding arbitrary descendants at any depth rather than just the immediate children. There are also some outdated questions from before many functional-style methods were added and standardized between browsers.
Upvotes: 2
Views: 1489
Reputation: 241048
One option is to use the direct child combinator, >
, and the :scope
pseudo-class:
var children = parentElement.querySelectorAll(':scope > div');
var parentElement = document.querySelector('.container');
var children = parentElement.querySelectorAll(':scope > div');
for (var i = 0; i < children.length; i++) {
children[i].style.background = '#f00';
}
.level2 { background-color: #fff; }
<div class="container">
<span>Span</span>
<span>Span</span>
<div class="level1">Direct 'div'
<div class="level2">Nested 'div'</div>
</div>
<div class="level1">Direct 'div'
<div class="level2">Nested 'div'</div>
</div>
<div class="level1">Direct 'div'
<div class="level2">Nested 'div'</div>
</div>
</div>
Note that the :scope
pseudo-class is still considered experimental and does not have full browser support. But nonetheless, it is probably the most "modern" solution (as you asked for).
Alternatively, you could use the .filter()
method and check whether the parent element's children match a given selector:
function getChildren(parent, selector) {
return Array.prototype.filter.call(parent.children, function(node) {
return node.matches(selector);
});
}
Usage:
getChildren(parentElement, 'div'); // Direct children 'div' elements
function getChildren(parent, selector) {
return Array.prototype.filter.call(parent.children, function(node) {
return node.matches(selector);
});
}
var parentElement = document.querySelector('.container');
var children = getChildren(parentElement, 'div');
for (var i = 0; i < children.length; i++) {
children[i].style.background = '#f00';
}
.level2 { background-color: #fff; }
<div class="container">
<span>Span</span>
<span>Span</span>
<div class="level1">Direct 'div'
<div class="level2">Nested 'div'</div>
</div>
<div class="level1">Direct 'div'
<div class="level2">Nested 'div'</div>
</div>
<div class="level1">Direct 'div'
<div class="level2">Nested 'div'</div>
</div>
</div>
Upvotes: 6
Reputation: 9576
A solution with a wider browser support:
[].some.call(yourEl.children, function(e){return e.matches(".z")})
In terms of conciseness, in ES2015 (obviously, by using a transpiler) it would be even better, with arrow functions:
[].some.call(yourEl.children, e=>e.matches(".z"))
And with Array.from
(ES2015):
Array.from(yourEl.children).some(e=>e.matches(".z"))
Or, in an utility function:
function childMatches(elmt, selector){
return [].some.call(elmt.children, function(e){
return e.matches(selector);
});
}
Usage
childMatches(yourElement, ".any-selector-you-want")
Upvotes: 3
Reputation: 66364
If you want to apply a selector starting from a specific node but can't assume :scope
support, you could build a selector to a specific node like this
function selectorPath(node) {
var idx;
if (node.nodeName === 'HTML' || !node.parentNode) return node.nodeName;
idx = Array.prototype.indexOf.call(node.parentNode.children, node);
return selectorPath(node.parentNode) + ' > ' + node.nodeName + ':nth-child(' + (idx + 1) + ')';
}
Then using this in a multi-part selector might look like this
function selectChildAll(parent, selector) {
var pSelector = selectorPath(parent) + ' > ';
selector = pSelector + selector.split(/,\s*/).join(', ' + pSelector);
return parent.querySelectorAll(selector);
}
So an example of using it might be, to get all <p>
and <pre>
immediate children from this answer's content,
var node = document.querySelector('#answer-35028023 .post-text');
selectChildAll(node, 'p, pre'); // [<p>…</p>, etc]
Upvotes: 0
Reputation: 36592
Use the child selector >
document.querySelectorAll('.parent-selector > .child-selector').length > 0
Upvotes: 1