nkuhta
nkuhta

Reputation: 11128

How to get parent element by selector

Example:

<div someAttr="parentDiv. We need to get it from child.">
    <table>
        ...
        <td> <div id="myDiv"></div> </td>
        ...
    </table>
</div>

I want to get the parent by some selector from the inner div element (the one with the myDiv class).

How do I achieve that with plain JavaScript, without jQuery?

Something like:

var div = document.getElementById('myDiv');
div.someParentFindMethod('some selector');

Upvotes: 153

Views: 285433

Answers (14)

Hugo
Hugo

Reputation: 138

As of "baseline 2023" there's a new :has CSS pseudo-class which should do the trick:

https://developer.mozilla.org/en-US/docs/Web/CSS/:has

Given the structure in the original question:

<div someAttr="parentDiv. We need to get it from child.">
    <table>
        ...
        <td> <div id="myDiv"></div> </td>
        ...
    </table>
</div>

The following should work (if I didn't make a mistake - I was experimenting/testing with a different page):

  • document.querySelector('div[someAttr]:has(#myDiv)') - should select the top-level div[someAttr].
  • document.querySelector('[someAttr]:has(>#myDiv)') - should fail to select anything, because it requires the id="myDiv" element to be an immediate child of an element with the someAttr attribute.
  • document.querySelector(':has(>#myDiv)') - selects the immediate parent of the div#myDiv element.

Upvotes: 0

KarlsMaranjs
KarlsMaranjs

Reputation: 535

This is a simple way using recursion to get the parent with a specific class. You can modify it to switch depending on the selector, but the principle is the same:

function findParentByClass(child, parentClass) {
    let parent = child.parentElement;

    while(!(parent.classList.contains(parentClass))){
        parent = findParentByClass(child.parentElement, parentClass)
    }

    return parent;
}

Upvotes: 3

David V McKay
David V McKay

Reputation: 122

I believe this is best solved with a recursive function. The following one-liner function uses a ternary expression and a coalescing operator to recursively search each parent in a chain of direct ancestors until the first parentElement is found which satisfies the given selector. If none are found by the time the recursive probing reaches the document, result is undefined.

/** finds the first direct ancestor matching the provided selector. */
    const probeSelector = (child, selector) =>
        !selector || !child || !child.parentElement             ? undefined
        : (child.parentElement.querySelectorAll(selector).values().find(x => x === child)
        ?? probeSelector(child.parentElement, selector));

Upvotes: 0

Gass
Gass

Reputation: 9344

By using querySelector() and closest() methods is possible to get the parent element.

  • querySelector() returns the first element that match a specified CSS selector(s) in the document.

  • closest() searches up the DOM tree for the closest element which matches a specified CSS selector.

Usage example

const element = document.querySelector('td')

console.log(element.closest('div'))
<div>
  <table>
    <tr>
      <td></td>
    </tr>
  </table>
</div>

In case of needing to select more than one element, querySelectorAll() is a good fit.

  • querySelectorAll() returns all elements in the document that match a specified CSS selector(s), as a static NodeList object.

To select the desired element, is necessary to specify it inside [] so, for example for the second element would be: element[1]

In the following example closest() method is used to get the parent <tr> element of an specific selected element.

const el = document.querySelectorAll('td')

console.log(el[1].closest('tr'))
<div>
  <table>
    <tr id="1">
      <td> First text </td>
    </tr>
    <tr id="2">
      <td> Second text </td>
    </tr>
  </table>
</div>

Upvotes: 17

user7212232
user7212232

Reputation: 115

  /* --------------------------------------------------
    get a parent element by its id or classname

    [child] - the element were starting with
    [idOrClassName] - id or classname of parent element
                      were looking for
    ------------------------------------------------- */
 function getParent(child, idOrClassName = ""){

    while(child.tagName.toLowerCase() != "body"){
  
       // get the next parent up
      child = child.parentNode;
 
       // we reached body tag which means parent element with
       // class or id of [idOrClassName] not found so exit
      if(child.tagName.toLowerCase() == "body"){
         return false;
      }

       // if the id of this element = [idOrClassName] or .classList
       // of [child] contains [idOrClassName], weve found our element
      if((child.id == idOrClassName) || (child.classList.contains(idOrClassName))){
         return child;
      }
   }

 }

Upvotes: 0

var base_element = document.getElementById('__EXAMPLE_ELEMENT__');
for( var found_parent=base_element, i=100; found_parent.parentNode && !(found_parent=found_parent.parentNode).classList.contains('__CLASS_NAME__') && i>0; i-- );
console.log( found_parent );

Upvotes: -4

Joel Teply
Joel Teply

Reputation: 3296

I thought I would provide a much more robust example, also in typescript, but it would be easy to convert to pure javascript. This function will query parents using either the ID like so "#my-element" or the class ".my-class" and unlike some of these answers will handle multiple classes. I found I named some similarly and so the examples above were finding the wrong things.

function queryParentElement(el:HTMLElement | null, selector:string) {
    let isIDSelector = selector.indexOf("#") === 0
    if (selector.indexOf('.') === 0 || selector.indexOf('#') === 0) {
        selector = selector.slice(1)
    }
    while (el) {
        if (isIDSelector) {
            if (el.id === selector) {
                return el
            }
        }
        else if (el.classList.contains(selector)) {
            return el;
        }
        el = el.parentElement;
    }
    return null;
}

To select by class name:

let elementByClassName = queryParentElement(someElement,".my-class")

To select by ID:

let elementByID = queryParentElement(someElement,"#my-element")

Upvotes: 2

George Kargakis
George Kargakis

Reputation: 5556

You may use closest() in modern browsers:

var div = document.querySelector('div#myDiv');
div.closest('div[someAtrr]');

Use object detection to supply a polyfill or alternative method for backwards compatability with IE.

Upvotes: 398

Cully
Cully

Reputation: 6955

Here's a recursive solution:

function closest(el, selector, stopSelector) {
  if(!el || !el.parentElement) return null
  else if(stopSelector && el.parentElement.matches(stopSelector)) return null
  else if(el.parentElement.matches(selector)) return el.parentElement
  else return closest(el.parentElement, selector, stopSelector)
}

Upvotes: 2

Trevor Nestman
Trevor Nestman

Reputation: 2526

Using leech's answer with indexOf (to support IE)

This is using what leech talked about, but making it work for IE (IE doesn't support matches):

function closest(el, selector, stopSelector) {
  var retval = null;
  while (el) {
    if (el.className.indexOf(selector) > -1) {
      retval = el;
      break
    } else if (stopSelector && el.className.indexOf(stopSelector) > -1) {
      break
    }
    el = el.parentElement;
  }
  return retval;
}

It's not perfect, but it works if the selector is unique enough so it won't accidentally match the incorrect element.

Upvotes: 2

Sergio Belevskij
Sergio Belevskij

Reputation: 2947

simple example of a function parent_by_selector which return a parent or null (no selector matches):

function parent_by_selector(node, selector, stop_selector = 'body') {
  var parent = node.parentNode;
  while (true) {
    if (parent.matches(stop_selector)) break;
    if (parent.matches(selector)) break;
    parent = parent.parentNode; // get upper parent and check again
  }
  if (parent.matches(stop_selector)) parent = null; // when parent is a tag 'body' -> parent not found
  return parent;
};

Upvotes: 0

leech
leech

Reputation: 8421

Finds the closest parent (or the element itself) that matches the given selector. Also included is a selector to stop searching, in case you know a common ancestor that you should stop searching at.

function closest(el, selector, stopSelector) {
  var retval = null;
  while (el) {
    if (el.matches(selector)) {
      retval = el;
      break
    } else if (stopSelector && el.matches(stopSelector)) {
      break
    }
    el = el.parentElement;
  }
  return retval;
}

Upvotes: 45

Chris
Chris

Reputation: 26878

Here's the most basic version:

function collectionHas(a, b) { //helper function (see below)
    for(var i = 0, len = a.length; i < len; i ++) {
        if(a[i] == b) return true;
    }
    return false;
}
function findParentBySelector(elm, selector) {
    var all = document.querySelectorAll(selector);
    var cur = elm.parentNode;
    while(cur && !collectionHas(all, cur)) { //keep going up until you find a match
        cur = cur.parentNode; //go up
    }
    return cur; //will return null if not found
}

var yourElm = document.getElementById("yourElm"); //div in your original code
var selector = ".yes";
var parent = findParentBySelector(yourElm, selector);

Upvotes: 45

ameya rote
ameya rote

Reputation: 1114

Here is simple way to access parent id

document.getElementById("child1").parentNode;

will do the magic for you to access the parent div.

<html>
<head>
</head>
<body id="body">
<script>
function alertAncestorsUntilID() {
var a = document.getElementById("child").parentNode;
alert(a.id);
}
</script>
<div id="master">
Master
<div id="child">Child</div>
</div>
<script>
alertAncestorsUntilID();
</script>
</body>
</html>

Upvotes: -3

Related Questions