DEV Community

Cover image for Writing a .parents() function using Vanilla JS
Uriel Bitton
Uriel Bitton

Posted on • Updated on

Writing a .parents() function using Vanilla JS

Intro

Moving away from the super useful and powerful Jquery in order to use more modern frameworks (such as React, Vue and Ember JS), the thing i miss the most about jquery was its super simple DOM manipulation that we don't have with Vanilla JS.

My top favorite built in functions were the .parents() function and the .on() event handlers (the $ elm saved so many charactersof code!). These 2 functions single handedly reduced Vanilla JS code by a factor of 10x at least. The .on() function in jquery let us bind an event handler such as click to any number of elements as easy as:

$('p').on("click", function() {
    //execute anything when any of the p tags on the page are clicked
}
Enter fullscreen mode Exit fullscreen mode

That was super cool and useful. Now with Vanila JS we know we must loop through the collection of p tags and bind click events to each item in that collection (as they are treated as an array).
The code is much more complex:

const p = document.querySelectorAll('p')
for(item of p) {
    p.onclick = () => { //ES6
        //execute actions when p tags are clicked
}
}
Enter fullscreen mode Exit fullscreen mode

As we can see its doable but adds easily 3-4 lines of extra code (even using ES6)

But for the magical .parents() function, Javascript does not even provide us a long or short way to achieve what jquery did. So let's see what we need to design in order to simulate the .parents() behaviour. In the end it'll add almost 20 lines of extra code as opposed to just writing .parents() but we'll have a readily available function that we can reuse whenever we want! Its gonna be a little long but stick around I promise you won't regret it.

function getParents(el, parentSelector) {
    if (parentSelector === undefined) {
        parentSelector = document;
    }
    var parents = [];
    var p = el.parentNode;
    while (p !== parentSelector) {
        var o = p;
        parents.push(o);
        p = o.parentNode;
    }
    parents.push(parentSelector);
    return parents;
}
Enter fullscreen mode Exit fullscreen mode

Explainging the Code

Let's understand this code. The first line initiates the function and passes two parameters, the actual element which we are at and an optional parent selector to stop our parent search at.

The second line says if we dont provide a parent selector then set the parent selector as the root-most element, which is the document itself (above the html tag).

Then we create a parent array and stuff all of the parent elements of our element. We then have a while loop that dictates that while the current parent node is not our provided parent selector (or document) then assign that node to a variable o and push o into the parents array.
So every iteration where that parent node is not the provided parent selector, we add that node to an array. Eventually our provided parent selector will be the current parent node as it goes up and compares to every parent node all the way up the DOM.
Once there is a match, the while loop will stop, the array will finally push our provided parent selector and thus the highest parent node will be at the end of the array. Finally we return the array parents and we are done.

Now how do we actually make use of our custom built function to use like .parents() in jquery? Simple. We call the function on the element we want and pass as arguments our element and a max parent selector like so:

//parentDiv is our max parent selector
const button = document.querySelector('button')
const parentDiv = document.querySelector('.parentdiv')

//here we call our getParents function
const parents = getParents(button, parentDiv)
//and here the magic happens, since our parent node is always the last one
parents[parents.length-1] 
Enter fullscreen mode Exit fullscreen mode

Since our parent selector is always the last one we can refer to it simply by using our parents array and retrieving the last element in the array by doing parents.length-1 (will get the last element in the array).

We can then do

parents[parents.length-1].querySelector('p').innerHTML //finds a p tag's inner html contents inside our parents element, just like in jquery
Enter fullscreen mode Exit fullscreen mode

Comparing Jquery to Vanilla JS

Finally lets see the comparision between our custom built Vanilla JS solution and jquery's solution:

//jquery
var mytxt = $('button').parents('.parent1').find('p').html() //will get us the html content of a p element inside the parent div of our button - super useful DOM manipulation

//Vanilla JS
const parents = getParents(button, parent1)//assuming button and parent1 are already assigned 
const mytxt = parents[parents.length-1].querySelector('p').innerHTML
Enter fullscreen mode Exit fullscreen mode

And now we have our desired result inside the mytxt const using Vanilla JS instead of Jquery!

Final Thoughts

Sure the Vanilla JS solution required quite some extensive code but ultimately its not that bad and well worth to use if we reuse the function across our app.

Thanks for taking interest in my post and reading up to the end. I hope you will find this useful and that it helps you in your future coding projects!

Top comments (9)

Collapse
 
leob profile image
leob • Edited

Great article ...

All in all if you want pure DOM manipulation (not using React or Vue or whatever) then still nothing beats the jQuery API for elegance and conciseness.

Makes me wonder if there isn't something like a "jQuery lite" which wraps the Vanilla API and provides a small subset of the most useful/popular jQuery functions, but with a minimal bundle size (the full jQuery library is still surprisingly large) ... you've written part of it already.

Collapse
 
gwutama profile image
Galuh Utama • Edited

Don’t forget that web servers will compress files before transferring to client (usually gzipped), which practically means that the size of a text file will be around 1/3 - 1/4 or even less than its original size.

Collapse
 
leob profile image
leob

Well yes that's always the case when server gzip is enabled, regardless the type of resource (JS, CSS) or whether you use jQuery or another library, but having a smaller bundle size is beneficial anyway. I was just a little bit surprised when looking at the bundle size of jQuery and realizing that it isn't really much smaller than more full-featured libs like React or Vue.

Collapse
 
urielbitton profile image
Uriel Bitton

Thanks!

Youre right a jquery lite could be a good idea.

Collapse
 
leob profile image
leob • Edited

Something like that might even exist already, previously there was something called zepto.js ... it still "exists" but latest version is from 2016 (and nobody uses it anymore I think) ... then there's"jquery-lite" (github.com/deager/jquery-lite) which is 5 years old and very limited ... no those 2 aren't going to win the war.

"jQuery Slim" looks better, but still large ... then this one, "Cash":

github.com/fabiospampinato/cash

I think that's what we would be looking for - almost all of the stuff you need and want from jQuery (including "parent()", "closest()", "append()", "on()", "off()", you name it), but the download is 3 to 4 times smaller.

Next question is, does it perform equally well as jQuery ... if it downloads quicker but execution is sluggish then you haven't gained anything.

Thread Thread
 
urielbitton profile image
Uriel Bitton

interesting i'll have a look at these thanks.

But i'm anyways working on enhancing my vanilla js skills so im gonna try to get as good with JS as i am with jquery and i wont even think about the convenience jquery has in a few months :)

Collapse
 
maxxheth profile image
Maximillian Heth

Regarding your example in which you looped through the paragraph elements, it's worth noting that it's also possible to use event delegation in vanilla JS so you can avoid looping altogether:

document.addEventListener('click', function(event) {

    if (event.target.tagName !== 'P') return; // If it's not a paragraph element, skip it.

    event.target.textContent += ' has been modified...';

});
Enter fullscreen mode Exit fullscreen mode

Also, nice function you wrote there! To further bolster our collective laziness as devs, I added some features to allow the function to receive class or ID strings as arguments and pick a specific element via an index if multiple elements share the same class or tag name:

function getParents(el, parentSelector, elIndex, parentIndex) {
  if (parentSelector === undefined) {
    parentSelector = document;
  }

  function ifStringUseQS(selector, index) {

    if (!index) index = 0;

    if (Object.prototype.toString.call(selector).toLowerCase().indexOf('string') > -1) { // Is it a string?

      /**
       * 
       * If the selector can potentially refer to more than one element, 
       * then entering an index will allow you grab that specific element.
       * 
       */

        return document.querySelectorAll(selector)[index]; 

    } else {

        return selector; // If it's not a string, it'll just be reassigned unto itself.

        }

  }

  el = ifStringUseQS(el, elIndex);

  parentSelector = ifStringUseQS(parentSelector, parentIndex);


  if (!el) return; // If el is undefined, hit the brakes.


  var parents = [];
  var p = el.parentNode;
  while (p !== parentSelector) {
    parents.push(p);
    p = p.parentNode;
  }
  parents.push(parentSelector);
  return parents;
}
Enter fullscreen mode Exit fullscreen mode

So now you can use the function like so:

// Grab an element.

getParents('.some-elem');

// Grab an element and set the uppermost parent.

getParents('.some-elem', '.some-parent');

// Choose a different element or a different parent with the same class.

getParents('.some-elem', '.some-parent', 2, 4);
Enter fullscreen mode Exit fullscreen mode
Collapse
 
benilda_key profile image
Benilda Key

This is not an acceptable alternative to JQuery .parents. This ASSumes you have an element for parentSelector. What if you have a string like "[aria-label][href]". Then this function is useless.

Collapse
 
urielbitton profile image
Uriel Bitton

Please note this is using Vanilla JS and not jquery. Jquery comes with a heavier bundle. JS is more advantageous is also more trendy now. Also jquery is slowly dying ;)