Web & Mobile Development JLW288

04.jQuery.plugins

Plugins

With the new eyePhone, you can watch, listen, ignore your friends, stalk your ex, download porno on a crowded bus, even check your email while getting hit by a train.

Extensibility

  • jQuery is built in such a way that it is very extensible
  • Extensions are released as plugins
    • (Cluttered) Overview available at http://plugins.jquery.com/
    • Some bigger extensions are released as projects
      • jQuery UI — abstractions for low-level interaction and animation, advanced effects and high-level, themeable widgets
      • jQuery Mobile — Touch-Optimized Web Framework for Smartphones & Tablets

Using Plugins

  • Include it in your HTML page after you've included jQuery and the new functionality becomes available
    <!doctype html>
    <html lang="en">
    <head>
    	<title>jQuery Plugins - 01</title>
    </head>
    <body>
    
    	<h1>jQuery.reverseText</h1>
    
    	<p>Hi, I will be reversed!</p>
    
    	<script src="js/jquery-1.7.1.min.js"></script>
    	<script src="js/jquery.reversetext.js"></script>
    	<script>
    		$(document).ready(function() {
    			$('p').reverseText();
    		});
    	</script>
    </body>
    </html>

Plugin Types

  • Plugins can extend the core/utility functions
    • Not performed on elements
    • viz. helper functions like $.trim()
    • viz. a function $.randomColor() to get a random color returned.
  • Plugins can extend the supported selectors/expressions
    • viz. $(':lists') to select all <ul>, <ol> and <dl> elements
    • viz. $(':inview') to select all elements inside the current viewport
    • viz. $(':external') to select all links that link to external websites
  • Plugins can extend jQuery instance functions
    • viz. roll out your own effects such as $(els).slideAndRemove();
    • viz. reverse the text of an element $(els).reverseText();
    • viz. inject descriptions next to images based on their alt attribute with $('img[alt]').addDescription();

Extensibility Side note

  • The jQuery you download actually is a jQuery Object bundled with some common plugins
    • Some of the jQuery functionality originally were external plugins (e.g. Ajax & Dimensions) and were later on moved into jQuery itself
    • jQuery Deconstructed gives a nice view on how jQuery is organized

Writing Plugins

Yeah, well... I'm gonna go build my own theme park, with blackjack and hookers. In fact, forget the park!

Stuff you should know about jQuery

  • The core object of jQuery is named jQuery
    • It is mapped to $ for convenience
      // These two have the same result
      var p1 = $('p'),
          p2 = jQuery('p');
  • jQuery Object functions like .slideUp() are defined in jQuery.prototype
    • jQuery.prototype is mapped to jQuery.fn (or $.fn) for convenience

Writing Plugins

  • Three types of plugins ...
    1. Plugins can extend the core/utility functions
    2. Plugins can extend the supported selectors/expressions
    3. Plugins can extend jQuery instance functions
  • ... equals three ways to writing plugins
    1. Extending the jQuery Object ($)
    2. Extending $.expr
    3. Extending jQuery's .prototype ($.fn)

Writing Utility Functions

Do refrigerators still come in cardboard boxes?
Yeah, but the rents are outrageous.

Writing Utility Functions

  • Use jQuery's built-in .extend() to extend itself
    If only one argument is supplied to $.extend(), this means the target argument was omitted. In this case, the jQuery object itself is assumed to be the target. By doing this, we can add new functions to the jQuery namespace. This can be useful for plugin authors wishing to add new methods to jQuery.

Example

  • Method to get a random color
    var randomColor = function() {
    	return '#' + Math.floor(Math.random() * 16777216).toString(16);
    }
  • jQuery version:
    $.extend({
    	randomColor : function() {
    		return '#' + Math.floor(Math.random() * 16777216).toString(16);
    	}
    })
  • As you're passing an object into $.extend, it's possible to pass in/define multiple functions at once

