DOM / pure JavaScript solution to jQuery.closest() implementation?

Here's the markup i'm trying to query. So given the markup:

<table class="non-unique-identifier table">
<tr><td><div id="unique-identifier"></div></td></tr>
</table>

I'm querying for #unique-identifier:

var myDiv = document.getElementById('#unique-identifier');

I'm then trying to select the table. The issue is that i want to make the code not brittle so i don't need to do this:

var myDiv = document.getElementById('#unique-identifier'),
    myTable = myDiv.parentNode.parentNode.parentNode.parentNode;

My question

Is there currently a DOM implementation of the jQuery equivalent of $().closest() ? A closest implementation that is efficient without nested for loops would be preferred.

Limitations

I'm required to not use jQuery or sizzle for this particular issue or introduce any new libraries. The code is quite old as well. Thus, that is the reason for such limitations and the existence of <tables>.

Answers:

Answer

You can't do this without a loop :

function closest (el, predicate) {
  do if (predicate(el)) return el;
  while (el = el && el.parentNode);
}

Well, actually you can, using recursivity (a disguised loop) :

function closest(el, predicate) {
  return predicate(el) ? el : (
     el && closest(el.parentNode, predicate)
  );
}

A demo (using Sizzle for the DOM queries) :

// s = selectors
// n = number of selectors
// get closest s[i+1] from s[i]
// where 0 <= i < n and i % 2 = 0

function main (s) {
  var i, el, from;
  var n = s.length;
  for (i = 0; i < n; i += 2) {
    from = Sizzle(s[i])[0];
    el = closest(from, function (el) {
      return !!el && el !== document && (
        Sizzle.matchesSelector(el, s[i + 1])
      );
    });
    console.log(el);
  }
}

function closest (el, predicate) {
  do if (predicate(el)) return el;
  while (el = el && el.parentNode);
}

main([
  "#winner" , "b", 
  "#winner" , "p", 
  "#winner" , "div", 
  "#winner" , "div:not(#trump)", 
  "#winner" , "#clinton",
  "#looser" , "html"
]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/sizzle/1.10.18/sizzle.min.js"></script>

<div id="main">
  <div id="trump">
    <p>Donald <b id="winner">Trump</b></p>
  </div>
  <div id="clinton">
    <p>Hillary <b>Clinton</b></p>
  </div>
</div>

Answer

To add an updated answer, there is now Element.closest(<query_selector>) available.

https://developer.mozilla.org/en-US/docs/Web/API/Element/closest

This isn't supported on IE, but that mozilla doc page includes code for a polyfill for IE8 and IE9+.

Answer

function closestById(el, id) {
  while (el.id != id) {
    el = el.parentNode;
    if (!el) {
      return null;
    }
  }
  return el;
}

// Use it like:

yourTarget = closestById(document.getElementById('unique-identifier'),'targetId')
alert(yourTarget.id);
<div id="targetId">
  Finish
  <div>
    <div id="unique-identifier">
      Start
    </div>
  </div>
</div>

This searches upwards, until a certain ID is found. You can also alter the code to find certain classes.

Answer

Concise and quick (tested with Benchmark.js) way to search for closest element by any css selector:

var ep = Element.prototype;
ep.matches = ep.matches || ep.webkitMatchesSelector || ep.msMatchesSelector || ep.mozMatchesSelector;

function getClosest( elem, selector ) {
    while (elem !== document.body) {
        elem = elem.parentElement;
        if (elem.matches(selector)) return elem;
    }
}

Supports IE9+ and the rest of the browsers you can expect to care about.

Answer

Alternative is a recursive function. This is slightly different to closest as i searches the children, I'm not sure if closest does.

function closest(elem) {
    if( elem.className.indexOf("non-unique-identifier") ) {
        return elem;
    } 

    var parent = elem.parentNode;

    for(var i = 0; i< parent.children.length; i++ ) {
        if( parent.children[i].className.indexOf("non-unique-identifier")!=-1)  {
            return parent.children[i];
        }
    }

    return closest(parent);
}



var elem = document.getElementById('unique-identifier');

var cl = closest(elem);

console.log(cl);

Non children searching example (more like closest):

function closest(elem) {
    if( elem.className.indexOf("non-unique-identifier") ) {
        return elem;
    } 

    var parent = elem.parentNode;

    if( parent.className.indexOf("non-unique-identifier")!=-1) {
        return parent;
    }    

    return closest(parent);
}



var elem = document.getElementById('unique-identifier');

var cl = closest(elem);

console.log(cl);
Answer
<div class="item">
  <div class="itemed">
    <p>Hello <b class="itemed" id="world">World</b></p>
  </div>
</div>

function closest(el, classname) {
   if(el.parentNode){
        if(el.parentNode.className.includes(classname)){
        return el.parentNode;
      }
      else{
        return closest(el.parentNode, classname);
      }
   }
   else{
    return false;
   }
}

var world = document.getElementById('world');
var closest = closest(world, 'item');
console.log(closest);
Answer

I wrote this simple function in one of my TypeScript projects so i used querySelector on parentNode so you can pass to function class, id, tag name etc

findParentNode(el, selector:string):Element | null {
  let found = null;
  let child = el;
  let childSelector = guessSelector(child);

  while(child !== document && found === null) {
    child = child.parentNode;
    childSelector = guessSelector(child);
    found = childSelector ? child.parentNode.querySelector(`${childSelector} > ${selector}`) : null;
  }

  return found;

  function guessSelector(child:any):string {
    childSelector = child.className ? `.${child.className.replace(' ', '.')}` : null;

    if (typeof child.getAttribute === 'function') {
      childSelector = !childSelector ?
        (child.getAttribute('id') ? `#${child.getAttribute('id')}` : null) : childSelector;

      childSelector = !childSelector ?
        child.tagName.toLowerCase() : childSelector;
    }

    return childSelector;
  }
}

Example:

If you want to find closest parent of target which has .param-input class you can do this like this:

document.body.addEventListener('click', (e) => {
  console.log(findParentNode(e.target, '.param-input'));
});
Answer

In this case, use JavaScript's closest()

Exemple:

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

var r1 = el.closest("#div-02");  
// returns the element with the id=div-02

var r2 = el.closest("div div");  
// returns the closest ancestor which is a div in div, here is div-03 itself

var r3 = el.closest("article > div");  
// returns the closest ancestor which is a div and has a parent article, here is div-01

var r4 = el.closest(":not(div)");
// returns the closest ancestor which is not a div, here is the outmost article
<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>

You can find more information, on MDN Web Docs by clicking here

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.