Video icon 64
Learn to Code. Start your free trial today.
Advertisement

Uncovering jQuery's Hidden Features

by
Student iconAre you a student? Get a yearly Tuts+ subscription for $45 →

jQuery is not always as it appears. There's a lot of cool stuff going on under the surface, and there are many methods just waiting to be discovered, and many potential usages of jQuery's API that you may not have considered before. In this article I'll be taking you through a few of the not-so-obvious things I've discovered about jQuery.

1. Understand jQuery!

When you call 'jQuery' what happens?

The jQuery function itself is very simple:

jQuery = function (selector, context) {
    // The jQuery object is actually just the init constructor 'enhanced'
    return new jQuery.fn.init(selector, context);
};

Under its skin, the jQuery function (commonly referred to as the "wrapper" function) simply returns an instantiated jQuery object -- i.e. an instance of the 'jQuery.fn.init' constructor.

This is useful to know; with this information we know that each time we call 'jQuery' we're actually creating a totally unique object with a set of properties. jQuery is clever in that it gives you an object that can be treated as an array. Each of your elements (all together, commonly known as the "collection") is referenced within the object under a numerical index, just like within an array. And jQuery also gives this object a 'length' property, just as you would expect from an array. This opens up a world of possibilities. For one, it means that we can borrow some functionality from 'Array.prototype'. jQuery's 'slice' method is a good example of this -- modified from the source:

/* ... jQuery.fn.extend({ ... */
slice: function() {
    return this.pushStack(
        Array.prototype.slice.apply( this, arguments ),
        "slice",
        Array.prototype.slice.call(<WBR>arguments).join(",")
    );
},
/* ... */

The native 'slice' method doesn't care that 'this' is not a real array-- it'll be fine with anything that's got a 'length' property and [0], [1], [2] etc.

There are some other interesting properties within this jQuery object -- '.selector' and '.context' will, most of the time, reflect the arguments that you pass into 'jQuery(...)'.

var jqObject = jQuery('a');
jqObject.selector; // => "a"

One thing that's important to note is that jQuery will sometimes give you new jQuery objects to work with. If you run a method that changes the collection in some way, such as '.parents()', then jQuery won't modify the current object; it'll simply pass you a brand new one:

var originalObject = jQuery('a');
var anotherObject = originalObject.parents();

originalObject === anotherObject; // => false

All methods that appear to mutate the collection in some way return a brand new jQuery object -- you can still access the old object though, via '.end()', or more verbosely, via '.prevObject'.

2. Bread-and-butter Element Creation

Central to jQuery's DOM capabilities, is its element creation syntax. 1.4 brought with it an entirely new way to create your elements quickly and succinctly. E.g.

var myDiv = jQuery('<div/>', {
    id: 'my-new-element',
    class: 'foo',
    css: {
        color: 'red',
        backgrondColor: '#FFF',
        border: '1px solid #CCC'
    },
    click: function() {
        alert('Clicked!');
    },
    html: jQuery('<a/>', {
        href: '#',
        click: function() {
            // do something
            return false;
        }
    })
});

As of 1.4 you can pass a second argument to the jQuery function when you're creating an element -- the object you pass will, for the most part, act as if you were passing it to '.attr(...)'. However, jQuery will map some of the properties to its own methods, for example, the 'click' property maps to jQuery's 'click' method (which binds an event handler for the 'click' event) and 'css' maps to jQuery's 'css' method etc.

To check out what properties map to jQuery's methods, open your console and type 'jQuery.attrFn'.

3. Serialize your Inputs

jQuery provides a method that you can use to serialize all of the inputs within one or more forms. This is useful when submitting data via XHR ("Ajax"). It's been in jQuery for a long time but it's not often talked about and so many developers don't realise it's there. Submitting an entire form via Ajax, using jQuery, couldn't be simpler:

var myForm = $('#my-form');
jQuery.post('submit.php', myForm.serialize(), function(){
    alert('Data has been sent!');
});

jQuery also provides the 'serializeArray' method, which is designed to be used with multiple forms, and the 'param' helper function (under the jQuery namespace) which takes a regular object and returns a query string, e.g.

var data = {
    name: 'Joe', 
    age: 44,
    profession: 'Web Developer'
};

jQuery.param(data); // => "name=Joe&age=44&profession=<WBR>Web+Developer"

4. Animate Anything

jQuery's 'animate' method is probably the most flexible of jQuery's methods. It can be used to animate pretty much anything, not just CSS properties, and not just DOM elements. This is how you would normally use 'animate':

jQuery('#box').animate({
    left: 300,
    top: 300
});

When you specify a property to animate (e.g. 'top') jQuery checks to see if you're animating something with a style property ('element.style'), and it checks if the specified property ('top') is defined under 'style' -- if it's not then jQuery simply updates 'top' on the element itself. Here's an example:

jQuery('#box').animate({
    top: 123,
    foo: 456
});

'top' is a valid CSS property, so jQuery will update 'element.style.top', but 'foo' is not a valid CSS property, so jQuery will simply update 'element.foo'.

We can use this to our advantage. Let's say, for example, that you want to animate a square on a canvas. First let's define a simple constructor and a 'draw' method that'll be called on every step of the animation:

function Square(cnvs, width, height, color) {

    this.x = 0;
    this.y = 0;
    this.width = width;
    this.height = height;
    this.color = color;
    
    this.cHeight = cnvs.height;
    this.cWidth = cnvs.width;
    this.cntxt = cnvs.getContext('2d');
    
}

