Check if class exists somewhere in parent
Asked Answered
F

11

66

I want to check if a class exsits somewhere in one of the parent elements of an element.

I don't want to use any library, just vanilla JS.

In the examples below it should return true if the element in question resides somewhere in the childs of an element with "the-class" as the class name.

I think it would be something like this with jQuery:

if( $('#the-element').parents().hasClass('the-class') ) {
    return true;
}

So this returns true:

<div>
    <div class="the-class">
        <div id="the-element"></div>
    </div>
</div>

So does this:

<div class="the-class">
    <div>
        <div id="the-element"></div>
    </div>
</div>

...but this returns false:

<div>
    <div class="the-class">
    </div>
    <div id="the-element"></div>
</div>
Family answered 31/5, 2013 at 18:50 Comment(3)
Using jquery this would be with closest('.theclass').lengthUndesigning
Do you have any code to show what you have tried so we can see where you may need assistance with?Cymophane
So, you want to see if any of the ancestors, or siblings of ancestors, have a given class-name?Idem
D
62

You'll have to do it recursively :

// returns true if the element or one of its parents has the class classname
function hasSomeParentTheClass(element, classname) {
    if (element.className.split(' ').indexOf(classname)>=0) return true;
    return element.parentNode && hasSomeParentTheClass(element.parentNode, classname);
}

Demonstration (open the console to see true)

Drover answered 31/5, 2013 at 18:53 Comment(12)
this fails if som parent doesnt have a classVanderhoek
@Carl91 If an element has no class, className is "".Undesigning
cannot split of undefinedVanderhoek
@DenysSéguret the problem is the element will eventually be document, which has no class.Territorialism
I used this code to work with elements with no class: var hasSomeParentTheClass = function(element, classname) { if (element.className && element.className.split(' ').indexOf(classname) >= 0) return true; return element.parentNode && hasSomeParentTheClass(element.parentNode, classname); } Denson
Using parentElement instead of parentNode will avoid this problem.Egypt
Why not use element.classList.has(classname) ?Norbert
@Norbert IE9 has no idea what a classList is.Undesigning
for lazy people who want the same but using 'id' instead of class : function hasSomeParentTheId(element, id) { if (element.id == id) return true; return element.parentNode && hasSomeParentTheId(element.parentNode, id); }Amphisbaena
I've put together a version of this on Codepen which checks for a few more edge cases.Metacarpus
Typescript version for those interested : export const hasSomeParentTheClass = (element: HTMLElement, classname: string): boolean | null => { if (element.className.split(" ").indexOf(classname) >= 0) return true; return element.parentNode && hasSomeParentTheClass(<HTMLElement>element.parentNode, classname); }Rollin
use element.className?.split(' ') instead of element.className.split(' '). It's worked for me.Jonette
B
79

You can use the closest() method of Element that traverses parents (heading toward the document root) of the Element until it finds a node that matches the provided selectorString. Will return itself or the matching ancestor. If no such element exists, it returns null.

You can convert the returned value into boolean

const el = document.getElementById('div-03');

const r1 = el.closest("#div-02");  
console.log(Boolean(r1));
// returns the element with the id=div-02

const r2 = el.closest("#div-not-exists");
console.log(Boolean(r2));
<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>
Brookweed answered 28/6, 2019 at 12:41 Comment(6)
This is the more modern answer. However, it has no support for an ancient browser called Internet Explorer, and would require a polyfill. (MDN Reference)Mange
This is the answer you want unless you still have to support MSIE 11 or lesser.Encumbrance
This should be the accepted answer.Impetuosity
When looking for a class as a parent of a div using JQuery, I check for length 0 or 1 since Boolean always returns true. console.log(r1.length);Entail
What a godsend of an answer.Backspace
awesome answer. FYI, you can also defeat the "return self" behavior by simply starting the search from the parent node e.g. el.parentNode.closest('your-selector').Motile
D
62

You'll have to do it recursively :

// returns true if the element or one of its parents has the class classname
function hasSomeParentTheClass(element, classname) {
    if (element.className.split(' ').indexOf(classname)>=0) return true;
    return element.parentNode && hasSomeParentTheClass(element.parentNode, classname);
}

Demonstration (open the console to see true)

Drover answered 31/5, 2013 at 18:53 Comment(12)
this fails if som parent doesnt have a classVanderhoek
@Carl91 If an element has no class, className is "".Undesigning
cannot split of undefinedVanderhoek
@DenysSéguret the problem is the element will eventually be document, which has no class.Territorialism
I used this code to work with elements with no class: var hasSomeParentTheClass = function(element, classname) { if (element.className && element.className.split(' ').indexOf(classname) >= 0) return true; return element.parentNode && hasSomeParentTheClass(element.parentNode, classname); } Denson
Using parentElement instead of parentNode will avoid this problem.Egypt
Why not use element.classList.has(classname) ?Norbert
@Norbert IE9 has no idea what a classList is.Undesigning
for lazy people who want the same but using 'id' instead of class : function hasSomeParentTheId(element, id) { if (element.id == id) return true; return element.parentNode && hasSomeParentTheId(element.parentNode, id); }Amphisbaena
I've put together a version of this on Codepen which checks for a few more edge cases.Metacarpus
Typescript version for those interested : export const hasSomeParentTheClass = (element: HTMLElement, classname: string): boolean | null => { if (element.className.split(" ").indexOf(classname) >= 0) return true; return element.parentNode && hasSomeParentTheClass(<HTMLElement>element.parentNode, classname); }Rollin
use element.className?.split(' ') instead of element.className.split(' '). It's worked for me.Jonette
H
13

You can use some and contains to achieve the result:

