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 says:

    impressive…we are very much amused ;)

  2. Felix Nagel says:

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

  3. Danny says:

    @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/

Leave a Reply


Warning: Undefined variable $user_ID in /home/public/blog/wp-content/themes/evanescence/comments.php on line 75