Square.prototype.draw = function() {

    this.cntxt.clearRect(0, 0, this.cWidth, this.cHeight);
    this.cntxt.fillStyle = this.color;
    this.cntxt.fillRect(this.x, this.y, this.width, this.height);
    
};

We've created our 'Square' constructor, and one of its methods. Creating a canvas and then animating it couldn't be simpler:

// Create a <canvas/> element
var canvas = $('<canvas/>').appendTo('body'<WBR>)[0];
canvas.height = 400;
canvas.width = 600;

// Instantiate Square
var square = new Square(canvas, 70, 70, 'rgb(255,0,0)');

jQuery(square).animate({
    x: 300,
    y: 200
}, {
    // 'draw' should be called on every step
    // of the animation:
    step: jQuery.proxy(square, 'draw'),
    duration: 1000
});

This is a very simple effect, but it does clearly demonstrate the possibilities. You can see it in action here: http://jsbin.com/ocida (this will only work in browsers that support the HTML5 canvas)

5. jQuery.ajax Returns the XHR Object

jQuery's Ajax utility functions ('jQuery.ajax', 'jQuery.get', 'jQuery.post') all return an 'XMLHttpRequest' object which you can use to perform subsequent operations on any request. For example:

var curRequest;

jQuery('button.makeRequest').<WBR>click(function(){
    curRequest = jQuery.get('foo.php', function(response){
        alert('Data: ' + response.responseText);
    });
});

jQuery('button.cancelRequest')<WBR>.click(function(){
    if (curRequest) {
        curRequest.abort(); // abort() is a method of XMLHttpRequest
    }
});

Here we're making a request whenever the 'makeRequest' button is clicked -- and we're cancelling the active request if the user clicks the 'cancelRequest' button.

Another potential usage is for synchronous requests:

var myRequest = jQuery.ajax({
    url: 'foo.txt',
    async: false
});

console.log(myRequest.<WBR>responseText);

Read more about the 'XMLHttpRequest' object and also be sure to check out jQuery's Ajax utilities.

6. Custom Queues

jQuery has a built-in queuing mechanism that's used by all of its animation methods (all of which use 'animate()' really). This queuing can be illustrated easily with a simple animation:

jQuery('a').hover(function(){
    jQuery(this).animate({<WBR>paddingLeft:'+=15px'});
}, function(){
    jQuery(this).animate({<WBR>paddingLeft:'-=15px'});
});

Quickly hovering over a bunch of anchors and then hovering over them again will cause the animations to queue up and occur one at a time -- I'm sure many of you have witnessed this queuing effect before. If not, check it out here: http://jsbin.com/aqaku

The 'queue' method is similar to the well-known 'each' method in how it's called. You pass a function, which will eventually be called for each of the elements in the collection:

jQuery('a').queue(function(){
    jQuery(this).addClass('all-<WBR>done').dequeue();
});

Passing just a function to 'queue' will cause that function to be added to the default 'fx' queue, i.e. the queue used by all animations done by jQuery. Therefore, this function will not be called until all current animations occurring on each element in the collection (in this case, all anchors) have completed.

Notice that we're adding a class of 'all-done' in the function above. As outlined, this class will only be added when all current animations are complete. We're also calling the 'dequeue' method. This is very important, as it will allow jQuery to continue with the queue (i.e. it lets jQuery know that you're finished with whatever you're doing). jQuery 1.4 provides another way of continuing the queue; instead of calling 'dequeue', simply call the first argument passed to your function:

jQuery('a').queue(function(<WBR>nextItemInQueue){
    // Continue queue:
    nextItemInQueue();
});

This does exactly the same, although it's slightly more useful in that it can be called anywhere within your function, even within a mess of closures (that typically destroy the 'this' keyword). Of course, pre-jQuery-1.4 you could just save a reference to 'this', but that would get a bit tiresome.

To add a function to a custom queue, simply pass your custom queue's name as the first argument and the function as the second:

jQuery('a').queue('<WBR>customQueueName', function(){
    // Do stuff
    jQuery(this).dequeue('<WBR>customQueueName');
});

Notice that, since we're not using the default 'fx' queue, we also have to pass our queue's name to the 'dequeue' method, in order to allow jQuery to continue with our custom queue.

Read more about 'queue', 'dequeue' and 'jQuery.queue'.

7. Event Namespacing

jQuery provides a way for you to namespace events, which can be very useful when authoring plugins and third-party components. If needed, the user of your plugin can effectively disable your plugin by unbinding all event handlers that it's registered.

To add a namespace when registering an event handler, simply suffix the event name with a period and then your unique namespace (e.g. '.fooPlugin'):

jQuery.fn.foo = function() {
    
    this.bind('click.fooPlugin', function() {
        // do stuff
    });
     
    this.bind('mouseover.<WBR>fooPlugin', function() {
        // do stuff
    });
    
    return this;
};

// Use the plugin:
jQuery('a').foo();

// Destroy its event handlers:
jQuery('a').unbind('.<WBR>fooPlugin');

Passing just the namespace to 'unbind' will unbind all event handlers with that namespace.

Conclusion

So which ones did I miss? Any helpful features that you feel jQuery doesn't document well enough? Let's discuss in the comments!

Advertisement