jQuery Pluginization

bocoup

bocoup

“Cowboy” Ben Alman

benalman.com
github.com/cowboy
@cowboy

Basic Functionality

(This is not very jQuery-like)

    function longUrl( elem ) {
      var href = elem.attr( "href" ),
          api = "http://www.longurlplease.com/api/v1.1?callback=?";
      
      // Fetch JSON data.
      jQuery.getJSON( api, { q: href }, function(data){
        if ( data[ href ] ) {
          // Update the element's title attribute.
          elem.attr( "title", data[ href ] );
        }
      });
    };
    
    // This is no fun!
    longUrl( $("a") );
    
    // Wouldn't you much rather do this?
    $("a").longUrl();
  

More jQuery-Like

(Implicit iteration, chaining, amazing!)

    jQuery.fn.longUrl = function() {
      return this.each(function(){
        var elem = jQuery(this),
            href = elem.attr( "href" ),
            api = "http://www.longurlplease.com/api/v1.1?callback=?";
        
        // Fetch JSON data.
        jQuery.getJSON( api, { q: href }, function(data){
          if ( data[ href ] ) {
            // If the data is valid, update the element's title attribute.
            elem.attr( "title", data[ href ] );
          }
        });
      });
    };
    
    // So much better!
    $("a").longUrl().addClass( "yay-i-am-totally-chainable" );
  

Consider Performance

