Prototype Classes have a sort-of multiple inheritance feature that lets you throw in Ruby-esque mixins when a Class is defined. There's a pretty good tutorial on the Prototype site.
I got a bit fed up my mixins defining not-really-hidden-but-they-start-with-an-underscore-and-have-a-slightly-obscure-name-so-lets-pretend-they-are-actually-private variables. Whenever I wanted to define state on the instance that could be shared between the mixed in functions this was the most obvious way to do this. These aren't proper private variables, they're just normal variables pretending to be
The basic form for a mixin factory is:
var HOP_RUN_MIXIN_FACTORY = {
make:function( likesToHop ) {
// vars here and arguments passed to make are per-Class that uses the mixin
var perClassVaraible = 'one of these per class using the mixin';
/* return the mixin object */
return {
hop:function(){
if( likesToHop )
alert( 'yay!' );
else
alert( 'meh' );
// ...etc ...
}
, run:function(){
// ...etc ...
}
}
}
};
Then, in the classes:
var Rabbit = Class.create( HOP_RUN_MIXIN_FACTORY.make( true ),
{
initialize:function(){
// ...etc...
}
, makeMoreRabbits:function(){
// ...etc...
}
}
);
var Owl = Class.create( HOP_RUN_MIXIN_FACTORY.make( false ),
{
initialize:function(){
// ...etc...
}
, lookArround:function(){
// ...etc...
}
}
);
The point is that this can be used to create slightly different versions of mixins and provides a place to keep per-class variables inside the mixin (eg, in Java, the closest analog is private static fields).
Great code, doesn't fix it!
It'll probably be a long wait until the uber awsomeness that is CSS transitions is well supported and until then I'm using the rather lovely, if somewhat breaky, Scriptaculous (aka Scripty) to fade elements in and out. CSS would be so much nicer for this, keeping the presentation (eg, that something is fading rather than just appearing) out of the Javascript. Oh well, one day.
When you want to fade something in and out, Scripty puts a surprising amount of work put on the programmer. Two conflicting effects acting at the same time on the same element are bad. Weird, flickery things happen. So before creating the Effect.appear you have to check the object isn't already appearing or fading: If appearing do nothing. If fading, cancel fade, start appear. I found I had the code to do this in several places: obvious candidate for moving into a mixin.
So, I started to implement as a Mixin Factory, storing the appear and fade effects inside the make method, ie like the var perClassVaraible in the HOP_RUN_MIXIN_FACTORY. Humph, closures are such a powerful concept but sometimes difficult to see - since the factory method is called once per class rather than once per instantiation, this was only allowing one instance of the class to do the fade in and out thing at a time. So, back to polluting my objects with this._fade_effect and this._appear_effect.
Variation - per-instance state in the mixin
Genuine per-instance mixin variables are possible but you have to ignore Class's mixin feature and mixin manually from the initialize method:
var Rabbit = Class.create({
initialize:function() {
Object.extend( this, HOP_RUN_MIXIN_FACTORY.make() );
}
}
);
One interesting side-effect is that the same mixin can be used to create state per-instance or per-class depending on how the mixin is applied. Maybe useful.
Fade Mixin Factory
Here is the real code I'm using now to fade in and out, in the form of a mixin factory applied from the initialize function. As usual, this is also available from Wikizzle's subversion, which is probably a more recent and/or better version than here:
var FADE_MIXIN_FACTORY =
{
make:function fade_mixin_factory__make( appear_options, fade_options, element ){
var appear_effect, fade_effect;
appear_options = appear_options || {};
fade_options = fade_options || {};
appear_options.afterFinish = (appear_options.afterFinish || function(){})
.wrap( function( proceed ){
proceed()
fade_effect = null;
});
fade_options.afterFinish = (fade_options.afterFinish || function(){})
.wrap( function( proceed ){
proceed();
appear_effect = null;
});
/* return the mixin we made: */
return {
appear: function fade_mixin__appear() {
if( appear_effect ) {
return;
}
if( fade_effect ) {
fade_effect.cancel();
fade_effect = null;
}
appear_options.owner = this;
appear_effect = new Effect.Appear( element || this.toElement(),
appear_options );
}
, fade: function() {
if( fade_effect ) {
return;
}
if( appear_effect ) {
appear_effect.cancel();
appear_effect = null;
}
fade_options.owner = this;
fade_effect = new Effect.Fade( element || this.toElement(),
fade_options );
}
};
}
}