A partial archive of discourse.wicg.io as of Saturday February 24, 2024.

Element.ancestors, a querySelectorAll for ancestors

stuartpb
2015-08-13

(This post has been edited since its original proposal to remove ancestorQuerySelector, which was essentially the same as Element.parentElement.closest, and rename ancestorQuerySelectorAll to simply ancestors.)

Pretty self-explanatory: where Element.querySelector returns the first matching child element, Element.querySelectorAll returns all matching child elements, and Element.closest returns the first ancestor matching a given selector query, Element.ancestors would return a NodeList (like querySelectorAll) of all ancestors matching a given selector query (similar to jQuery’s parents() function).

Implementation / prollyfill:

Element.ancestors = function ancestors(selector) {
  // this would use a (non-live) NodeList, but there doesn't appear to be an API for that
  var matchingAncestors = [];

  var parent = this.parentElement;
  while (parent) {
    if (parent.matches(selector)) matchingAncestors.push(parent);
    parent = parent.parentElement;
  }
  return parent;
}
MT
2015-08-13

Element.closest()?

Edwin_Reynoso
2015-08-13

@stuartpb as @MT said Element#closest() returns the single Element parent, you would need to think of a method that returns a NodeList or the future Elements collection, which its name is close to closest(). I don’t think we’ll want to have closest and ancestorQuerySelectorAll.

Only thing I can think of is closestAll but don’t really like it.

stuartpb
2015-08-13

Ah yes, I’d misinterpreted the name - it sounds like something that’s going to select siblings and/or children (and it didn’t turn up when I Googled “ancestor query selector”). Reading the docs now, I see that it is only ancestors, so yes, this is what I was proposing with ancestorQuerySelector.

As @Edwin_Reynoso says, closest doesn’t tackle the ancestorQuerySelectorAll case, which could maybe be added as listClosest.

Edwin_Reynoso
2015-08-13

parents() could work since it has the letter s. The only other thing with the string “parent” is parentElement and parentNode since parents is a method I think it’ll make sense?

But still doesn’t makes sense kind of weird, so closestParents()??? or closestAncestors() since closest is not really a parent of the element its being called on.

tabatkins
2015-08-13

If you’re returning all of the parents that match a selector, “closest” doesn’t make sense any more. Just ancestors() would do.

Edwin_Reynoso
2015-08-13

TBH I’m not sure when is this ever wanted. Like when do you ever want to query multiple parents, which is probably why only closest() was created and created to return 1 Element

stuartpb
2015-08-13

Well, there’s also furthest(), which would want to address the highest parent matching a selector - and sometimes you do want to affect multiple levels of parent (ie. changing background colors in a zebra-striping fashion).

Edwin_Reynoso
2015-08-13

What do you mean by your example??

stuartpb
2015-08-14
var levels = target.ancestors('.differentiated-level');

var d = 360 / (1+Math.PHI);

for (var i = 0; i < ancestors.length; i++) {
  levels[i].style.backgroundColor = 'hsl(' + (i*d % 360) + ',100%, 50%)';
}

This is more like a Fruit Stripe zebra, but it’s a demo of the general behavior I was talking about.

Now, of course, the issue with that example is that you could just as easily do repeated calls to .closest with an increment of i in a while (closest) loop - but for other cases (eg. counting how many levels a node is deep in a selector), .ancestors() is significantly easier (although the savings at this point are more bikeshedding, and I honestly don’t feel the need to argue in favor of ancestors() so hard when closest() already exists).

stuartpb
2015-08-18

I edited the OP to reflect the above discussion.

stuartpb
2015-08-18

Also, I just noticed: .closest can select the element itself - I suppose the element.ancestorQuerySelector I was proposing would be equivalent to element.parentElement.closest.

Edwin_Reynoso
2015-08-19

I think you mean parent.parentElement in your ancestor function.