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. sydney phelps | October 26, 2008 at 2:26 pm | Permalink

    impressive…we are very much amused ;)

  2. Felix Nagel | July 1, 2009 at 3:54 pm | Permalink

    Mhh examples seem not to work. Am i stupid? Or is it the code ;-)

  3. Danny | July 5, 2009 at 9:05 pm | Permalink

    @Felix:
    As the note at the top of the post says, this article is obsolete (it relies on jQuery UI 1.5). Please see the updated and maintained version at http://bililite.nfshost.com/blog/extending-jquery-ui-widgets/

Post a Comment

Your email is never published nor shared. Required fields are marked *