Advertisement
JavaScript & AJAX

From jQuery to JavaScript: A Reference

by

Whether we like it or not, more and more developers are being introduced to the world of JavaScript through jQuery first. In many ways, these newcomers are the lucky ones. They have access to a plethora of new JavaScript APIs, which make the process of DOM traversal (something that many folks depend on jQuery for) considerably easier. Unfortunately, they don't know about these APIs!

In this article, we'll take a variety of common jQuery tasks, and convert them to both modern and legacy JavaScript.

Modern vs. Legacy - For each item in the list below, you'll find the modern, "cool kids" way to accomplish the task, and the legacy, "make old browsers happy" version. The choice you choose for your own projects will largely depend on your visitors.


Before We Begin

Please note that some of the legacy examples in this article will make use of a simple, cross-browser, addEvent function. This function will simply ensure that both the W3C-recommended event model, addEventListener, and Internet Explorer's legacy attachEvent are normalized.

So, when I refer to addEvent(els, event, handler) in the legacy code snippets below, the following function is being referenced.

var addEvent = (function () {
	var filter = function(el, type, fn) {
		for ( var i = 0, len = el.length; i < len; i++ ) {
			addEvent(el[i], type, fn);
		}
	};
	if ( document.addEventListener ) {
		return function (el, type, fn) {
			if ( el && el.nodeName || el === window ) {
				el.addEventListener(type, fn, false);
			} else if (el && el.length) {
				filter(el, type, fn);
			}
		};
	}

	return function (el, type, fn) {
		if ( el && el.nodeName || el === window ) {
			el.attachEvent('on' + type, function () { return fn.call(el, window.event); });
		} else if ( el && el.length ) {
			filter(el, type, fn);
		}
	};
})();

// usage
addEvent( document.getElementsByTagName('a'), 'click', fn);

1 - $('#container');

This function call will query the DOM for the element with an id of container, and create a new jQuery object.

Modern JavaScript

var container = document.querySelector('#container');

querySelector is part of the Selectors API, which provides us with the ability to query the DOM using the CSS selectors that we're already familiar with.

This particular method will return the first element that matches the passed selector.

Legacy

var container = document.getElementById('container');

Pay special attention to how you reference the element. When using getElementById, you pass the value alone, while, with querySelector, a CSS selector is expected.


2 - $('#container').find('li');

This time, we're not hunting for a single element; instead, we're capturing any number of list items that are descendants of #container.

Modern JavaScript

var lis = document.querySelectorAll('#container li');

querySelectorAll will return all elements that match the specified CSS selector.

Selector Limitations

While nearly all relevant browsers support the Selectors API, the specific CSS selectors you pass are still limited to the capability of the browser. Translation: Internet Explorer 8 will only support CSS 2.1 selectors.

Legacy

var lis = document.getElementById('container').getElementsByTagName('li');

3 - $('a').on('click', fn);

In this example, we're attaching a click event listener to all anchor tags on the page.

Modern JavaScript

[].forEach.call( document.querySelectorAll('a'), function(el) {
   el.addEventListener('click', function() {
     // anchor was clicked
  }, false);

  

});

The above snippet looks scary, but it's not too bad. Because querySelectorAll returns a static NodeList rather than an Array, we can't directly access methods, like forEach. This is remedied by calling forEach on the Array object, and passing the the results of querySelectorAll as this.

Legacy

var anchors = document.getElementsbyTagName('a');
addEvent(anchors, 'click', fn);

4 - $('ul').on('click', 'a', fn);

Ahh - this example is slightly different. This time, the jQuery snippet is using event delegation. The click listener is being applied to all unordered lists, however, the callback function will only fire if the target (what the user specifically clicked on) is an anchor tag.

Modern JavaScript

document.addEventListener('click', function(e) {
   if ( e.target.matchesSelector('ul a') ) {
      // proceed
   }
}, false);

Technically, this vanilla JavaScript method isn't the same as the jQuery example. Instead, it's attaching the event listener directly to the document. It then uses the new matchesSelector method to determine if the target - the node that was clicked - matches the provided selector. This way, we're attaching a single event listener, rather than many.

Please note that, at the time of this writing, all browsers implement matchesSelector via their own respective prefixes: mozMatchesSelector, webkitMatchesSelector, etc. To normalize the method, one might write:

var matches;

(function(doc) {
   matches = 
      doc.matchesSelector ||
      doc.webkitMatchesSelector ||
      doc.mozMatchesSelector ||
      doc.oMatchesSelector ||
      doc.msMatchesSelector;
})(document.documentElement);

document.addEventListener('click', function(e) {
   if ( matches.call( e.target, 'ul a') ) {
      // proceed
   } 
}, false);

With this technique, in Webkit, matches will refer to webkitMatchesSelector, and, in Mozilla, mozMatchesSelector.

Legacy

var uls = document.getElementsByTagName('ul');