Usage

  • The name of the function has become available on $
    <!doctype html>
    <html lang="en">
    <head>
    	<title>jQuery Plugins - 02</title>
    </head>
    <body>
    
    	<h1>jQuery.randomColor</h1>
    
    	<p>Click me to change backgroundColor</p>
    
    	<script src="js/jquery-1.7.1.min.js"></script>
    	<script src="js/jquery.randomcolor.js"></script>
    	<script>
    		$(document).ready(function() {
    			$('p').css('cursor','pointer').on('click', function(e) {
    				$(this).css('background', $.randomColor());
    			});
    		});
    	</script>
    </body>
    </html>

Code Improvement

  • Use an IIFE to make sure $ refers to jQuery
    (function($) {
    	$.extend({
    		randomColor : function() {
    			return '#' + Math.floor(Math.random() * 16777216).toString(16);
    		}
    	})
    })(jQuery);
    • Always use the IIFE

Writing Custom Expressions

Yes, I'd like to make a collect call.

Writing Custom Expressions

  • Custom expressions are stored in jQuery.expr[':']
  • Use jQuery.extend() to extend jQuery.expr[':'] with a new function
    jQuery.extend(jQuery.expr[':'], {
    	example : function(elem, index, meta, stack) {
    		// elem - is a current DOM element
    		// index - the current loop index in stack
    		// meta - meta data about your selector
    		// stack - stack of all elements to loop
    
    		// Return true to include current element
    		// Return false to explude current element
    	}
    });
    • Most of the time you'll only need the elem parameter
      • elem is not a jQuery Instance!
    • The meta param is an array with info about your selector, allows you to use parameters with your selector

Example

  • Select all possible lists
    (function($) {
    	$.extend($.expr[':'], {
    		lists : function(elem, index, match, stack) {
    			var els = ['ul','ol','dl'];
    		    return ($.inArray(elem.nodeName.toLowerCase(), els) > -1);
    		}
    	});
    })(jQuery);
  • Usage
    $(':lists').css('border', '1px solid red');
  • Notes
    • These selectors perform badly (slow), as they loop all elements
      • Best to only use to refine a selection: $('#somediv').find(':lists');
    • This performs better (and does the same): $('ul,ol,dl');

Writing Instance Functions

Somehow he has cobbled together a random assortment of other brain waves into a working mind.

Writing Instance Functions

  • Extend jQuery.prototype/$.fn as you'd extend a regular .prototype
    (function($) {
    	$.fn.myPlugin = function() {
    		// ... plugin code here
    	}
    })(jQuery);
  • Also possible
    (function($) {
    	$.fn.extend({
    		myPlugin : function() {
    			// ... plugin code here
    		}
    	});
    })(jQuery);
  • Usage
    $('...').myPlugin();

