Skip to content

Extending jQuery UI Widgets

Last modified February 10, 2015.

This page is obsolete; current versions are on my github pages at github.bililite.com/extending-widgets.html. This page is being kept here for historical purposes.

Avoiding Bloat in Widgets

A while back, Justin Palmer wrote an excellent article on "Avoiding Bloat in Widgets." The basic premise (no suprise to anyone whose ever dealt with object-oriented programming) is that your widgets should not do everything possible; they should do one thing well but be flexible enough to allow others to modify them.

He describes two ways of extending objects: subclassing and aspect-oriented programming (AOP). Subclassing creates new classes, while AOP modfies the methods of a single object. Both can be useful.

So let's make Palmer's Superbox widget (it just moves randomly about the screen with mouse clicks):


var Superbox = {
	_init: function(){
		var self = this;
		this.element.click(function(){
			self.move();
		});
	},
	move: function(){
		this.element.css (this._newPoint());
	},
	_newPoint: function(){
		return {top: this._distance(), left: this._distance()};
	},	
	_distance: function(){
		return Math.round (Math.random()*this.options.distance);
	},
	options: {
		distance: 200
	}
};
$.widget ('ui.superbox', Superbox);

I've factored apart a lot of the code, so we have plenty of "hooks" to use to extend the method without copying code. Note that none of the code refers to "superbox" directly, so we can create subclasses that don't know the superclass's name.

Experiment 1 (Click Me)

Subclassing Widgets

Note that jQuery UI 1.9 incorporates most of these ideas, so you may not need to use $.ui.widget.subclass at all; it is built in. Use $.widget('ui.subclass', $.ui.baseclass);. See my post on this.

Download the code.

The widget factory ($.widget) allows you to use one widget as the base for another, but that's not the same as subclassing; it copies all the methods from one widget to the next.