function hasParentWithMatchingSelector (target, selector) {
  return [...document.querySelectorAll(selector)].some(el =>
    el !== target && el.contains(target)
  )
}

// usage
hasParentWithMatchingSelector(myElement, '.some-class-name');
Hueston answered 12/5, 2017 at 13:27 Comment(1)
This is really clever.. just needs a fix because el.contains(el) returns true. So we should rewrite it as: el !== target && el.contains(target)Kosse
D
8

The fiddle

The code

function hasClass(element, className) {
  var regex = new RegExp('\\b' + className + '\\b');
  do {
    if (regex.exec(element.className)) {
      return true;
    }
    element = element.parentNode;
  } while (element);
  return false;
}

OR

function hasClass(element, className) {
  do {
    if (element.classList && element.classList.contains(className)) {
      return true;
    }
    element = element.parentNode;
  } while (element);
  return false;
}
Declamatory answered 27/9, 2013 at 11:0 Comment(0)
T
5

I'm ok with the function that Denys Séguret posted, it looks elegant and I like it. I just tweaked a little bit that function since if the class specified in the parameter, is not present in the whole DOM, it fails when the recursion reaches the document object because is true that we control if the element has the parent node (in the last line, and when the document is the element the parent node is null) but before we execute the previous line, and when the element is the document, document.className is undefined and it fails, so the control must be moved to the top.

function hasSomeParentTheClass(element, classname) {
    //
    // If we are here we didn't find the searched class in any parents node
    //
    if (!element.parentNode) return false;
    //
    // If the current node has the class return true, otherwise we will search
    // it in the parent node
    //
    if (element.className.split(' ').indexOf(classname)>=0) return true;
    return hasSomeParentTheClass(element.parentNode, classname);
}
Tectonics answered 11/5, 2017 at 11:18 Comment(1)
there isn't a need for a separate conditional, you can do it by doing an explicit null check to make sure you get a boolean: element.parentNode != null && hasSomeParentTheClass(..)Moneychanger
U
3

I believe if( $('#the-element').parents('.the-class').length ) to be more efficient, but perhaps not as human-readable; which, with querySelector in the picture, could be replaced with the following method:

function hasParent(element, parentSelector) {
    var potentialParents = document.querySelectorAll(parentSelector);
    for(i in potentialParents) if(potentialParents[i].contains(element))
        return potentialParents[i];
    return false;
}

That'd give you the ability to do:

var elm = document.getElementById('the-element');
if(hasParent(elm, '.the-class')) return true;
Utricle answered 23/8, 2014 at 11:11 Comment(4)
I had the same problem and I came up with this solution, so I thought I'd post it here for others facing this issue.Utricle
why is $ more efficent?Dyspnea
@Dyspnea I was not suggesting that jQuery is more efficient than single purposed vanilla code, if that's what you're asking. I was implying, I think, that using a selector with parents may result in one less loop, compared to OP's jq line. Bad phrasing, huh?Utricle
This is a great method. I only suggest running a .hasOwnProperty() check.Climb
C
3

Try the closest() function - For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree. Refer to the Official Docs here.

Corenecoreopsis answered 12/2, 2020 at 4:42 Comment(0)
M
2

Another alternative for some those who like this style for modern/polyfilled browsers.

const hasClass = (element, className) => {
    return element.classList.contains(className);
};

const hasParent = (element, className) => {
    if (!element.parentNode) {
        return false;
    }

    if (hasClass(element, className)) {
        return true;
    }

    return hasParent(element.parentNode, className)
};

Working demo:

const hasClass = (element, className) => {
    return element.classList.contains(className);
};

const hasParent = (element, className) => {
    if (!element.parentNode) {
        return false;
    }

    if (hasClass(element, className)) {
        return true;
    }

    return hasParent(element.parentNode, className)
};


/* Demo Code, can ignore */
const child = document.getElementById('child');
const orphan = document.getElementById('orphan');
const output = document.getElementById('output');

const log = `child has parent? ${hasParent(child, 'list')}
orphan has parent? ${hasParent(orphan, 'list')}
`

output.innerText = log;
#output {
  margin-top: 50px;
  background: black;
  color: red;
  padding: 20px;
}
<div>
  <ul class="list">
    <li>
      <a id="child" href="#">i have a parent</a> 
    </li>
  </ul>
</div>

<div>
  <ul>
    <li>
      <a id="orphan" href="#">im an orphan</a> 
    </li>
  </ul>
</div>

<div id="output"></div>
Memberg answered 11/10, 2019 at 11:37 Comment(0)
K
1

My example for Vanilla JS, it's use a vanilla equivalent of parents() from jQuery

var htmlElement = <htmlElement>,
    parents = [],
    classExist;
while (htmlElement = htmlElement.parentNode.closest(<parentSelector>)) {
    parents.push(htmlElement);
}
classExist = (parents > 0);

So your selector just to be a .className

And just check if parent is > 0

Karinekariotta answered 24/11, 2015 at 22:38 Comment(0)
I
0

My solution: (it goes up to html element)

// e = element
// name = name of class
// Returns element or undefined

function classParent( e, name )
{
    if( e.parentElement )
        if( e.parentElement.classList.contains( name ))
            return e.parentElement;
        else
            return classParent( e.parentElement, name )
}
Ingrained answered 31/5, 2023 at 15:0 Comment(0)
P
-1

Because ID must be unique on document context, you could just use instead:

return !!document.querySelector('.the-class #the-element');

If you want to include element itself, you can use:

return !!document.querySelector('.the-class #the-element, #the-element.the-class');
Platt answered 24/10, 2015 at 9:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.