addEvent(uls, 'click', function() {
   var target = e.target || e.srcElement;
   if ( target && target.nodeName === 'A' ) {
      // proceed
   }
});

As a fallback, we determine if the nodeName property (the name of the target element) is equal to our desired query. Pay special attention to the fact that older versions of Internet Explorer sometimes plays by their own rules - sort of like the kid who eats play-doh during lunch time. You won't be able to access target directly from the event object. Instead, you'll want to look for event.srcElement.


5 - $('#box').addClass('wrap');

jQuery provides a helpful API for modifying class names on a set of elements.

Modern JavaScript

document.querySelector('#box').classList.add('wrap');

This new technique uses the new classList API to add, remove, and toggle class names.

var container = document.querySelector('#box');

container.classList.add('wrap'); 
container.classList.remove('wrap');
container.classList.toggle('wrap');

Legacy

var box = document.getElementById('box'),

    hasClass = function (el, cl) {
        var regex = new RegExp('(?:\\s|^)' + cl + '(?:\\s|$)');
        return !!el.className.match(regex);
    },

    addClass = function (el, cl) {
        el.className += ' ' + cl;
    },

    removeClass = function (el, cl) {
        var regex = new RegExp('(?:\\s|^)' + cl + '(?:\\s|$)');
        el.className = el.className.replace(regex, ' ');
    },

    toggleClass = function (el, cl) {
        hasClass(el, cl) ? removeClass(el, cl) : addClass(el, cl);

    };

addClass(box, 'drago'); 
removeClass(box, 'drago');
toggleClass(box, 'drago'); // if the element does not have a class of 'drago', add one.

The fallback technique requires just a tad more work, ay?


6 - $('#list').next();

jQuery's next method will return the element that immediately follows the current element in the wrapped set.

Modern JavaScript

var next = document.querySelector('#list').nextElementSibling; // IE9

nextElementSibling will refer specifically to the next element node, rather than any node (text, comment, element). Unfortunately, Internet Explorer 8 and below do not support it.

Legacy

var list = document.getElementById('list'),
	next = list.nextSibling;

// we want the next element node...not text.
while ( next.nodeType > 1 ) next = next.nextSibling;

There's a couple ways to write this. In this example, we're detecting the nodeType of the node that follows the specified element. It could be text, element, or even a comment. As we specifically need the next element, we desire a nodeType of 1. If next.nodeType returns a number greater than 1, we should skip it and keep going, as it's probably a text node.


7 - $('<div id=box></div>').appendTo('body');

In addition to querying the DOM, jQuery also offers the ability to create and inject elements.

Modern JavaScript

var div = document.createElement('div');
div.id = 'box';
document.body.appendChild(div);

There's nothing modern about this example; it's how we've accomplished the process of creating and injecting elements into the DOM for a long, long time.

You'll likely need to add content to the element, in which case you can either use innerHTML, or createTextNode.

div.appendChild( document.createTextNode('wacka wacka') );

// or

div.innerHTML = 'wacka wacka';

8 - $(document).ready(fn)

jQuery's document.ready method is incredibly convenient. It allows us to begin executing code as soon as possible after the DOM has been loaded.

Modern JavaScript

document.addEventListener('DOMContentLoaded', function() {
   // have fun
});

Standardized as part of HTML5, the DOMContentLoaded event will fire as soon as the document has been completed parsed.

Legacy

// http://dustindiaz.com/smallest-domready-ever
function ready(cb) {
	/in/.test(document.readyState) // in = loadINg
		? setTimeout('ready('+cb+')', 9)
		: cb();
}

ready(function() {
   // grab something from the DOM
});

The fallback solution, every nine milliseconds, will detect the value of document.readyState. If "loading" is returned, the document hasn't yet been fully parsed (/in/.test(). Once it has, though, document.readyState will equal "complete," at which point the user's callback function is executed.


9 - $('.box').css('color', 'red');

If possible, always add a class to an element, when you need to provide special styling. However, sometimes, the styling will be determined dynamically, in which case it needs to be inserted as an attribute.

Modern JavaScript

[].forEach.call( document.querySelectorAll('.box'), function(el) {
  el.style.color = 'red'; // or add a class
});

Once again, we're using the [].forEach.call() technique to filter through all of the elements with a class of box, and make them red, via the style object.

Legacy

var box = document.getElementsByClassName('box'), // refer to example #10 below for a cross-browser solution
   i = box.length;
 
while ( i-- > 0 && (box[i].style.color = 'red') );

This time, we're getting a bit tricky with the while loop. Yes, it's a bit snarky, isn't it? Essentially, we're mimicking:

var i = 0, len;

for ( len = box.length; i < len; i++ ) {
   box[i].style.color = 'red';
}

However, as we only need to perform a single action, we can save a couple lines. Note that readability is far more important than saving two lines - hence my "snarky" reference. Nonetheless, it's always fun to see how condensed you can make your loops. We're developers; we do this sort of stuff for fun! Anyhow, feel free to stick with the for statement version.


