Problem
In pure JavaScript, how can I discover an element’s ancestor that is closest up the tree and has a specific class? For instance, consider the following tree:
<div class="far ancestor">
<div class="near ancestor">
<p>Where am I?</p>
</div>
</div>
If I do this on the p and search for ancestor, I get div.near.ancestor.
Asked by rvighne
Solution #1
Most major browsers now offer this feature.
document.querySelector("p").closest(".near.ancestor")
It’s worth noting that this can match selectors as well as classes.
For legacy browsers that don’t provide closest() but do have matches(), selector-matching can be built in the same way as @rvighne’s class matching:
function findAncestor (el, sel) {
while ((el = el.parentElement) && !((el.matches || el.matchesSelector).call(el,sel)));
return el;
}
Answered by the8472
Solution #2
This is effective:
function findAncestor (el, cls) {
while ((el = el.parentElement) && !el.classList.contains(cls));
return el;
}
The while loop waits until el has the required class, then sets el to el’s parent on each iteration, resulting in an ancestor with that class or null at the end.
If someone wants to improve it, here’s a violin. It won’t work in older browsers (such as Internet Explorer); check the classList compatibility table for further information. Because parentNode would require extra work to ensure that the node is an element, parentElement is used instead.
Answered by rvighne
Solution #3
Use element.closest()
Take a look at this DOM example:
<article>
<div id="div-01">Here is div-01
<div id="div-02">Here is div-02
<div id="div-03">Here is div-03</div>
</div>
</div>
</article>
This is how element.closest is used:
var el = document.getElementById('div-03');
var r1 = el.closest("#div-02");
// returns the element with the id=div-02
var r2 = el.closest("div div");
// returns the closest ancestor which is a div in div, here is div-03 itself
var r3 = el.closest("article > div");
// returns the closest ancestor which is a div and has a parent article, here is div-01
var r4 = el.closest(":not(div)");
// returns the closest ancestor which is not a div, here is the outmost article
Answered by simbro
Solution #4
Here is a cross-platform 2017 solution based on the8472 answer and https://developer.mozilla.org/en-US/docs/Web/API/Element/matches:
if (!Element.prototype.matches) {
Element.prototype.matches =
Element.prototype.matchesSelector ||
Element.prototype.mozMatchesSelector ||
Element.prototype.msMatchesSelector ||
Element.prototype.oMatchesSelector ||
Element.prototype.webkitMatchesSelector ||
function(s) {
var matches = (this.document || this.ownerDocument).querySelectorAll(s),
i = matches.length;
while (--i >= 0 && matches.item(i) !== this) {}
return i > -1;
};
}
function findAncestor(el, sel) {
if (typeof el.closest === 'function') {
return el.closest(sel) || null;
}
while (el) {
if (el.matches(sel)) {
return el;
}
el = el.parentElement;
}
return null;
}
Answered by marverix
Solution #5
@rvighne’s solution works great, but ParentElement and ClassList both have compatibility difficulties, as mentioned in the comments. I used the following to make it more compatible:
function findAncestor (el, cls) {
while ((el = el.parentNode) && el.className.indexOf(cls) < 0);
return el;
}
IndexOf, of course, is just looking for the presence of that string; it doesn’t care if it’s the entire string or not. If you have another element with the class ‘ancestor-type,’ it will still return ‘ancestor.’ If this is a problem for you, you can try using regexp to discover an exact match.
Answered by Josh
Post is based on https://stackoverflow.com/questions/22119673/find-the-closest-ancestor-element-that-has-a-specific-class