4

I have HTML like:

<div id="hooray" class="section">
   <div>
      <div>
        <div id="click">click</div>
      </div>
   </div>
 </div>

When I click the div with class click, I want to move up the DOM until reaching the nearest section and grab the id.

With JQuery, I would use something like $().closest('div.section'), but how do I do that with pure Javascript without hardcoding in every parentNode?

var node = document.getElementById('click').parentNode.parentNode....etc
leonheess
  • 16,068
  • 14
  • 77
  • 112
cusejuice
  • 10,285
  • 26
  • 90
  • 145
  • why not direct access the `id` `hooray` when you click on the `div` with class `click`? – jhyap May 29 '14 at 04:07
  • @AmitJoki - while that post is related to this question, it doesn't really contain a full working answer to this question and, as such, I don't think it qualifies as a duplicate or helps anyone to mark this as a duplicate of that one. – jfriend00 May 29 '14 at 04:17
  • Consider accepting an answer if your problem has been solved (: – leonheess Aug 02 '21 at 20:41

3 Answers3

7

You can just use Element.closest():

let closestElement = document.getElementById('click').closest('.section');

Here is a snippet to show it in action:

console.log(document.getElementById('click').closest('.section').id);
<div id="hooray" class="section">
   <div>
      <div>
        <div id="click">click</div>
      </div>
   </div>
 </div>
leonheess
  • 16,068
  • 14
  • 77
  • 112
3

You can just walk up the parent chain looking for an ancestor with the proper class. This version of the code allows there to be other class names on the object of interest so the code can be used more flexibly:

function hasClass(elem, cls) {
    var str = " " + elem.className + " ";
    var testCls = " " + cls + " ";
    return(str.indexOf(testCls) != -1) ;
}

function closest(el, cls) {
    while (el  && el !== document) {
        if (hasClass(el, cls)) return el;
        el = el.parentNode;
    }
    return null;
}

So, from your click handler, you can just do:

var id;
var target = closest(this, "section");
if (target) {
    id = target.id;
    // do whatever you want with the id
}

Working demo: http://jsfiddle.net/jfriend00/6V7HG/


FYI, a version of this code that can take any arbitrary selector is much, much more complicated as you essentially have to create a CSS selector parser and then evaluate if a given object matches all the requirements in that selector. That is probably way more work than you would want to do for this particular operation.

Usually, you're not looking up a chain for an id because you can just get the element with that id directly so I've offered a version that works with a class name. Other versions (like one that matched an attribute) could also be written.

This version is a little more generic than your particular question (returning the target element rather than the target id) which allows you to use it in more circumstances. If you really just want a function that returns the target id, that can be done like this:

function closest(el, cls) {
    while (el  && el !== document) {
        if (hasClass(el, cls)) return el.id;
        el = el.parentNode;
    }
    return null;
}
jfriend00
  • 683,504
  • 96
  • 985
  • 979
1

There are n number of ways to do this, you may try this one:

document.getElementById('click').onclick = function(e) {
    e = e || window.event;
    var el = e.target || e.srcElement;
    if (el = el.parentNode) do { //its an inverse loop
        var cls = el.className;
        if (cls) {
            cls = cls.split(" ");
            if (-1 !== cls.indexOf("section")) {
                console.log("here it is: ");
                console.log(el);
                break;
            }
        }
    } while (el = el.parentNode); //its assignment
    if (!el) {
        console.log('not found !');
    }
    console.log('end..');
};