95

I am trying to find the closest element with a specific tag name without jquery. When I click on a <th> I want to get access to the <tbody> for that table. Suggestions? I read about offset but didn't really understand it too much. Should I just use:

Assume th is already set to clicked th element

th.offsetParent.getElementsByTagName('tbody')[0]
BenMorel
  • 34,448
  • 50
  • 182
  • 322
hunterc
  • 1,957
  • 2
  • 15
  • 18

13 Answers13

137

Very simple:

el.closest('tbody')

Supported on all browsers except IE.
UPDATE: Edge now support it as well.

No need for jQuery. More over, replacing jQuery's $(this).closest('tbody') with $(this.closest('tbody')) will increase performance, significantly when the element is not found.

Polyfill for IE:

if (!Element.prototype.matches) Element.prototype.matches = Element.prototype.msMatchesSelector;
if (!Element.prototype.closest) Element.prototype.closest = function (selector) {
    var el = this;
    while (el) {
        if (el.matches(selector)) {
            return el;
        }
        el = el.parentElement;
    }
};

Note that there's no return when the element was not found, effectively returning undefined when the closest element was not found.

For more details see: https://developer.mozilla.org/en-US/docs/Web/API/Element/closest

oriadam
  • 7,747
  • 2
  • 50
  • 48
71

Little (very) late to the party, but nonetheless. This should do the trick:

function closest(el, selector) {
    var matchesFn;

    // find vendor prefix
    ['matches','webkitMatchesSelector','mozMatchesSelector','msMatchesSelector','oMatchesSelector'].some(function(fn) {
        if (typeof document.body[fn] == 'function') {
            matchesFn = fn;
            return true;
        }
        return false;
    })

    var parent;

    // traverse parents
    while (el) {
        parent = el.parentElement;
        if (parent && parent[matchesFn](selector)) {
            return parent;
        }
        el = parent;
    }

    return null;
}
Shashank Agrawal
  • 25,161
  • 11
  • 89
  • 121
Ales
  • 906
  • 8
  • 9
  • 2
    Great answer which works a treat. MDN also has a polyfill for element.closest(). Chrome includes element.matches() in the current release, so no prefix is needed. I've just added this to a lib I'm using in an app I'm developing, Clibu. – nevf Jan 31 '15 at 03:35
  • 2
    I've change the code so that it also tests element `el` which jQuery.closest() and also Element.closest() does. `for( var parent = el ; parent !== null && !parent[matchesFn](selector) ; parent = el.parentElement ){ el = parent; } return parent;` – nevf Jan 31 '15 at 07:07
  • 1
    This code is fine, but it's missing a semicolon and also defining `parent` in global scope (!) – Steven Lu Jun 17 '15 at 15:31
  • 1
    Might be worth mentioning: https://developer.mozilla.org/en-US/docs/Web/API/Element/closest the native method for closest, though little low on support: Chrome41, FF35, IE-nope, Opera28, Safari9 – Michiel D Dec 03 '15 at 05:57
  • Just a note on this, you may want to do `el.parentNode` or this will break when traversing an SVG in IE. – smcka Nov 11 '16 at 19:46
  • speed of execution will be the same as with `Element.closest()?` – user25 Nov 19 '16 at 21:24
  • This answer is now outdated. See @oriadam 's answer below – Jan Sverre Nov 25 '17 at 11:13
  • @nevf, The above solution didn't worked for me, but modifying the function with your suggestion did the trick. Cheers! – VikrantMore Nov 26 '19 at 10:00
22

Here's how you get the closest element by tag name without jQuery:

function getClosest(el, tag) {
  // this is necessary since nodeName is always in upper case
  tag = tag.toUpperCase();
  do {
    if (el.nodeName === tag) {
      // tag name is found! let's return it. :)
      return el;
    }
  } while (el = el.parentNode);

  // not found :(
  return null;
}

