Vanilla Javascript or Why You Should Pay the Bandwidth Gods

TLDR version: use new built-in DOM features if you are doing REALLY BASIC DOM manipulation otherwise do yourself a favor and pay the 32kB to the bandwidth gods and save yourself some time/headache/lines of code!

I’ve Become Spoiled By jQuery

Those of you who know me know that I’ve become obsessed with d3.js lately. The selectors in d3 look a lot like jQuery but in the documentation it states it only uses Sizzle (jQuery’s selector engine) for older browsers and in modern browsers it uses the W3C Selectors API.

Huh? What the heck is that? Apparently while I’ve been living in jQuery-land there have been a ton of advances in plain vanilla Javascript (well, the DOM anyway). So let’s explore what’s new. Let’s start out with a VERY basic html structure:

<html>
  <body>
    <ul>
      <li id="foo" class="bar">Item 1</li>
      <li id="foo2" class="bar2">Item 2</li>
    </ul>
  </body>
</html>

classList API

The classList API allows you to easily add/remove/toggle classes on an html element.
Works In: Most modern browsers except IE8/9

var elem = document.getElementById('foo');
 
elem.classList.add('bar'); //#foo now has .bar
elem.classList.remove('bar'); //.bar is now removed
elem.classList.toggle('bar'); //#foo now has .bar again

querySelector / querySelectorAll API

JQuery-like selector functions that return the first matched element (querySelector) or all matched elements as a NodeList (querySelectorAll).
Works In: Every browser except IE7

//WOW this looks a LOT like jQuery
var foo = document.querySelector("#foo"); 
var bar = document.querySelector(".bar"); //returns first match
 
//querySelector is available on elements in addition to document
bar.querySelector('.foobar');
 
//return a NodeList of all .bar's inside #foo's
var bars = document.querySelectorAll("#foo .bar"); 
 
//also available on elements
bar.querySelectorAll(".foobar"); //returns all .foobar's inside bar

So far so good. Looks and feels a lot like JQuery without downloading a JS lib (awesome!). Chances are if you’re reading this you’re not using IE7 so this capability is in your browser right now. Open the console and play around with it! When I saw these in action for the first time I thought “sweet! Let’s scrap JQuery and use this”. My heart was broken though once I tried to do anything useful with the results from these two new methods.

OK Let’s Manipulate The DOM!

What is the point of selecting stuff if we’re not going to manipulate it in some way. Let’s start with some simple examples like adding/removing/toggling a class:

  //jQuery

  //add a class to one element
  $('#foo').addClass('bar');

  //add a class to many elements
  $('.foo').addClass('bar');
 
  //DOM
 
  //add a class to one element
  //classList makes this easy!
  document.querySelector("#foo").classList.add('bar');
 
  //add a class to many elements
  //holy crap that is a lot of typing for a simple example!
  for(var i = 0, foos = document.querySelectorAll('.foo'); i < foos.length; i++) {
    foos[i].classList.add('bar');
  }

Problems

The querySelectorAll method returns a NodeList object. This NodeList objects looks like an Array and acts like an Array but it’s not an Array. It’s what Javascripters call an “Array-like object”. It can be iterated over and has a length property but is missing all of the essential Array methods (forEach, map, reduce, etc.).

var lis = document.querySelectorAll('li');
 
console.log(lis.length); // 2
 
for(var i = 0; i < lis.length; i++) {
  console.log(lis[i]);
}

You *can* use Array’s map, reduce, etc methods by using some JS hackery. This will help out when we get frustrated below:

var lis = document.querySelectorAll('li');
 
//borrowing Array's methods.  .call on a method is a fancy way to
//set the this property when the method runs
 
[].forEach.call(lis, function(li) {
  console.log(li);
});
 
[].map.call(lis, function(li) { return li.id; })
//["foo", "foo2"]
 
[].reduce.call(lis, function(total, li) { 
  return total + li.id; }, "")
//"foofoo2"

You *could* add to Nodelist’s prototype but in general that’s a bad direction to go. People way smarter than me have already covered why not.

More DOM Manipulation

Now let’s try to find a sub element and alter it:

//jQuery
 
var foos = $('.foo');
...other code...
 
//now select one child element and toggle a class
foos.find('#bar').toggleClass('highlight');

//select many child elements and toggle their classes
foos.find('.bar').toggleClass('highlight');
 
//DOM
 
var foos = document.querySelectorAll('.foo');
..other code...
 
//now select one child element and toggle a class
for(var i = 0; i < foos.length; i++) {
  foos[i].querySelector('#bar').classList.toggle('highlight');
}
 
//select many child elements and toggle their classes
//really starting to appreciate jQuery at this point!
for(var i = 0; i < foos.length; i++) {
  for(var j = 0, foobars = foos[i].querySelectorAll('.bar'); j < foobars.length; j++) {
    foobars[j].classList.toggle('highlight');
  }
}

…and We Haven’t Even Covered

If you’re not convinced yet that jQuery is still far superior to Vanilla Javascript then remember we haven’t hit on Cross Browser Support, Events, .closest, .parents, etc. All stuff jQuery handles rather nicely and make us ignorant of the mess that is cross browser development.

I know that DOM manipulation is out of style currently in favor of MVC/MVVM flavor Javascript libs but if even ever need to manipulate the DOM then do yourself a favor and pay the bandwidth gods the 32kB and send a thank you note to the awesome jQuery team!!

Leave a Reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>