Using closest() to return the correct DOM element
I was recently working with a vertical navigation component and ran into a hiccup where the JavaScript code wouldn’t fire depending on where I clicked on the menu item link. I did some digging and thought I’d share a little about what I discovered and how I was able to resolve the problem.
To start, I have a list item that when selected will expand or collapse a submenu:
<li>
<a href="#submenu" class="toggle">Billing</a>
<div id="submenu">
<ul>
<li><a href="/statment/">My Statement</a></li>
<li><a href="/history/">Pay History</a></li>
</ul>
</div>
</li>
This event listener will manage toggling the submenu’s expanded/collapsed state:
document.addEventListener('click', function (event) {
// Make sure clicked element is our toggle
if (!event.target.classList.contains('toggle')) {
return;
}
event.preventDefault();
// Get the content
let content = document.querySelector(event.target.hash);
if (!content) {
return;
}
// Toggle the content
toggle(content);
}, false);
The toggle()
method executes a function to check if the submenu has the .is-visible
CSS class. If the element has that class, the submenu will be hidden; otherwise, the submenu is displayed:
const toggle = function (elem, timing) {
// If the element is visible, hide it
if (elem.classList.contains('is-visible')) {
hide(elem);
return;
}
// Otherwise, show it
show(elem);
};
I expected that clicking anywhere within the menu item would fire the JavaScript and perform the toggle. But if I clicked on either the icon or the label child elements, the JavaScript wouldn’t execute. The reason is that event.target
returns the exact DOM element. Clicking on the icon or the label returned only the image or span elements.
The closest() method
This was something I had to look up. I needed the target and return the parent element, not the child elements. I found the solution using the closest()
method. This method travels up the DOM tree from the current element and returns the closest ancestor that matches the given parameter:
const closestElement = Element.closest(selector);
This was my “ah-ha!” moment. I could chain closest()
to event.target
to find and return the parent element (menu item link), regardless if I ended up clicking on the child elements (icon or label):
if (!event.target.closest('a').classList.contains('toggle')) {
return;
}
const content = document.querySelector(event.target.closest('a').hash);
Now clicking anywhere in the menu item link fires the JavaScript to toggle the submenu.
Hopefully this tip will help you if you need to target specific elements in the DOM. The closest()
method is supported in most major browsers but requires a polyfill with IE11.
If you’re looking for more in-depth reading on this, I’d recommend Zell Liew’s post on traversing the DOM. He covers this method and a few other tricks that are worth checking out.