getClosest(th, 'tbody');
Joon
  • 9,346
  • 8
  • 48
  • 75
  • 2
    I don't believe this would work. It only checks up the DOM tree. th->thead->table never considers siblings – hunterc Sep 06 '13 at 18:32
  • 2
    You should've state that specific case in your question. – Joon Sep 06 '13 at 19:46
  • Your answer doesn't find the closest element node though. It finds the closest parent node in the chain. Just doing a recursive call checking if current nodeName matches return current else return search(parentNode) accomplishes the same task – hunterc Sep 06 '13 at 19:59
  • This answer is just bogus. –  Sep 06 '13 at 20:27
  • @Hunter If you think that this only searches parentNode, you should know better before you speak. There is a reason I used do before while. – Joon Sep 07 '13 at 00:37
  • 2
    Look. This function traverses parentNodes to find the closest parent (even by extension) that uses the provided tag. This won't go "down" the document tree, only "up." The average user would see the "closest" node most likely as the closest sibling. He just doesn't know the terminology he needed. –  Sep 07 '13 at 01:52
  • 1
    In fairness to @Jhawins your definition of closest is what jQuery terms closest. This is not a jQuery question. I can't attest to why jQuery decided closest meant closest ancestor, but a more reasonable definition would be closest element whether that be a parent, a previous sibling, a next sibling etc. Whatever is found "closest" to the target element. – ryandlf Feb 14 '14 at 07:39
  • 1
    @ryandlf But what if you had both a parent and a sibling at the same "distance"? jQuery's definition is clear in that it will return one match at most. – mtone Feb 17 '14 at 03:45
  • I'm fairly new to js, so please forgive my ignorance. Can you access and/or manipulate tag after el has been returned? If not, how would you do so? – SwankyLegg Jun 04 '14 at 18:54
  • is it possible to do that with input[type='whatever']? – www139 Mar 26 '15 at 12:26
9

There exists a standardised function to do this: Element.closest. Most browsers except IE11 support it (details by caniuse.com). The MDN docs also include a polyfill in case you have to target older browsers.

To find the closest tbody parent given a th you could do:

th.closest('tbody');

In case you want to write the function yourself - here is what I came up with:

function findClosestParent (startElement, fn) {
  var parent = startElement.parentElement;
  if (!parent) return undefined;
  return fn(parent) ? parent : findClosestParent(parent, fn);
}

To find the closest parent by tag name you could use it like this:

findClosestParent(x, element => return element.tagName === "SECTION");
david1995
  • 165
  • 1
  • 7
6
function closest(el, sel) {
    if (el != null)
        return el.matches(sel) ? el 
            : (el.querySelector(sel) 
                || closest(el.parentNode, sel));
}

This solution uses some of the more recent features of the HTML 5 spec, and using this on older/incompatible browsers (read: Internet Explorer) will require a polyfill.

Element.prototype.matches = (Element.prototype.matches || Element.prototype.mozMatchesSelector 
    || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector 
    || Element.prototype.webkitMatchesSelector || Element.prototype.webkitMatchesSelector);
Balachandran
  • 9,567
  • 1
  • 16
  • 26
Matthew James Davis
  • 12,134
  • 7
  • 61
  • 90
3

Here's the simple function I am using:-

function closest(el, selector) {
    var matches = el.webkitMatchesSelector ? 'webkitMatchesSelector' : (el.msMatchesSelector ? 'msMatchesSelector' : 'matches');

    while (el.parentElement) {
        if (el[matches](selector)) return el;

        el = el.parentElement;
    }

    return null;
}
Samuel Katz
  • 24,066
  • 8
  • 71
  • 57
3

To extend @SalmanPK answer

it will allow to use node as selector, useful when you working with events like mouseover.

function closest(el, selector) {
    if (typeof selector === 'string') {
        matches = el.webkitMatchesSelector ? 'webkitMatchesSelector' : (el.msMatchesSelector ? 'msMatchesSelector' : 'matches');
        while (el.parentElement) {
            if (el[matches](selector)) {
                return el
            };
            el = el.parentElement;
        }
    } else {
        while (el.parentElement) {
            if (el === selector) {
                return el
            };
            el = el.parentElement;
        }
    }

    return null;
}
Sebastián Palma
  • 32,692
  • 6
  • 40
  • 59
zb'
  • 8,071
  • 4
  • 41
  • 68
3

Summary:

For finding a particular ancestor we can use:

Element.closest();

This function takes a CSS selector string as an argument. it then returns the closest ancestor of the current element (or the element itself) which matches the CSS selector which was passed in the arguments. If there is no ancestor it will return null.

Example:

const child = document.querySelector('.child');
// select the child

console.dir(child.closest('.parent').className);
// check if there is any ancestor called parent
<div class="parent">
  <div></div>
  <div>
    <div></div>
    <div class="child"></div>
  </div>
</div>
Willem van der Veen
  • 33,665
  • 16
  • 190
  • 155
