Finding closest element without jQuery

Solution 1:

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

Solution 2:

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;
}

Solution 3:

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');

Solution 4:

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");

Solution 5:

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);