Skip to content

Extending jQuery UI Widgets, The Final Chapter

This page is obsolete (it uses jQuery UI 1.5). Please see the updated page.

OK, this is the final update to the widget subclassing. Rather than creating a new method, $.widget.subclass, I created a single base widget $.ui.widget that does nothing but includes the Aspect-Oriented-Programming code and a subclassing method. I put everything in the $.ui namespace (since namespacing plugins doesn't work anyway, all plugin names need to be globally unique). I removed the callSuper method, since this.callSuper('ui.widget', 'method', args) is no better than just doing it straight, $.ui.widget.prototype.method.apply(this, args).

Without further ado, here's the code (download):


// create the master widget
$.widget("ui.widget",{
	// Aspect Oriented Programming tools from Justin Palmer's article
  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() {
      this.yield = original;
      return f.apply(this, arguments);
    }
  }
});

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

// create a widget subclass (always in the $.ui namespace)
$.ui.widget.subclass = function subclass(name) {
	$.widget('ui.'+name); // Slightly inefficient to create a widget only to discard its prototype, but it's not too bad
	var widget = $.ui[name]; // $.widget should return the object itself!
	widget.subclass = subclass;
	var superclass = this;
	
	widget.prototype = object(this.prototype);
	arguments[0] = widget.prototype; // add the the new methods to the prototype
	$.extend.apply(null, arguments); 
	widget.defaults = object(this.defaults);
	widget.getter = this.getter;
	
	// Subtle point: we want to call superclass init and destroy if they exist
	// (otherwise the user of this function would have to keep track of all that)
	if (widget.prototype.hasOwnProperty('init')){;
	  var init = widget.prototype.init;
		widget.prototype.init = function(){
			superclass.prototype.init.apply(this);
			init.apply(this);
		}
	};
	if (widget.prototype.hasOwnProperty('destroy')){
		var destroy = widget.prototype.destroy;
		widget.prototype.destroy = function(){
			destroy.apply(this);
			superclass.prototype.destroy.apply(this);
		}
	}
	return widget; // address my complaint above
};

And here are the samples from the Extending Widgets tutorial:


//  Experiment 1
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.getData('distance'));
	}
};
$.ui.widget.subclass ('superbox', Superbox);
$.ui.superbox.defaults = { distance: 200 };

$('#experiment1').superbox();

// Experiment 2
$.ui.superbox.subclass ('supererbox', {
	// overriding and new methods
	move: function(){
		this.element.animate(this.newPoint(), this.getData('speed'));
	},
	home: function(){
		this.element.animate({top:0, left:0}, this.getData('speed'));
	}
});
$.ui.supererbox.defaults.speed = 'normal';

$('#experiment2').supererbox();

// Experiment 3
$.ui.supererbox.subclass('superboxwithtext',  {
	move: function(){
		var count = this.getData('count') || 0;
		++count;
		this.setData('count', count);
		this.element.text('Move number '+count);
		$.ui.supererbox.prototype.move.call(this); // note that we could just as well use $.ui.superbox.move for the original, "jumpy" move
	}
});

$('#experiment3').superboxwithtext();

// Experiment 4
$.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
};
$('#experiment4').supererbox().supererbox('before','move', function() {
	this.element.pulse();
});

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

Experiment 1 (Click Me)
Experiment 2 (Click Me)
Experiment 3 (Click Me)
Experiment 4 (Click Me)
Experiment 5 (Click Me)

{ 3 } 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/single.php(44): 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