There’s tons of doc out there explaining how to chain Mootools events, but very little of it really explains what’s really going on, why the code looks the way it does, and most importantly, how to get it to do what you want. This is my attempt to fill in the gap with Mootools 1.2.1.
Chaining
The basic problem is that the way people do transitions in javascript. Every transition library effectively just does something very fancy aroundsetTimeout
. This works well if you’re just sliding in an element, but if you want to slide out an element, then slide in another one, you get something that looks very weird, if you’re not careful. Transitions aren’t atomic functions. When the javascript interpreter hits a setTimeout
in Transition A, it moves on to Transition B, and vice versa. Effectively they happen at the same time.
To get what you want, Mootools includes the Chain class. From the doc:
A Utility Class which executes functions one after another, with each function firing after completion of the previous. Its methods can be implemented with Class:implement into any Class, and it is currently implemented in Fx and Request. In Fx, for example, it is used to create custom, complex animations.
Chain
is implemented by the Fx.Tween class. The Fx.Tween class is the class that allows you to transition between various style properties. This super simple example shows how to switch between background colors for the element with id="example1"
(download mootools_chaining):
var fx = new Fx.Tween($('example1'));
fx.start('backgroundColor', 'red').chain(function() {
this.start('backgroundColor', 'blue')
}).chain(function() {
this.start('backgroundColor', 'green')
});
All I’m doing here is creating a tween on the element example1
. Then I turn the background red, blue, and green (in that order).
Ok, that looks easy enough. But what if I want to do something more complex, such as fade out one element (example2_header1
), and fade in another (example2_header2
)? The first thought would be to do something like:
// THE WRONG WAY TO DO IT!
var fx = new Fx.Tween($('example2_header1'));
fx.start('opacity', 0).chain(function() {
this.set('display', 'none');
}).chain(function() {
$('example2_header2').setStyle('display', 'block');
}).chain(function() {
$('example2_header2').tween('opacity', 1);
});
This example will never show the element example2_header2
. To understand why this is, we need to look at the difference between the Fx.Tween.set
and Fx.Tween.start
methods.
Fx.Tween.set
set: function(property, now){
if (arguments.length == 1){
now = property;
property = this.property || this.options.property;
}
this.render(this.element, property, now, this.options.unit);
return this;
}
Fx.Tween.start
start: function(property, from, to){
if (!this.check(arguments.callee, property, from, to)) return this;
var args = Array.flatten(arguments);
this.property = this.options.property || args.shift();
var parsed = this.prepare(this.element, this.property, args);
return this.parent(parsed.from, parsed.to);
}
Notice the difference in return. Fx.Tween.start
calls into the Fx
parent class, but Fx.Tween.set
does not. The Fx.start
method creates a timer, and when that completes, it calls the Fx.onComplete method:
onComplete: function(){
this.fireEvent('complete', this.subject);
if (!this.callChain()) this.fireEvent('chainComplete', this.subject);
},
Aha! This is why the second example fails. For non-fx type function calls, this.callChain()
must be called manually. Thus, the code to get it all working is:
var fx = new Fx.Tween($('example2_header1'));
fx.start('opacity', 0).chain(function() {
this.set('display', 'none');
this.callChain();
}).chain(function() {
$('example2_header2').setStyle('display', 'block');
this.callChain();
}).chain(function() {
$('example2_header2').tween('opacity', 1);
});
});
You can download the full working example (including the HTML around it).