(Don't over-optimize, just optimize)

    // Store fetched long URLs here.
    var cache = {};
    
    jQuery.fn.longUrl = function() {
      return this.each(function(){
        var elem = jQuery(this),
            href = elem.attr( "href" ),
            api = "http://www.longurlplease.com/api/v1.1?callback=?";
        
        if ( cache[ href ] ) {
          // URL exists in cache, so use it.
          elem.attr( "title", cache[ href ] );
          
        } else {
          // Fetch JSON data.
          jQuery.getJSON( api, { q: href }, function(data){
            if ( data[ href ] ) {
              // If the data is valid, update the cache.
              cache[ href ] = data[ href ];
              
              // Update the element's title attribute.
              elem.attr( "title", cache[ href ] );
            }
          });
        }
      });
    };
  

Use a Closure!

(Less global vars == less compatibility headaches)

    (function($){
      
      // Store fetched long URLs here.
      var cache = {};
      
      $.fn.longUrl = function() {
        return this.each(function(){
          var elem = $(this),
              href = elem.attr( "href" ),
              api = "http://www.longurlplease.com/api/v1.1?callback=?";
          
          if ( cache[ href ] ) {
            // URL exists in cache, so use it.
            elem.attr( "title", cache[ href ] );
            
          } else {
            // Fetch JSON data.
            $.getJSON( api, { q: href }, function(data){
              if ( data[ href ] ) {
                // If the data is valid, update the cache.
                cache[ href ] = data[ href ];

                // Update the element's title attribute.
                elem.attr( "title", cache[ href ] );
              }
            });
          }
        });
      };
      
    })(jQuery);
  

Keep it DRY

(Why write something twice, when you can write it once?)

    (function($){
      
      // Store fetched long URLs here.
      var cache = {};
      
      $.fn.longUrl = function() {
        return this.each(function(){
          var elem = $(this),
              href = elem.attr( "href" ),
              api = "http://www.longurlplease.com/api/v1.1?callback=?";
          
          // Lengthen the element's URL.
          function lengthen( url ) {
            elem.attr( "title", url );
          };
          
          if ( cache[ href ] ) {
            // URL exists in cache, so use it.
            lengthen( cache[ href ] );
            
          } else {
            // Fetch JSON data.
            $.getJSON( api, { q: href }, function(data){
              if ( data[ href ] ) {
                // If the data is valid, update the cache.
                cache[ href ] = data[ href ];

                // Update the element's title attribute.
                lengthen( cache[ href ] );
              }
            });
          }
        });
      };
      
    })(jQuery);
  

Options

(Sensible parameterization == less editing, down the road)

    (function($){
      
      // Store fetched long URLs here.
      var cache = {};
      
      $.fn.longUrl = function( attr ) {
        return this.each(function(){
          var elem = $(this),
              url = elem.attr( attr || "href" ),
              api = "http://www.longurlplease.com/api/v1.1?callback=?";
          
          // Lengthen the element's URL.
          function lengthen( url ) {
            elem.attr( "title", url );
          };
          
          if ( cache[ url ] ) {
            // URL exists in cache, so use it.
            lengthen( cache[ url ] );
            
          } else {
            // Fetch JSON data.
            $.getJSON( api, { q: url }, function(data){
              if ( data[ url ] ) {
                // If the data is valid, update the cache.
                cache[ url ] = data[ url ];

                // Update the element's title attribute.
                lengthen( cache[ url ] );
              }
            });
          }
        });
      };
      
    })(jQuery);
  

More Options

(There must be a better way than this)

    (function($){
      
      // Store fetched long URLs here.
      var cache = {};
      
      $.fn.longUrl = function( attr, lengthen ) {
        // If only one argument is passed, and that argument is a function,
        // it must be `lengthen, so `attr` must have been omitted.
        if ( $.isFunction( attr ) ) {
          lengthen = attr;
          attr = null;
        }
        
        // If lengthen is not set, use a default function.
        lengthen = lengthen || function( elem, url ) {
          elem.attr( "title", url );
        };
        
        return this.each(function(){
          var elem = $(this),
              url = elem.attr( attr || "href" ),
              api = "http://www.longurlplease.com/api/v1.1?callback=?";
          
          if ( cache[ url ] ) {
            // URL exists in cache, so use it.
            lengthen( elem, cache[ url ] );
            
          } else {
            // Fetch JSON data.
            $.getJSON( api, { q: url }, function(data){
              if ( data[ url ] ) {
                // If the data is valid, update the cache.
                cache[ url ] = data[ url ];
                
                // Update the element's title attribute.
                lengthen( elem, cache[ url ] );
              }
            });
          }
        });
      };
      
    })(jQuery);
  

Better Options

(This is the better way)

    (function($){
      
      // Store fetched long URLs here.
      var cache = {};
      
      $.fn.longUrl = function( options ) {
        // Some sensible defaults.
        var defaults = {
          attr: "href",
          lengthen: function( elem, url ) {
            elem.attr( "title", url );
          }
        };
        
        // Override defaults with specified options.
        options = $.extend( {}, defaults, options );
        
        return this.each(function(){
          var elem = $(this),
              url = elem.attr( options.attr ),
              api = "http://www.longurlplease.com/api/v1.1?callback=?";
          
          if ( cache[ url ] ) {
            // URL exists in cache, so use it.
            options.lengthen( elem, cache[ url ] );
            
          } else {
            // Fetch JSON data.
            $.getJSON( api, { q: url }, function(data){
              if ( data[ url ] ) {
                // If the data is valid, update the cache.
                cache[ url ] = data[ url ];
                
                // Update the element's title attribute.
                options.lengthen( elem, cache[ url ] );
              }
            });
          }
        });
      };
      
    })(jQuery);
  

Best Options!

(Options can be overriden both globally and per-call)

    (function($){
      
      // Store fetched long URLs here.
      var cache = {};
      
      $.fn.longUrl = function( options ) {
        // Override defaults with specified options.
        options = $.extend( {}, $.fn.longUrl.options, options );
        
        return this.each(function(){
          var elem = $(this),
              url = elem.attr( options.attr ),
              api = "http://www.longurlplease.com/api/v1.1?callback=?";
          
          if ( cache[ url ] ) {
            // URL exists in cache, so use it.
            options.lengthen( elem, cache[ url ] );
            
          } else {
            // Fetch JSON data.
            $.getJSON( api, { q: url }, function(data){
              if ( data[ url ] ) {
                // If the data is valid, update the cache.
                cache[ url ] = data[ url ];
                
                // Update the element's title attribute.
                options.lengthen( elem, cache[ url ] );
              }
            });
          }
        });
      };
      
      // Some sensible defaults.
      $.fn.longUrl.options = {
        attr: "href",
        lengthen: function( elem, url ) {
          elem.attr( "title", url );
        }
      };
      
    })(jQuery);
  

Even more jQuery-Like

(How can the lengthen callback be improved?)

    (function($){
      
      // Store fetched long URLs here.
      var cache = {};
      
      $.fn.longUrl = function( options ) {
        // Override defaults with specified options.
        options = $.extend( {}, $.fn.longUrl.options, options );
        
        return this.each(function(){
          var that = this,
              url = $(this).attr( options.attr ),
              api = "http://www.longurlplease.com/api/v1.1?callback=?";
          
          if ( cache[ url ] ) {
            // URL exists in cache, so use it.
            options.lengthen.call( that, cache[ url ] );
            
          } else {
            // Fetch JSON data.
            $.getJSON( api, { q: url }, function(data){
              if ( data[ url ] ) {
                // If the data is valid, update the cache.
                cache[ url ] = data[ url ];
                
                // Update the element's title attribute.
                options.lengthen.call( that, cache[ url ] );
              }
            });
          }
        });
      };
      
      // Some sensible defaults.
      $.fn.longUrl.options = {
        attr: "href",
        lengthen: function( url ) {
          $(this).attr( "title", url );
        }
      };
      
    })(jQuery);
  

Even more Generalized

(The only thing left out is $.kitchenSink)

    (function($){
      
      // Store fetched long URLs here.
      var cache = {};
      
      $.longUrl = function( url, callback ) {
        var api = "http://www.longurlplease.com/api/v1.1?callback=?";
        
        if ( cache[ url ] ) {
          // URL exists in cache, so use it.
          callback( cache[ url ] );
          
        } else {
          // Fetch JSON data.
          $.getJSON( api, { q: url }, function(data){
            if ( data[ url ] ) {
              // If the data is valid, update the cache.
              cache[ url ] = data[ url ];
              
              // Call `callback` for this element + long url.
              callback( cache[ url ] );
            }
          });
        }
      }
      
      $.fn.longUrl = function( options ) {
        // Override defaults with specified options.
        options = $.extend( {}, $.fn.longUrl.options, options );
        
        return this.each(function(){
          var that = this,
              url = $(this).attr( options.attr );
          
          // Fetch long URL and call `lengthen` when done.
          $.longUrl( url, function( long_url ){
            options.lengthen.call( that, long_url );
          });
        });
      };
      
      // Some sensible defaults.
      $.fn.longUrl.options = {
        attr: "href",
        lengthen: function( url ) {
          $(this).attr( "title", url );
        }
      };
      
    })(jQuery);
  

Recap