Archive

Archive for the ‘jquery’ Category

How to fulfill your own feature request -or- Duck Punching with jQuery!

February 23rd, 2010

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…

(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 $:

(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:

    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:

        // 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:

(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:

(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.

Paul Irish jquery

On jQuery's live()

January 22nd, 2010

Since the release of 1.4, there's been a lot of discussion about live(). Now that it's almost as fully featured as bind(), it's getting quite a workout.

One of the larger calls to action is to introduce $.live(selector,eventType,handler). There are two primary reasons I've seen behind this proposal:

  1. There is a performance hit for the initial selection in $('a.whatever').live(.. that isn't neccessary
  2. live() cannot work on any arbitrary collection of DOM elements in jQuery object, as a side effect of a very different internal operation, thus providing an inconsistent developer experience than all other $.fn.foo methods.

$.live for performance

Sizzle is used to find any matching elements before the live method discards them an only uses the jQobj.selector property and context. It's a waste, I agree. But you can avoid it. Zachleat posted this technique first. Then I stole a code snippet from @janl to show off in my jQuery Performance talk for jQCon. This inspired furf's jQuery.live() patch. And now jmar777 has resurrected that discussion.

I do not think the performance gain in $.live is worth adding it to jQuery core, by any means. Let's take an example.

Let's say we're dealing with $(selector).live('click',fn)

On every click on the page, the event is caught at the context.. the document. But evt.target points to the top-most element that was clicked on.
jQuery then traverses from evt.target through each ancestor of that node all the way up to document checking if (jQuery(elem).is(selector)).[source]

I think a reasonable estimated depth of evt.targets is like 8 (From the document to HTML element to BODY and so on.. a depth of 20 is not uncommon by any means). And lets say we have an average 5 clicks on a page.
That's 40 total checks against the selector (plus the original one done at bind time).
So in my opinion a 1/41 speed gain isn't worth changing the API.

Scoping the delegation a context, however has a much larger impact on performance: $(selector,elem).live('click',fn) Here, only clicks within the context are caught, so not only is the parent traversal depth much more shallow, but that entire sequence is avoided for all events occurring outside that area.

The current API to take advantage of this optimization requires code like $('a.awesome', $('#box')[0]).live(… Yuck! So I wrote a patch to deliver this same optimization to any ID-rooted selectors. (The perk being that everyone who learns #id-rooted selectors are fast for selecting get a related perf boost here). But as Justin Meyer pointed out in the comments, it's not as flexible as live() currently is.

$.fn.live cannot operate on any jQuery-wrapped collection of elements

$('a.foo').filter('.bar').live(... // fails
$('a.link').parent().nextAll().live(... // fails
$(this).live(... // fails

Quite simply, $.fn.live needs to be at the top of a chain to guarantee the .selector and .context properties of the jQuery object represent that set. Unlike other $.fn methods, live utilizes those properties and not the actual collection of elements that result from selection, traversing, and filtering.

$.live() and let $.die()

What are the possible solutions?

  1. Turn on a dirty flag in traversal methods, so if you try and live() that collection, it will throw an error.
  2. Deprecate $.fn.live and use $.live(selector[,context],type,handler)
  3. Use $.live to create a "live jQuery object" that defaults to live-bindings: $.live(selector,context).click(fn).mouseenter(fn).unbind("click") [source]
  4. Change $.fn.live to operate from a context: $(context).live('selector', event, fn) This bakes the performance benefit from above right in.

Any others? What do you guys think? Is there a solution that preserves backwards-compatibility while solving the issue?

btw- thx to jmar777, yehuda katz, ben alman, ajpiano, brandon aaron and julian aubourg for their great thoughts on the issue.

The people in this discussion got together and hashed out what would make sense. John Resig then landed a new delegate/undelegate API. It's good. :)

Paul Irish jquery

Updates from all around - Dec 2009

December 19th, 2009

A lot of the work I'm doing doesn't turn into new posts so here's a summary of what's been going on:

yayQuery

Since it was announced here, we've come a long way. We just put out our 7th episode (now in HD!). The show format has tightened up and we're putting a lot of time into making it even better. Follow us on twitter or subscribe to catch all the goodness. We also put out an entire episode devoted to what's coming in jQuery 1.4 (aka 1.MOAR), so peep it if you're curious.

jQuery Singalong plugin

I was a finalist at Boston's Music Hack Day with the jQuery Singalong plugin. It essentially allows you to add timed annotations to the HTML5 audio and video elements. (Also it was the first time I legitimately used javascript's Infinity :) In addition, I put out the (very!) alpha jQuery Bouncing Ball plugin. Check out the demos to get a taste

Modernizr updates

Faruk and I pushed out Modernizr 1.1, which can now detect what audio/video formats your browser supports, all sorts of HTML5 forms goodness, and some other ones like localStorage and applicationCache. We're delighted to be part of Mark Pilgrim's Dive into HTML5 book and pumped to help push the edge of progressive enhancement.

@font-face and webfonts

It's a rapidly changing landscape, so I've been keeping all my font-y posts up to date:

@font-face feature detection

I've updated the existing feature detection script to use a smaller file. (Thanks to the Font Squirrel for the help). You would use this script to detect if support is present or not; perhaps implement a Cufon fallback. These new improvements will make their way into Modernizr 1.2.

I've also added on a isFontFaceSupported browser sniffing alternative. The main benefit here is that you're guaranteed accurate results synchronously. (The true feature detection can't do that, though the new small size is much faster in Firefox.)

Other bits and bobs

The nice guys at that other jQuery Podcast had me on the show in November. I talked about, well.. basically all of the stuff above. :) It's a good show.

I tweeted about a new quick approach to a for loop:

for (var i = -1, len = arr.length; ++i < len; ) // the cool guy loop

The fun part is that the counting expression (the third part) is empty. It's faster than your standard loops, but reverse while() is still quicker. @jdalton beat me to this one, for the record.

Lastly, along with my writers, I've been keeping my mp3 blog Aurgasm fresh with new music you'll love. I've got a lot more things in the pipeline, so right after my Christmas and New Years jaunt around Germany, Denmark, and Scotland, you'll see them soon. Happy December ya'll.

Paul Irish javascript, jquery, typography

Infinite Scroll 1.4 is out. Twitter-style now supported!

November 30th, 2009

I just released version 1.4 of the jQuery Infinite Scroll plugin. Along with that comes a new release of the wordpres plugin. A few small bugs were fixed in the wordpress code, improving compatibility with other plugins and themes.

I've also added the ability to manually trigger loading in new content. This allows you to do a twitter or facebook style of interaction where the user must click an element for the new data to come in.

A demo showing that functionality. The documentation of how to use this style is on the documentation page.

Paul Irish jquery

Introducing yayQuery - A jQuery podcast

November 6th, 2009

A few friends of mine got together to put together a new podcast we're calling…

yayQuery!

2009.11.13. Now visit the podcast at:
http://yayquery.com

In our inaugural episode I'm joined by Rebecca Murphey, Alex Sexton (of goto.js fame), and Adam Sontag (ajpiano). We talk about…

  • Underscore.js - a great utility belt (very handy for Ruby/Python folks), comes with John Resig's microtemplating script and lots more
  • Thickbox - Rest in peace. Also alternatives: Colorbox, jQuery UI Dialog
  • jQuery on mobile. Phonegap, XUI, jQTouch, going it alone
  • Anti-Pattern of the week: css(key,val)
  • $var and Hungarian notation
  • … and dancing

Moar?

We hope to do this again (and again) and make things better. We're also working on proper podcast feeds and a site.

But really this is an experiment, for now. We'd love any and all feedback.

… and follow us on twitter to get your fix!! @yayquery

Paul Irish jquery

jQuery Conference 2009

September 13th, 2009

Currently rounding up the last day of #jqcon and it's one of the best events I've been too. Crazy to see jQuery's annual meetup double in size each year and scale with talent and organization just as nicely. I somehow tricked the attendees into voting two talk proposals up (thanks!), so I got to discuss two things this weekend.

I'd love to any feedback you may have and plan to write on these topics in more detail, but for now.. my slides:

jQuery Anti-Patterns for Performance & Compression

Employing Custom Fonts… Now!

Paul Irish jquery

jQuery Anti-Patterns for Performance

September 11th, 2009

Debounced resize() jQuery plugin

August 11th, 2009

If you've ever attached an event handler to the window's resize event, you have probably noticed that while Firefox fires the event slow and sensibly, IE and Webkit go totally spastic.

PPK lays it out like this:

  • In IE, Safari, and Chrome many resize events fire as long as the user continues resizing the window.
  • Opera uses as many resize events, but fires them all at the end of the resizing.
  • Firefox fires one resize event at the end of the resizing.

When I first saw John Hann's debounce post, this use case is what I immediately thought of.

This isn't exactly throttling, but it's close. Basically debouncing will fire your function after a threshold of time (e.g. 100ms) has elapsed since the last time it's tried to fire. Throttling would withhold subsequent firings, but debouncing waits for the last one and runs that.

Take a look at the difference on the demo.

The code for smartresize:

(function($,sr){
 
  // debouncing function from John Hann
  // http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/
  var debounce = function (func, threshold, execAsap) {
      var timeout;
 
      return function debounced () {
          var obj = this, args = arguments;
          function delayed () {
              if (!execAsap)
                  func.apply(obj, args);
              timeout = null; 
          };
 
          if (timeout)
              clearTimeout(timeout);
          else if (execAsap)
              func.apply(obj, args);
 
          timeout = setTimeout(delayed, threshold || 100); 
      };
  }
	// smartresize 
	jQuery.fn[sr] = function(fn){  return fn ? this.bind('resize', debounce(fn)) : this.trigger(sr); };
 
})(jQuery,'smartresize');
 
 
// usage:
$(window).smartresize(function(){  
  // code that takes it easy...
});

Pretty straightforward stuff. What else do you think would be an appropriate use of debounce()?

Paul Irish jquery

jQuery idleTimer plugin

June 3rd, 2009

There are a few cases where you want to know if the user is idle. Namely:

  • You want to preload more assets
  • You want to grab their attention to pull them back
  • You want close their banking session after 5 minutes of inactivity. (Jerk!)
  • You want the site to sneak off the screen and see if they notice ;-)

Nick Zakas wrote a script for YUI3 to handle these cases. His writeup has a great description of the architecture approach he took to the script.

In my jQuery adaptation, I did a few different things:

  1. Leveraged event namespaces for easy unbinding
  2. Considered mousewheel as activity, in addition to keyboard and mouse movement.
  3. Gave it a bit more jQuery-ish of an API

To use:

// idleTimer() takes an optional argument that defines the idle timeout
// timeout is in milliseconds; defaults to 30000
$.idleTimer(10000);
 
 
$(document).bind("idle.idleTimer", function(){
 // function you want to fire when the user goes idle
});
 
 
$(document).bind("active.idleTimer", function(){
 // function you want to fire when the user becomes active again
});
 
// pass the string 'destroy' to stop the timer
$.idleTimer('destroy');

Get the source on github (4.6k unminified)
View the demo

Note: If you want to change the timeout interval, you'll have to destroy the existing timer first.

2009.09.22: I've updated the idleTimer script for a few more features…
// you can query if the user is idle or not with data()
$.data(document,'idleTimer');  // 'idle'  or 'active'
 
// you can get time elapsed since user when idle/active
$.idleTimer('getElapsedTime'); // time since state change in ms

You can get the latest code, naturally, on github.

Paul Irish jquery

Flip text upside-down jQuery plugin

April 2nd, 2009

For April Fools day, Youtube enabled you to view any page and video, totally upside down.

Pretty cute and clever, so I unapologetically stole their code and re-appropriated it as a jQuery plugin. As you can see, I have very valuable time. :-p

Download: jquery.textflip.js 1.4K

Demo: Click me to flip all this posts text

Paul Irish jquery