So let's use real inheritance to make a new class, supererbox, that moves rather than jumps to its new location. I'll use Richard Cornford's variation on Douglas Crockford's prototypal inheritance pattern to simplify subclassing (you could use a fancier one like Dean Edward's Base, or manipulate prototypes yourself). I'll use Dean Edward's technique for calling overridden superclass functions.


var object = (function(){
    function F(){}
    return (function(o){
        F.prototype = o;
        return new F();
    });
})();

And create an empty "base" widget class.


$.widget('ui.widget',{});

And add a method to create subclasses that inherit from the base.


var OVERRIDE = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; 
$.ui.widget.subclass = function subclass (name){
	$.widget(name,{}); // Slightly inefficient to create a widget only to discard its prototype, but it's not too bad
	name = name.split('.');
	var widget = $[name[0]][name[1]], superclass = this, superproto = superclass.prototype;
	
	
	var args = $.makeArray(arguments); // get all the add-in methods
	var proto = args[0] = widget.prototype = object(superproto); // and inherit from the superclass
	$.extend.apply(null, args); // and add them to the prototype
	widget.subclass = subclass;
	
	// Subtle point: we want to call superclass _create, _init and _destroy if they exist
	// (otherwise the user of this function would have to keep track of all that)
	// and we want to extend the options with the superclass's options. We copy rather than subclass
	// so changing a default in the subclass won't affect the superclass
	for (key in proto) if (proto.hasOwnProperty(key)) switch (key){
		case '_create':
			var create = proto._create;
			proto._create = function(){
				superproto._create.apply(this);
				create.apply(this);
			};
		break;
		case '_init':
			var init = proto._init;
			proto._init = function(){
				superproto._init.apply(this);
				init.apply(this);
			};
		break;
		case 'destroy':
			var destroy = proto.destroy;
			proto.destroy = function(){
				destroy.apply(this);
				superproto.destroy.apply(this);
			};
		break;
		case 'options':
			var options = proto.options;
			proto.options = $.extend ({}, superproto.options, options);
		break;
		default:
			if ($.isFunction(proto[key]) && $.isFunction(superproto[key]) && OVERRIDE.test(proto[key])){
				proto[key] = (function(name, fn){
					return function() {
						var tmp = this._super;
						this._super = superproto[name];
						try { var ret = fn.apply(this, arguments); }   
						finally { this._super = tmp; }					
						return ret;
					};
				})(key, proto[key]);
			}
		break;
	}
};

And use it like this to create a new, subclassable superbox and a subclass of that:


$.ui.widget.subclass('ui.superbox',{
	_init: function(){
		var self = this;
		this.element.click(function(){
			self.move();
		});
	},
	move: function(){
		this.element.css (this._newPoint());
	},
	_newPoint: function(){
		return {top: this._distance(), left: this._distance()};
	},	
	_distance: function(){
		return Math.round (Math.random()*this.options.distance);
	},
	options: {
		distance: 200
	}
});

$.ui.superbox.subclass ('ui.supererbox', {
	// overriding and new methods
	move: function(){
		this.element.animate(this._newPoint(), this.options.speed);
	},
	home: function(){
		this.element.animate({top:0, left:0}, this.options.speed);
	},
	options: {
		speed: 'normal'
	}
});

The function signature is $.namespace.widget.subclass(name <String>, [newMethods <Object>]*), where you can use as many newMethod objects as you want. This lets you use mixin objects, like $.ui.mouse, that add a specific set of methods.

We now have a new widget called supererbox that is just like superbox but moves smoothly.

Experiment 2 (Click Me)

Calling Superclass Methods

If we want to use the superclass methods in our method, we use this._super:


$.ui.supererbox.subclass('ui.superboxwithtext', {
	move: function(){
		this.options.count = this.options.count || 0;
		++this.options.count;
		this.element.text('Move number '+this.options.count);
		this._super(); 
	}
});
Experiment 3 (Click Me)

Aspect Oriented Programming

Aspect oriented programming allows the user of an object to modify its behavior after it has been instantiated. New methods don't so much override the old ones as supplement them, adding code before or after (or both) the original code, without hacking at the original class definition.

We'll add methods for widgets that are stolen straight from Justin Palmer's article:


$.extend($.ui.widget.prototype, { // note that we could extend $.Widget.prototype to add these to all widgets, rather than ones descended from $.ui.widget
	yield: null,
	returnValues: { },
	before: function(method, f) {
		var original = this[method];
		this[method] = function() {
			f.apply(this, arguments);
			return original.apply(this, arguments);
		};
	},
	after: function(method, f) {
		var original = this[method];
		this[method] = function() {
			this.returnValues[method] = original.apply(this, arguments);
			return f.apply(this, arguments);
		}
	},
	around: function(method, f) {
		var original = this[method];
		this[method] = function() {
			var tmp = this.yield;
			this.yield = original;
			var ret = f.apply(this, arguments);
			this.yield = tmp;
			return ret;
		}
	}
});

And now we can use these methods in our code.

For example, let's say we have a cool plugin to make an element pulsate (I know, UI has a pulsate method that does this):


$.fn.pulse = function (opts){
	opts = $.extend ({}, $.fn.pulse.defaults, opts);
	for (i = 0; i < opts.times; ++i){
		this.animate({opacity: 0.1}, opts.speed).animate({opacity: 1}, opts.speed);
	}
	return this;
};
$.fn.pulse.defaults = {
	speed: 'fast',
	times: 2
};

And we'll create a supererbox object, then make it pulse before moving:


$('#experiment4').supererbox().supererbox('before','move', function() {
	this.element.pulse();
});
Experiment 4 (Click Me)

Or even make it pulse before and after moving:


$('#experiment5').supererbox().supererbox('around','move', function() {
	this.element.pulse();
	this.yield();
	this.element.pulse();
});
Experiment 5 (Click Me)

Note that we didn't create any new classes to get this new behavior; we added the behavior to each object after the object was created.

Note that I did not use the widget factory directly. It may be possible to make this more efficient, but I haven't analyzed the code in jQuery UI 1.8 closely enough.

{ 43 } Comments


  1. Fatal error: Uncaught Error: Call to undefined function ereg() in /home/public/blog/wp-content/themes/barthelme/functions.php:178 Stack trace: #0 /home/public/blog/wp-content/themes/barthelme/comments.php(34): barthelme_commenter_link() #1 /home/public/blog/wp-includes/comment-template.php(1469): require('/home/public/bl...') #2 /home/public/blog/wp-content/themes/barthelme/page.php(22): comments_template() #3 /home/public/blog/wp-includes/template-loader.php(74): include('/home/public/bl...') #4 /home/public/blog/wp-blog-header.php(19): require_once('/home/public/bl...') #5 /home/public/blog/index.php(17): require('/home/public/bl...') #6 {main} thrown in /home/public/blog/wp-content/themes/barthelme/functions.php on line 178