1

Get closest DOM element up the tree that contains a class, ID, data attribute, or tag. Includes the element itself. Supported back to IE6.

var getClosest = function (elem, selector) {

    var firstChar = selector.charAt(0);

    // Get closest match
    for ( ; elem && elem !== document; elem = elem.parentNode ) {

        // If selector is a class
        if ( firstChar === '.' ) {
            if ( elem.classList.contains( selector.substr(1) ) ) {
                return elem;
            }
        }

        // If selector is an ID
        if ( firstChar === '#' ) {
            if ( elem.id === selector.substr(1) ) {
                return elem;
            }
        } 

        // If selector is a data attribute
        if ( firstChar === '[' ) {
            if ( elem.hasAttribute( selector.substr(1, selector.length - 2) ) ) {
                return elem;
            }
        }

        // If selector is a tag
        if ( elem.tagName.toLowerCase() === selector ) {
            return elem;
        }

    }

    return false;

};

var elem = document.querySelector('#some-element');
var closest = getClosest(elem, '.some-class');
var closestLink = getClosest(elem, 'a');
var closestExcludingElement = getClosest(elem.parentNode, '.some-class');
Chris Ferdinandi
  • 1,892
  • 4
  • 26
  • 34
1

Find nearest Elements childNodes.

closest:function(el, selector,userMatchFn) {
var matchesFn;

// find vendor prefix
['matches','webkitMatchesSelector','mozMatchesSelector','msMatchesSelector','oMatchesSelector'].some(function(fn) {
    if (typeof document.body[fn] == 'function') {
        matchesFn = fn;
        return true;
    }
    return false;
});
function findInChilds(el){
    if(!el) return false;
    if(el && el[matchesFn] && el[matchesFn](selector)

    && userMatchFn(el) ) return [el];
    var resultAsArr=[];
    if(el.childNodes && el.childNodes.length){
        for(var i=0;i< el.childNodes.length;i++)
        {
             var child=el.childNodes[i];
             var resultForChild=findInChilds(child);
            if(resultForChild instanceof Array){
                for(var j=0;j<resultForChild.length;j++)
                {
                    resultAsArr.push(resultForChild[j]);
                }
            } 
        }

    }
    return resultAsArr.length?resultAsArr: false;
}

var parent;
if(!userMatchFn || arguments.length==2) userMatchFn=function(){return true;}
while (el) {
    parent = el.parentElement;
    result=findInChilds(parent);
    if (result)     return result;

    el = parent;
}

return null;

}

MajidTaheri
  • 3,813
  • 6
  • 28
  • 46
0

Here.

function findNearest(el, tag) {
    while( el && el.tagName && el.tagName !== tag.toUpperCase()) {
        el = el.nextSibling;     
    } return el;
} 

Only finds siblings further down the tree. Use previousSibling to go the other way Or use variables to traverse both ways and return whichever is found first. You get the general idea, but if you want to traverse through parentNodes or children if a sibling doesn't match you may as-well use jQuery. At that point it's easily worth it.

RonnyKnoxville
  • 6,166
  • 10
  • 46
  • 75
  • Slightly modified your suggestion to support deep-child traversal: `function findNearest(el, tag) { while( el && el.tagName && el.tagName !== tag.toUpperCase()) { el = findNearest(el.firstElementChild, tag) || el.nextElementSibling; } return el; } ` – Ernstjan Freriks Dec 10 '21 at 09:19
0

A little late to the party, but as I was passing by and just answer back a very similar question, I drop here my solution - we can say it's the JQuery closest() approach, but in plain good ol' JavaScript.

It doesn't need any pollyfills and it's older browsers, and IE (:-) ) friendly: https://stackoverflow.com/a/48726873/2816279

Pedro Ferreira
  • 493
  • 7
  • 14
-5

I think The easiest code to catch with jquery closest:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script>
    $(document).ready(function () {
        $(".add").on("click", function () {
            var v = $(this).closest(".division").find("input[name='roll']").val();
            alert(v);
        });
    });
</script>
<?php

for ($i = 1; $i <= 5; $i++) {
    echo'<div class = "division">'
        . '<form method="POST" action="">'
        . '<p><input type="number" name="roll" placeholder="Enter Roll"></p>'
        . '<p><input type="button" class="add" name = "submit" value = "Click"></p>'
        . '</form></div>';
}
?>

Thanks much.

AL MaMun
  • 65
  • 7