10 - $()

Clearly, our intention is not to replicate the entire jQuery API. Typically, for non-jQuery projects, the $ or $$ function is used as shorthand for retrieving one or more elements from the DOM.

Modern JavaScript

var $ = function(el) {
	return document.querySelectorAll(el);
};
// Usage = $('.box');

Notice that $ is simply a one-character pointer to document.querySelector. It saves time!

Legacy

if ( !document.getElementsByClassName ) {
	document.getElementsByClassName = function(cl, tag) {
	   var els, matches = [],
	      i = 0, len,
	      regex = new RegExp('(?:\\s|^)' + cl + '(?:\\s|$)');
	 
	   // If no tag name is specified,
	   // we have to grab EVERY element from the DOM	 
	   els = document.getElementsByTagName(tag || "*");
	   if ( !els[0] ) return false;

	   for ( len = els.length; i < len; i++ ) {
	      if ( els[i].className.match(regex) ) {
	         matches.push( els[i]);
	      }
	   }
	   return matches; // an array of elements that have the desired classname
	};
}
 
// Very simple implementation. We're only checking for an id, class, or tag name.
// Does not accept CSS selectors in pre-querySelector browsers.
var $ = function(el, tag) {
   var firstChar = el.charAt(0);
 
   if ( document.querySelectorAll ) return document.querySelectorAll(el);
 
   switch ( firstChar ) {
      case "#":
         return document.getElementById( el.slice(1) );
      case ".":
         return document.getElementsByClassName( el.slice(1), tag );
      default:
         return document.getElementsByTagName(el);
   }
};

// Usage
$('#container');
$('.box'); // any element with a class of box
$('.box', 'div'); // look for divs with a class of box
$('p'); // get all p elements

Unfortunately, the legacy method isn't quite so minimal. Honestly, at this point, you should use a library. jQuery is highly optimized for working with the DOM, which is why it's so popular! The example above will certainly work, however, it doesn't support complex CSS selectors in older browsers; that task is just a wee-bit more complicated!


Summary

It's important for me to note that that I'm not encouraging you to abandon jQuery. I use it in nearly all of my projects. That said, don't always be willing to embrace abstractions without taking a bit of time to research the underlying code.

I'd like this posting to serve as a living document, of sorts. If you have any of your own (or improvements/clarifications for my examples), leave a comment below, and I'll sporadically update this posting with new items. Bookmark this page now! Lastly, I'd like to send a hat-tip to this set of examples, which served as the impetus for this post.

Related Posts
  • Web Design
    HTML & CSS
    Build a Dynamic Grid with Salvattore and Bootstrap in 10 MinutesSalvatorre thumb
    Today, we will use Salvattore in combination with Twitter Bootstrap 3 to make a responsively awesome flowing grid structure.Read More…
  • Code
    Web Development
    Using Polymer to Create Web ComponentsPolymer wide retina preview
    Polymer will help you to easily create Web Components for your web apps, using a similar syntax to HTML.Read More…
  • Web Design
    Typography
    Getting the Hang of Hanging PunctuationGrand motel text effect
    Hanging Punctuation is a powerful typographic tool for creating optically aligned bodies of text. Unfortunately, it has been largely forgotten on the web... until now. We’ll take a look at the value of hanging punctuation and how you can partially implement it using a little JavaScript and a CSS rule which has been around for years.Read More…
  • Code
    HTML5
    HTML5: Battery Status APIPdl54 preview image@2x
    The number of people browsing the web using mobile devices grows every day. It's therefore important to optimize websites and web applications to accommodate mobile visitors. The W3C (World Wide Web Consortium) is well aware of this trend and has introduced a number of APIs that help with this challenge. In this article, I will introduce you to one of these APIs, the Battery Status API.Read More…
  • Web Design
    HTML/CSS
    Creating Friendlier, “Conversational” Web FormsForm retina
    Web forms are constantly a hot topic when it comes to web design and user interaction. The reasons for this are vast, but one of the more obvious reasons is that forms are the most basic way for a user to input information into your application. In this article, we'll discuss a few techniques that allow your forms to respond to the user's input, while helping to obscure unnecessarily confusing or overwhelming elements.Read More…
  • Code
    HTML & CSS
    Intro to Shadow DOMShadow dom retina preview
    Take any modern web page and you will notice that it invariably contains content stitched together from a variety of different sources; it may include the social sharing widgets from Twitter or Facebook or a Youtube video playing widget, it may serve a personalized advertisement from some ad-server or it may include some utility scripts or styles from a third party library hosted over CDN and so on. And if everything is HTML based (as is preferred these days) there is a high probability of collisions between the markup, scripts or styles served from various sources. Generally, namespaces are employed to prevent these collisions which solve the problem to some extent, but they don't offer Encapsulation.Read More…