Climbing up and down the DOM tree with vanilla JavaScript
On a recent Javascript project, I needed to climb up the DOM tree looking for the first element that had a particular class, ID, or data attribute.
This is really easy to do with jQuery, but today I wanted to share some simple vanilla JavaScript methods to duplicate jQuery’s .closest()
, .parent/s()
, and .find()
APIs.
Note: Updated on December 16, 2014, to include tag selectors.
Climbing up the DOM tree
jQuery offers a few methods for climbing up the DOM tree. Let’s explore a few of the tasks you might want to achieve.
Getting the first match up the tree
In jQuery, .closest()
climbs up the DOM tree and finds the first element that has the selector you’re trying to match against. It starts with the first element, and then climbs. Here’s a vanilla JS method that does the same thing:
/**
* Get the closest matching element up the DOM tree.
* @private
* @param {Element} elem Starting element
* @param {String} selector Selector to match against
* @return {Boolean|Element} Returns null if not match found
*/
var getClosest = function ( elem, selector ) {
// Element.matches() polyfill
if (!Element.prototype.matches) {
Element.prototype.matches =
Element.prototype.matchesSelector ||
Element.prototype.mozMatchesSelector ||
Element.prototype.msMatchesSelector ||
Element.prototype.oMatchesSelector ||
Element.prototype.webkitMatchesSelector ||
function(s) {
var matches = (this.document || this.ownerDocument).querySelectorAll(s),
i = matches.length;
while (--i >= 0 && matches.item(i) !== this) {}
return i > -1;
};
}
// Get closest match
for ( ; elem && elem !== document; elem = elem.parentNode ) {
if ( elem.matches( selector ) ) return elem;
}
return null;
};
And to use it:
var elem = document.querySelector('#example');
var closestElem = getClosest(elem, '.sample-class');
If you wanted to start with the element’s parent instead of the element itself (equivalent to the .parent()
method), you would do this:
var elem = document.querySelector('#example');
var closestElem = getClosest(elem.parentNode, '#sample-id');
Getting all matches up the tree
In jQuery, .parents()
climbs the DOM tree and returns all parent elements. If you include a selector, it will only return those that match. Here’s the vanilla JavaScript equivalent:
/**
* Get all of an element's parent elements up the DOM tree
* @param {Node} elem The element
* @param {String} selector Selector to match against [optional]
* @return {Array} The parent elements
*/
var getParents = function ( elem, selector ) {
// Element.matches() polyfill
if (!Element.prototype.matches) {
Element.prototype.matches =
Element.prototype.matchesSelector ||
Element.prototype.mozMatchesSelector ||
Element.prototype.msMatchesSelector ||
Element.prototype.oMatchesSelector ||
Element.prototype.webkitMatchesSelector ||
function(s) {
var matches = (this.document || this.ownerDocument).querySelectorAll(s),
i = matches.length;
while (--i >= 0 && matches.item(i) !== this) {}
return i > -1;
};
}
// Setup parents array
var parents = [];
// Get matching parent elements
for ( ; elem && elem !== document; elem = elem.parentNode ) {
// Add matching parents to array
if ( selector ) {
if ( elem.matches( selector ) ) {
parents.push( elem );
}
} else {
parents.push( elem );
}
}
return parents;
};
And to use it:
var elem = document.querySelector('#some-element');
var parents = getParents(elem, '.some-class');
var allParents = getParents(elem.parentNode);
Once again, if you wanted to start with the element’s parent instead of the element itself, you would do this:
var elem = document.querySelector('#example');
var parents = getClosest(elem.parentNode, '[data-sample]');
Getting all matches up the tree until a matching parent is found
In jQuery, .parentsUntil()
climbs the DOM tree and returns all parent elements until a matching parent is found. If you include a selector, it will only return those that match. Here’s the vanilla JavaScript equivalent:
/**
* Get all of an element's parent elements up the DOM tree until a matching parent is found
* @param {Node} elem The element
* @param {String} parent The selector for the parent to stop at
* @param {String} selector The selector to filter against [optionals]
* @return {Array} The parent elements
*/
var getParentsUntil = function ( elem, parent, selector ) {
// Element.matches() polyfill
if (!Element.prototype.matches) {
Element.prototype.matches =
Element.prototype.matchesSelector ||
Element.prototype.mozMatchesSelector ||
Element.prototype.msMatchesSelector ||
Element.prototype.oMatchesSelector ||
Element.prototype.webkitMatchesSelector ||
function(s) {
var matches = (this.document || this.ownerDocument).querySelectorAll(s),
i = matches.length;
while (--i >= 0 && matches.item(i) !== this) {}
return i > -1;
};
}
// Setup parents array
var parents = [];
// Get matching parent elements
for ( ; elem && elem !== document; elem = elem.parentNode ) {
if ( parent ) {
if ( elem.matches( parent ) ) break;
}
if ( selector ) {
if ( elem.matches( selector ) ) {
parents.push( elem );
}
break;
}
parents.push( elem );
}
return parents;
};
And to use it:
var elem = document.querySelector('#some-element');
var parentsUntil = getParentsUntil(elem, '.some-class');
var parentsUntilByFilter = getParentsUntil(elem, '.some-class', '[data-something]');
var allParentsUntil = getParentsUntil(elem);
var allParentsExcludingElem = getParentsUntil(elem.parentNode);
Climbing down the DOM tree
In jQuery, the .find()
method provides an easy way to match elements down the DOM tree by selector. In vanilla JS, climbing down the DOM tree is actually a lot easier than going up, and can be achieved with the querySelector
family of web API’s.
Getting the first match down the tree
var elem = document.querySelector('#example');
var firstElem = elem.querySelector('.sample-class');
Getting all matches down the tree
var elem = document.querySelector('#example');
var allElems = elem.querySelectorAll('[data-sample]');
Enjoy!