What is this?

  • The this inside your plugin is a jQuery instance (in contrast to other situations where it's a DOM element)
    (function($) {
    	$.fn.slideUpAndRemove = function() {
    		this.slideUp("slow", function() { // <-- jQuery Instance
    	        $(this).remove();
    	    });
    	}
    })(jQuery);

Chainability

  • To not break chainability one must return the instance
    (function($) {
    	$.fn.slideUpAndRemove = function() {
    		return this.slideUp("slow", function() {
    	        $(this).remove();
    	    });
    	}
    })(jQuery);
  • Mostly you'll loop all selected elements and still must return the instance
    (function($) {
    	$.fn.myPlugin = function() {
    		return this.each(function() {
    	        // ... plugin code here
    	    });
    	}
    })(jQuery);
  • In short: always return this

Example

  • Inject the link locations after each link
    (function($) {
    	$.fn.showLinkLocation = function() {
    		return this.filter('a').each(function() {
    			$(this).append( ' (' + this.href + ')');
    		});
    	};
    }(jQuery));
  • Usage
    $('a').showLinkLocation().css('text-decoration','none');

Working with parameters (1)

  • Possible, best to work with an object and with defaults
    (function($) {
    
    	$.fn.hilight = function(options) {
    
    		var defaults = {
    			foreground: 'red',
    			background: 'yellow'
    		};
    
    		// Extend our default options with those provided.
    		var o = $.extend({}, defaults, options);
    
    		return this.each(function() {
    			// ... plugin code here
    		});
    
    	};
    
    }(jQuery));

Working with parameters (2)

  • Best to put defaults outside the plugin definition, so they can be adjusted by the user
    (function($) {
    
    	$.fn.hilight = function(options) {
    
    		// Extend our default options with those provided.
    		var o = $.extend({}, $.fn.hilight.defaults, options);
    
    		return this.each(function() {
    			// ... plugin code here
    		});
    
    	};
    
    	$.fn.hilight.defaults = {
    		foreground: 'red',
    		background: 'yellow'
    	};
    
    }(jQuery));

Putting it together

  • Plugin code
    (function($) {
    	$.fn.hilight = function(options) {
    		var o = $.extend({}, $.fn.hilight.defaults, options);
    		return this.each(function() {
    			var $this = $(this);
    			$this.css({
    				'color': o.foreground,
    				'background-color': o.background
    			});
    			$this.html('<strong>' + $this.html() + '</strong>');
    		});
    	};
    	$.fn.hilight.defaults = {
    		foreground: 'red',
    		background: 'yellow'
    	};
    }(jQuery));
  • Usage
    $('em').hilight();
    $('a').hilight({foreground: '#00F'});

Advanced Instance Functions

This is it. The moment we should've trained for.

Advanced Instance Functions (1)

  • When allowing one to remove hilight, we could do this
    (function($) {
    	$.fn.hilight = function(options) {
    		// ... plugin code here
    	};
    	$.fn.hilightRemove = function() {
    		// ... plugin code here
    	};
    	$.fn.hilight.defaults = {
    		foreground: 'red',
    		background: 'yellow'
    	};
    }(jQuery));
    • This is bad idea, things will become cluttered when offering lots of functionality
    • We've had this problem before, and then solved it by namespacing our code

Advanced Instance Functions (2)

  • What if we could do this?
    $('em').hilight();
    $('em').on('click', function() {
    	$(this).hilight('remove');
    });
    • Code much cleaner
    • No function loitering

A Design Pattern

  • jQuery has a design pattern for this
    (function($) {
    	var methods = {
    		init : function(options) {
    			// ... plugin code here
    		},
    		otherfunction : function() {
    			// ... plugin code here
    		}
    	};
    	$.fn.pluginName = function(method) {
    		if (methods[method]) {
    			return methods[method].apply(
    				this, Array.prototype.slice.call(arguments, 1));
    		} else if (typeof method === 'object' || ! method) {
    			return methods.init.apply(this, arguments);
    		} else {
    			$.error('Method ' + method + ' does not exist on jQuery.plugin');
    		}
    	};
    })(jQuery);
    • Call plugin without a function name and init will be called.

Putting it together (1)

(function($) {
	var methods = {
		init : function(options) {
			var o = $.extend({}, $.fn.hilight.defaults, options);
			return this.each(function() {
				var $this = $(this);
				$this.css({
					'color': o.foreground,
					'background-color': o.background
				});
				$this.html('<strong>' + $this.html() + '</strong>');
			});
		},
		remove : function() {
			return this.each(function(){
				var $this = $(this);
				$this.css({
					'color': 'inherit',
					'background-color': 'transparent'
				});
				$this.html($this.find('strong').html());
			});
		}
	};

// ...

Putting it together (2)

// ...

	$.fn.hilight = function(method) {
		if (methods[method]) {
			return methods[method].apply(
				this, Array.prototype.slice.call(arguments, 1));
		} else if (typeof method === 'object' || ! method) {
			return methods.init.apply(this, arguments);
		} else {
			$.error('Method ' +  method + ' does not exist on jQuery.hilight');
		}
	};

	$.fn.hilight.defaults = {
		foreground: 'red',
		background: 'yellow'
	};

})(jQuery);

Putting it together (3)

Usage
// Add highlights
$('em').hilight();
$('a').hilight({foreground: '#00F'});

// remove highlight after 1 second
setTimeout(function() {
	$('em').hilight('remove').css('border', '1px solid red');
}, 1000);

One more thing

Questions?

Sources

ikdoeict.be