New plugin: Class.Refactor
Whenever I start a new project (like Iminta.com) one of the first things I find myself doing is redefining how I want my classes to work in that specific environment. Maybe I want all my Request.HTML instances to change the document body’s cursor property (in css) to the hour glass (I do this in ajax heavy apps - I want the user to know that the browser is working on something). Or maybe I want all my popup divs to be at a specific zIndex, or all my effects to default to 100ms instead of 500.
So I often have code like this:
var StickyWin = new Class({
Extends: StickyWin, //extends itself onto itself.
options: {
zIndex: 100
}
});
This works great most of the time. MooTools is designed to let you do this sort of thing. Yes, I could subclass it so that I have StickyWin2 or something, but a lot of the times classes are designed to work together. Consider Fx.Tween. Let’s say I want all my effects for a given app to default to 100ms. If I subclass Fx.Tween and create, say, Fx.MyTween with this new default setting, that would work. But then Element.tween isn’t using my new setting and so I can’t use that shortcut (unless I go replace Element.tween, too).
Or consider my Waiter class. I have this integrated into Request.HTML so that with a single option I can automatically have an ajax spinner on any DOM element being updated. Here’s what that looks like:
Request.HTML = new Class({
Extends: Request.HTML,
options: {
useWaiter: false,
waiterOptions: {},
waiterTarget: false
},
initialize: function(options){
this._send = this.send;
this.send = function(options){
if(this.waiter) this.waiter.start().chain(this._send.bind(this, options));
else this._send(options);
return this;
};
this.parent(options);
if (this.options.useWaiter && ($(this.options.update) || $(this.options.waiterTarget))) {
this.waiter = new Waiter(this.options.waiterTarget || this.options.update, this.options.waiterOptions);
['onComplete', 'onException', 'onCancel'].each(function(event){
this.addEvent(event, this.waiter.stop.bind(this.waiter));
}, this);
}
}
});
Now by simply passing in useWaiter: true I enable the ajax spinner right inside Request.HTML.
The catch
This is fine and dandy, but there’s a practice in MooTools (that arrived in version 1.11) that I mirror in my own code wherein subclasses are attached to the namespace of the parent. So, Fx.Tween extends Fx. If I extend Fx onto itself, I reassign that namespace to a new pointer:
Fx = new Class({ Extends: Fx, ....other properties....});
In doing this, I destroy references to anything on Fx that weren’t part of the class, which includes Fx.Tween, Fx.Css, etc. That won’t do at all.
Class.refactor
All that Class.refactor does is allow you to assign new properties to a class in the same manner as extending the class onto itself without destroying the namespace. You can download this script on the download page and view the docs.
This functionality comes in two flavors: A static method on the Class namespace and a method available on all classes defined after this script is included in your environment. Why two flavors? Because any class that is defined before Class.Refactor.js is included in your environment won’t have the method baked in. So:
Class.refactor - the static method
Use this method on any class that existed before Class.refactor was included in your environment (i.e. all the MooTools classes defined before the Clientcide javascript):
Fx = Class.refactor(Fx, {
options: {
duration: 100
}
});
//all new instances of Fx start with a default duration of 100
//Fx.Css, Fx.Tween, etc are unaltered in any way
Class.refactor - the ‘baked in’ method
For all other classes (the Clientcide code and your own classes) you can just use the instance method:
StickyWin.refactor({
options: {
zIndex: 100
}
});
//all new instances of StickyWin will have //options.zIndex// set to //100//
//StickyWin.Fx, StickyWin.Modal, etc. are unaltered in any way
Note that the static method will work here, too; this is just a shortcut.
Refactoring Class Families
What happens if you wanted to take the example above (where we set the default zIndex on StickyWin) and apply it to all the StickyWin classes? Well, you’ll have to refactor them all. That would look like this:
[StickyWin, StickyWin.Fx, StickyWin.Modal, StickyWin.Fx.Modal, etc].each(function(cls){
cls.refactor({
...new properties...
});
});
November 19th, 2008 at 8:13 pm
This is awesome. I’ve wanted this functionality for a while now but have been too lazy to investigate/implement it. Rad.
November 20th, 2008 at 10:01 am
Nice work Aaron. A simple and elegant solution to this problem.