I have a code with lots of submenus that share the same class name.
Here's a structure:
.menu .sub-menu .sub-menu .sub-menu .sub-menu .sub-menu .sub-menu .elem .elem .sub-menu
Note that .sub-menu may be infinite levels deep.
So how do I achieve this: when .elem is clicked, I want to travers the DOM upwards until the top-most .sub-menu is reached and apply a style to it. I am aware of .closest[] and .parent[] and .find[], but I have no idea if jQuery has such feature such as .topMost[selector]?
The only way I can think of is maybe running a loop and going through .closest['.sub-menu'] of the new element until its length is zero [there are no more parents with this class, so it must be the top-most]. However, I think there should be a more practical approach to this.
Here’s a boil-down on the jQuery’s .closest[]
method and 2 native JavaScript alternatives.
jQuery’s .closest[]
function is great! Here’s what it does:
For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree.
Essentially, the closest function has two things:
- A starting point — start from here and traverse the DOM up.
- The element to look for — stop the traverse when hit this element and return it.
Here's our example markup that we'll be working with:
item1
item2
item3
item4
Imagine you want to do something to the nav
, but your 'starting point' is one of the li
elements [for whatever reason]. You might be tempted to do this:
$['.nav-item-1']
.parent[] // ul
.parent[] // nav
.parent[] // div
.css['background-color', 'red']
That's not too bad, but what if you need to traverse longer distance? Or if you add a wrapper there you need to update the code. Closest is made just for these kinds of thing:
$['.nav-item-1'].closest['nav'].css['background-color', 'red']
You give the starting point $['.nav-item-1']
and the ending point .closest['nav']
, and it will traverse the DOM up until it hits the target and returns it.
Pure JavaScript closestByClass
function
Below is vanilla JavaScript function that gets the closest by class name. From it, you can see much more in detail how the closest function really works:
var closestByClass = function [el, clazz] {
// Traverse the DOM up with a while loop
while [el.className != clazz] {
// Increment the loop to the parent node
el = el.parentNode
if [!el] {
return null
}
}
// At this point, the while loop has stopped and `el` represents the element
// that has the class you specified in the second parameter of the function
// `clazz`
return el
}
Example usage:
document. title = function [e] {
if [closestByClass[e.target, 'some-class']] {
// Do something
} else {
// Do something else
}
}
That's nice and all, but it only takes class names, it's easy to make it check for ID, just replace the className
with id
. Where as it might be totally sufficient in majority of use cases, it's still not very versatile.
Recursive pure JavaScript closest
Here's a more flexible function that uses recursion and is a bit shorter:
var closest = function [el, fn] {
return el && [fn[el] ? el : closest[el.parentNode, fn]]
}
Or write it as one-liner with an arrow function:
var closest = [el, fn] => el && [fn[el] ? el : closest[el.parentNode, fn]]
That's quite dense. Let's look at the usage with the example list we used earlier. Note how easy it is to target class, id, or tag name:
var srcEl = document.getElementsByClassName['nav__item-3']
// The element with a class of `.nav` is the wanted element in this case
var nav = closest[srcEl[0], function [el] {
// Here's the beauty of this function, we have control on the target, here
// we're using class name `.nav`
return el.className === 'nav'
}]
// Now the variable `nav` contains the closest element with a class `.nav`
console.log[nav]
// Here the target is given as id `#nav-id`
var nav = closest[srcEl[0], el => el.id === 'nav-id']
// Here it's the tag ``, and so on...
var nav = closest[srcEl[0], el => el.tagName.toLowerCase[] === 'nav']
Now the nav
variable is set to the closest element, matched with class name, id,
or tag name. It's just a regular old reference to a DOM element, anything can be done with it.
This method is unbelievably light and versatile! It originates to this great SO answer.
Here's a little demo.
Pure JavaScript closest demo