jQuery for JavaScript programmers
15th August 2007
When jQuery came out back in January 2006, my first impression was that it was a cute hack. Basing everything around CSS selectors was a neat idea (see getElementsBySelector) but the chaining stuff looked like a bit of a gimmick and the library as a whole didn’t look like it would cover all of the bases. I wrote jQuery off as a passing fad.
Over the past few months it’s become clear to me exactly how wrong I was. jQuery is an exceptionally clever piece of engineering. It neatly encapsulates an extraordinary range of common functionality, and provides a clever plugin API for any functionality not included by default. It takes a core abstraction—that of a selection of DOM elements—and extracts as much mileage out of it as possible. Most importantly, it does so in a way that obeys best practices and plays well with other JavaScript code.
Most introductions to jQuery focus on designers and inexperienced developers. I’m going to try to explain why jQuery should be of interest to experienced programmers as well.
Namespacing
The key to writing good, reusable JavaScript is to zealously manage your namespace. JavaScript provides a single global namespace (the window object), and many programmers (and some libraries) add symbols to this with abandon. Global variables are evil! Smart developers minimise their number of global objects, using techniques like the module pattern.
jQuery introduces just one symbol to the global namespace: the jQuery function/object. Everything else is either a directy property of jQuery or a method of the object returned by calls to the jQuery function.
What about language enhancements? Most libraries provide some variation of map, filter and strip, functions that are sadly missing from the JavaScript engines that ship with most browsers. Some libraries directly extend JavaScript’s built-in String and Array classes, but this can be a risky strategy. String.prototype
and Array.prototype
are themselves global namespaces, and adding properties to them brings the same risk of collisions as being careless with regular globals.
jQuery provides a number of language enhancement functions, but each one is made available as a property of the jQuery object: jQuery.each
, jQuery.extend
, jQuery.grep
, jQuery.map
, jQuery.merge
and jQuery.trim
. There’s no chance of these colliding with someone else’s code.
The infamous $ function
I wasn’t being entirely truthful when I said that jQuery was the only global symbol introduced: the $
symbol is also set up as a shortcut for jQuery. Thankfully, this is done in a non-destructive way: if you need your old $
function back (if you are already using code based on Prototype, for example) you can call jQuery.noConflict()
to revert to the old $
function.
$
function for jQuery without colliding with some other use of the global $
function, the jQuery documentation suggests the following idiom:
(function($) {
// Within this block, $ is a reference to jQuery
// Neat, huh?
})(jQuery);
Attaching everything to the $
symbol was one of the things that made me initially dismiss jQuery as a gimmick. For some reason thinking of it in terms of the jQuery symbol makes everything seem a lot more sensible, even though I’m happy to use the $
shortcut in my own code.
Selecting some elements
Every jQuery operation starts with selecting one or more nodes from the DOM. jQuery’s selection syntax (really a domain specific language) is an interesting hybrid of CSS 1, 2, bits of CSS 3, some XPath and a few custom extensions as well. I won’t describe it in detail here, but here are some useful examples:
jQuery('div.panel')
- All divs with class=“panel”
jQuery('p#intro')
- The paragraph with id=“intro”
jQuery('div#content a:visible')
- All visible links inside the div with id=“content”
jQuery('input[@name=email]')
- All input fields with name=“email”
jQuery('table.orders tr:odd')
- “odd” numbered rows in a table with class “orders”
jQuery('a[@href^="http://"]')
- All external links (links that start with http://)
jQuery('p[a]')
- All paragraphs that contain one or more links
Of particular interest from the above are :visible
and :odd
, which are jQuery specific extensions. Also note that the attribute selectors use an @ sign, in common with XPath rather than CSS 2.
The selection language is extremely rich, and is similar to regular expressions in that time taken to learn it will pay off many times over.
Doing stuff with them
The object returned by a jQuery selector call is an interesting beast. It represents a collection of DOM elements, and behaves a bit like an array—it has a length property, items can be accessed by index and (most importantly) Firebug treats it as an array when displaying it in the interactive console. This is a clever illusion; the collection is actually a jQuery object, incorporating a large number of methods which can be used to query, modify and extend the collection of selected elements.
There are three principle categories of jQuery methods: those that manipulate all of the matched elements, those that return a value from the first matched object, and those that modify the selection itself.
I won’t list all of the methods (see visualjquery.com for that), but I’ll illustrate with some examples. If you have Firebug you can try these out interactively: use this Insert jQuery bookmarklet first to load the jQuery library in to any page, then paste the code examples in to the Firebug console.
jQuery('div#primary').width(300);
- Set the width of div id=“primary” to 300 px.
jQuery('p').css('line-height', '1.8em');
- Apply a line-height of 1.8em to all paragraphs.
jQuery('li:odd').css({color: 'white', backgroundColor: 'black'});
- Apply two CSS rules to every other list item; note that the css() function can take an object instead of two strings.
jQuery('a[@href^="http://"]').addClass('external').attr('target', '_blank');
- Add a class of “external” to all external links (those beginning with http://), then add target=“_blank” for good measure. This makes use of chaining, described below.
jQuery('blockquote').each(function(el) { alert(jQuery(this).text()) });
- Iterate over every blockquote on the page, and alert its textual content (excluding HTML tags).
jQuery('a').html('Click here!');
- Replace all link text on the page with the insidious “Click here!”.
Here are some examples of methods that read values from the first matched element:
var width = jQuery('div').width();
- How wide is the first div on the page?
var src = jQuery('img').attr('src');
- What’s the src attribute of the first image on the page?
var color = jQuery('h1').css('color');
- What colour is the first h1?
There’s a pleasing symmetry at work here: the methods used to set attributes (when passed two arguments, or an object representing multiple settings) can instead be used to read values if called with only one argument. This symmetry is used throughout jQuery, making the API much easier to commit to memory.
Finally, there are methods that modify the set of selected elements itself. Many of these also provide simpler ways of traversing the DOM:
jQuery('div').not('[@id]')
- Returns divs that do not have an id attribute.
jQuery('h2').parent()
- Returns all elements that are direct parents of an h2.
jQuery('blockquote').children()
- Returns all elements that are children of a blockquote.
jQuery('p').eq(4).next()
- Find the fifth paragraph on the page, then find the next element (its direct sibling to the right).
jQuery('input:text:first').parents('form')
- Find the form parent of the first input type=“text” field on the page. The optional argument to parents() is another selector.
Chaining
The jQuery team frequently boast about jQuery’s support of chaining, even to the point of declaring that “jQuery is designed to change the way that you write JavaScript” right on the front page. Personally I found this a big turn-off, so I’m happy to say that you can make good use of jQuery while avoiding lengthy chains of methods entirely.
That said, chaining can be used for some neat tricks. In addition to chaining a bunch of DOM manipulation methods together, you can use jQuery’s end()
method to push and pop a stack of the selected element contexts. This is a little hard to explain; essentially, every time you use a method that changes the selected set of elements (such as children()
or filter()
) you can later use end()
to revert back to the previous selection. Jesse Skinner gives a neat example of this in action in his tutorial Simplify Ajax development with jQuery:
$('form#login')
// hide all the labels inside the form with the 'optional' class
.find('label.optional').hide().end()
// add a red border to any password fields in the form
.find('input:password').css('border', '1px solid red').end()
// add a submit handler to the form
.submit(function(){
return confirm('Are you sure you want to submit?');
});
That whole thing is essentially a one-liner. It selects a form, finds some elements within that form, applies changes to them, reverts the selection back to the original form and assigns a submit()
handler to it.
It’s a cute concept, but you don’t have to use it if you don’t want to. I’m quite happy splitting my code up with a few self-documenting variable names.
DOM manipulation
jQuery offers a few smart ways of making large scale manipulations to the DOM. The first is quite surprising: the jQuery function can take a snippet of HTML which it will turn in to a DOM element (it actually looks out for a string that starts with a less than sign):
var div = jQuery('<div>Some text</div>');
You can use chaining to add attributes to the div once it has been created:
var div = jQuery('<div>Some text</div>').addClass('inserted').attr('id', 'foo');
Now append it to the body tag:
div.appendTo(document.body)
Or prepend it to a known location using a selector:
div.prependTo('div#primary')
Handling events
All JavaScript libraries need an event handling utility and jQuery’s is no exception. As with attr()
and css()
, the event methods serve dual purpose: call them with a function to assign an event handler; call them without to simulate that event being triggered:
jQuery('p').click(function() { jQuery(this).css('background-color', 'red'); });
- Set up paragraphs so that when you click them they turn red.
jQuery('p:first').click()
- Send a fake “click” to the first paragraph on the page.
Similar functions exist for the other browser events—mouseover, keyup and so on. Note that within an event handler the ’this’ keyword is set to the element that triggered the event; using jQuery(this)
is a common idiom to enable jQuery methods on that element.
A couple of event related functions deserve a special mention:
jQuery('a').hover(function() {
jQuery(this).css('background-color', 'orange');
}, function() {
jQuery(this).css('background-color', 'white');
});
hover()
is a shortcut for setting up two functions that run onmouseover and onmouseout.
jQuery('p').one('click', function() { alert(jQuery(this).html()); });
one()
sets up an event handler that will be removed after the first time it has been fired. The above example causes all paragraphs to alert their contents once the first time they are clicked.
jQuery also supports custom events, through the bind()
and trigger()
methods (for which click()
and friends are just shortcuts). Custom events can take arguments, handled using an array passed to trigger()
:
jQuery(document).bind('stuffHappened', function(event, msg) {
alert('stuff happened: ' + msg);
});
jQuery(document).trigger('stuffHappened', ['Hello!']);
Unobtrusive scripting
This is a topic that is very dear to me. I still believe that the best Web applications are the ones that are still usable with scripting turned off, and that the best way to achieve that is through unobtrusive scripting, with events being assigned to elements after the regular page has been loaded (see Unobtrusive Scripting and Hijax for more).
jQuery has excellent support for this. Firstly, the node selection metaphor is core to both jQuery and unobtrusive scripting as a whole. Secondly, jQuery ships with a solution to the window.onload problem based on Dean Edwards’ work getting a “DOM loaded” event to work cross-browser. You can set a function up to run when the DOM is ready for it like so:
jQuery(document).ready(function() {
alert('The DOM is ready!');
});
Even better, you can shortcut the above by passing your function directly to jQuery()
:
jQuery(function() {
alert('The DOM is ready!');
});
jQuery and Ajax
jQuery has the best API for Ajax I’ve seen in any of the major libraries. The most simple form of an Ajax call looks like this:
jQuery('div#intro').load('/some/fragment.html');
This performs a GET request against /some/fragment.html and populates div#intro with the returned HTML fragment.
The first time I saw this, I was unimpressed. It’s a neat shortcut, but what if you want to do something more advanced like display an Ajax loading indicator? jQuery exposes custom events (ajaxStart
, ajaxComplete
, ajaxError
and more) for you to hook in this kind of code. It also offers a comprehensive low-level API for more complex Ajax interactions:
jQuery.get('/some/script.php', {'name': 'Simon'}, function(data) {
alert('The server said: ' + data);
}); // GET against /some/script.php?name=Simon
jQuery.post('/some/script.php', {'name': 'Simon'}, function(data) {
alert('The server said: ' + data);
}); // POST to /some/script.php
jQuery.getJSON('/some.json', function(json) {
alert('JSON rocks: ' + json.foo + ' ' + json.bar);
}); // Retrieves and parses /some.json as JSON
jQuery.getScript('/script.js'); // GET and eval() /script.js
Plugins
Considering the amount of functionality you get out of the box, jQuery is actually pretty small—it comes in at 20KB when minified, even smaller when gzipped. Additional functionality outside the framework is handled using plugins, which can (and do) add brand new methods to the existing jQuery instance object. If you want to be able to run:
jQuery('p').bounceAroundTheScreenAndTurnGreen();
jQuery’s plugin mechanism provides documented hooks for adding that method to the jQuery system. The ease with which these can be created has attracted an impressive community of plugin authors; the Plugin directory lists well over 100.
One really nice touch is that you can add custom selectors in a similar way to custom methods. The moreSelectors plugin adds things like “div:color(red)” to match divs with red text, for example.
Leaky abstractions
In my new-found respect for jQuery, I’ve been struggling with one philosophical blocker. For the past few years, I’ve been advising people to only pick a JavaScript library if they were willing to read the source code and figure out exactly how it works. My reasoning was based on Joel Spolsky’s classic essay The Law of Leaky Abstractions, which points out that the more complexity your APIs are hiding, the more trouble you’ll be in when some of that complexity leaks through. Browsers are the leakiest abstraction of them all, so being able to dig yourself out of a hole when the library fails to cover for you is of paramount importance.
jQuery uses some really arcane tricks to achieve its functionality—parts of it (like the selector code) are positively terrifying. If understanding exactly how the library works is necessary, jQuery must be a poor choice for the majority of developers. However, the enormous popularity of jQuery combined with a distinct lack of horror stories demonstrates that this is not the case.
I think I’m going to have to reconsider my advice. The way the library works isn’t the issue: it’s understanding the underlying issues, and knowing what kind of browser differences there are and what kind of techniques your library is using to fix them. No library can protect you 100% against weird browser behaviour, but as long as you have a grounding in the underlying theory you should be able to figure out if a problem stems from your own code, your library or the underlying implementation.
To conclude
I hope I’ve made the case that jQuery isn’t just another library—there are enough interesting ideas in there to teach even the most hardened of JavaScript programmers some new tricks. Even if you don’t intend on using it, it’s still worth spending some time exploring the jQuery ecosystem.
More recent articles
- Claude 3.5 Haiku - 4th November 2024
- W̶e̶e̶k̶n̶o̶t̶e̶s̶ Monthnotes for October - 30th October 2024
- You can now run prompts against images, audio and video in your terminal using LLM - 29th October 2024