Skip to main content Skip to secondary navigation Accessibility Feedback

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.

Psst... I'm writing a book on ditching jQuery for vanilla JS. Pre-order it today and save 50%.

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.
 * @param  {Element} elem     Starting element
 * @param  {String}  selector Selector to match against (class, ID, data attribute, or tag)
 * @return {Boolean|Element}  Returns null if not match found
 */
var getClosest = function ( elem, selector ) {

    // Variables
    var firstChar = selector.charAt(0);
    var supports = 'classList' in document.documentElement;
    var attribute, value;

    // If selector is a data attribute, split attribute from value
    if ( firstChar === '[' ) {
        selector = selector.substr( 1, selector.length - 2 );
        attribute = selector.split( '=' );

        if ( attribute.length > 1 ) {
            value = true;
            attribute[1] = attribute[1].replace( /"/g, '' ).replace( /'/g, '' );
        }
    }

    // Get closest match
    for ( ; elem && elem !== document && elem.nodeType === 1; elem = elem.parentNode ) {

        // If selector is a class
        if ( firstChar === '.' ) {
            if ( supports ) {
                if ( elem.classList.contains( selector.substr(1) ) ) {
                    return elem;
                }
            } else {
                if ( new RegExp('(^|\\s)' + selector.substr(1) + '(\\s|$)').test( elem.className ) ) {
                    return elem;
                }
            }
        }

        // If selector is an ID
        if ( firstChar === '#' ) {
            if ( elem.id === selector.substr(1) ) {
                return elem;
            }
        }

        // If selector is a data attribute
        if ( firstChar === '[' ) {
            if ( elem.hasAttribute( attribute[0] ) ) {
                if ( value ) {
                    if ( elem.getAttribute( attribute[0] ) === attribute[1] ) {
                        return elem;
                    }
                } else {
                    return elem;
                }
            }
        }

        // If selector is a tag
        if ( elem.tagName.toLowerCase() === 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 DOM element up the tree that contain a class, ID, or data attribute
 * @param  {Node} elem The base element
 * @param  {String} selector The class, id, data attribute, or tag to look for
 * @return {Array} Null if no match
 */
var getParents = function (elem, selector) {

    var parents = [];
    var firstChar;
    if ( selector ) {
        firstChar = selector.charAt(0);
    }

    // Get matches
    for ( ; elem && elem !== document; elem = elem.parentNode ) {
        if ( selector ) {

            // If selector is a class
            if ( firstChar === '.' ) {
                if ( elem.classList.contains( selector.substr(1) ) ) {
                    parents.push( elem );
                }
            }

            // If selector is an ID
            if ( firstChar === '#' ) {
                if ( elem.id === selector.substr(1) ) {
                    parents.push( elem );
                }
            }

            // If selector is a data attribute
            if ( firstChar === '[' ) {
                if ( elem.hasAttribute( selector.substr(1, selector.length - 1) ) ) {
                    parents.push( elem );
                }
            }

            // If selector is a tag
            if ( elem.tagName.toLowerCase() === selector ) {
                parents.push( elem );
            }

        } else {
            parents.push( elem );
        }

    }

    // Return parents if any exist
    if ( parents.length === 0 ) {
        return null;
    } else {
        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:

var getParentsUntil = function (elem, parent, selector) {

    var parents = [];
    if ( parent ) {
        var parentType = parent.charAt(0);
    }
    if ( selector ) {
        var selectorType = selector.charAt(0);
    }

    // Get matches
    for ( ; elem && elem !== document; elem = elem.parentNode ) {

        // Check if parent has been reached
        if ( parent ) {

            // If parent is a class
            if ( parentType === '.' ) {
                if ( elem.classList.contains( parent.substr(1) ) ) {
                    break;
                }
            }

            // If parent is an ID
            if ( parentType === '#' ) {
                if ( elem.id === parent.substr(1) ) {
                    break;
                }
            }

            // If parent is a data attribute
            if ( parentType === '[' ) {
                if ( elem.hasAttribute( parent.substr(1, parent.length - 1) ) ) {
                    break;
                }
            }

            // If parent is a tag
            if ( elem.tagName.toLowerCase() === parent ) {
                break;
            }

        }

        if ( selector ) {

            // If selector is a class
            if ( selectorType === '.' ) {
                if ( elem.classList.contains( selector.substr(1) ) ) {
                    parents.push( elem );
                }
            }

            // If selector is an ID
            if ( selectorType === '#' ) {
                if ( elem.id === selector.substr(1) ) {
                    parents.push( elem );
                }
            }

            // If selector is a data attribute
            if ( selectorType === '[' ) {
                if ( elem.hasAttribute( selector.substr(1, selector.length - 1) ) ) {
                    parents.push( elem );
                }
            }

            // If selector is a tag
            if ( elem.tagName.toLowerCase() === selector ) {
                parents.push( elem );
            }

        } else {
            parents.push( elem );
        }

    }

    // Return parents if any exist
    if ( parents.length === 0 ) {
        return null;
    } else {
        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!

If you liked this article, you might also like Ditching jQuery, my new beginner's guide to vanilla JavaScript. Pre-order it today and save 50%.
  • More articles on...
  • Code

Have any questions or comments about this post? Email me at chris@gomakethings.com or contact me on Twitter at @ChrisFerdinandi.

Get Weekly Digests