Paul Irish

Making the www great

How to Fulfill Your Own Feature Request -or- Duck Punching With jQuery!

Wait, what? Well first, duck punching is another name for monkey patching. Monkey Patching is a technique to “extend or modify the runtime code of dynamic languages without altering the original source code.” But then some Ruby hackers decided that since we rely on duck typing so often, a more proper name was in order:

… if it walks like a duck and talks like a duck, it’s a duck, right? So if this duck is not giving you the noise that you want, you’ve got to just punch that duck until it returns what you expect.[1]

In jQuery we can use this to great effect. We’ll employ an AOP-like approach (aspect-oriented programming), maintaining the original function but wrapping it with our enhancement.

Example 1: Adding color support

(This is a silly example, but hopefully it explains the idea. ) You’re fully aware that you can use certain color names in HTML. But sadly, everyone’s favorite Crayola™ color, Burnt Sienna, can’t play along. Well, not until now…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(function($){

    var _oldcss = $.fn.css;

    $.fn.css = function(prop,value){

        if (/^background-?color$/i.test(prop) && value.toLowerCase() === 'burnt sienna') {
           return _oldcss.call(this,prop,'#EA7E5D');
        } else {
           return _oldcss.apply(this,arguments);
        }
    };
})(jQuery);

// and using it...
jQuery(document.body).css('backgroundColor','burnt sienna')

Let’s walk through that a little bit slower, shall we?

First we start off with a self-executing anonymous function that makes a happy closure while remapping jQuery to $:

1
2
3
(function($){
    // ...
})(jQuery);

Now we save a reference to the old css method as a local variable, and set up our new definition for that method:

1
2
3
4
5
var _oldcss = $.fn.css;

$.fn.css = function(prop,value){
    // ....
};

Inside our function definition we have an if statement that breaks up two code paths, the new enhanced route, and the old default:

1
2
3
4
5
6
7
8
9
// if the user passed in backgroundColor and 'burnt sienna'
if (/^background-?color$/i.test(prop) && value.toLowerCase() === 'burnt sienna') {

    // call the old css() method with this hardcoded color value
    return _oldcss.call(this,prop,'#EA7E5D');
} else {
    // otherwise call the old guy normally, taking his return value and passing it on.
    return _oldcss.apply(this,arguments);
}

Scroll back up to see the whole thing together. Can you dig it?

Example 2: $.unique() enhancement

$.unique only winnows an array of DOM elements to contain no duplicates, but what if you want all non-duplicates of any type? Here’s an upgrade:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(function($){

    var _old = $.unique;

    $.unique = function(arr){

        // do the default behavior only if we got an array of elements
        if (!!arr[0].nodeType){
            return _old.apply(this,arguments);
        } else {
            // reduce the array to contain no dupes via grep/inArray
            return $.grep(arr,function(v,k){
                return $.inArray(v,arr) === k;
            });
        }
    };
})(jQuery);

// in use..
var arr = ['first',7,true,2,7,true,'last','last'];
$.unique(arr); // ["first", 7, true, 2, "last"]

var arr = [1,2,3,4,5,4,3,2,1];
$.unique(arr); // [1, 2, 3, 4, 5]

A Duck Punching Pattern

Here’s the bare pattern. Feel free to use this as the basis of your own:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(function($){

    // store original reference to the method
    var _old = $.fn.method;

    $.fn.method = function(arg1,arg2){

        if ( ... condition ... ) {
           return  ....
        } else {           // do the default
           return _old.apply(this,arguments);
        }
    };
})(jQuery);

What else could I use this for?

Some other uses I’ve had or seen for this sort of thing:

So while you’re free to file a feature request of jQuery, most of the time you can actually enhance jQuery’s methods yourself! Enjoy.

2013.01.04: BONUS PICTURE! Thx @SlexAxton for being the best photographer

Comments