API Docs for: 1.0.0
Show:

File: src/gallery-anim-sequence/js/anim-seq.js

"use strict";

/**
 * @module gallery-anim-sequence
 */

/**********************************************************************
 * Manages a sequence of animations, so you don't have to chain them
 * manually. Each item in the sequence can be a single animation, an array
 * of animations to perform in parallel, a function which performs an
 * immediate action, or a delay in milliseconds.
 * 
 * Pass `sequence` in the configuration to set the initial animation
 * sequence.
 * 
 * This class exposes the same basic API as Y.Anim, so you can pass
 * Y.AnimSequence to anything that just needs to run/pause/stop an
 * animation.
 * 
 * @main gallery-anim-sequence
 * @class AnimSequence
 * @constructor
 * @param config {Object} configuration
 */
function AnimSequence(config)
{
	this._list = [];
	AnimSequence.superclass.constructor.apply(this, arguments);
}

AnimSequence.NAME = "AnimSequence";

AnimSequence.ATTRS =
{
	/**
	 * If true, the animation runs backwards.  Immediate actions receive
	 * the value of reverse as the only argument.
	 * 
	 * @attribute reverse
	 * @type {Boolean}
	 * @default false
	 */
	reverse:
	{
		value:     false,
		validator: Y.Lang.isBoolean
	},

	/**
	 * The number of times the animation should run.  Can be "infinite"
	 * 
	 * @attribute iterations
	 * @type {Number|String}
	 * @default 1
	 */
	iterations:
	{
		value: 1,
		validator: function(value)
		{
			return Y.Lang.isNumber(value) || value == 'infinite';
		}
	},

	/**
	 * The number of times the animation has run.  Resets to zero when
	 * the animation finishes or is stopped.
	 * 
	 * @attribute iterationCount
	 * @type {Number}
	 * @readOnly
	 */
	iterationCount:
	{
		value:    0,
		readOnly: true
	},

	/**
	 * The behavior of the animation when "iterations" > 1:
	 * If "normal", the animation will repeat in the same direction.
	 * If "alternate", the animation will flip "reverse" at the end of the sequence.
	 * 
	 * @attribute direction
	 * @type {String}
	 * @default "normal"
	 */
	direction:
	{
		value: 'normal',
		validator: function(value)
		{
			return value == 'normal' || value == 'alternate';
		}
	},

	/**
	 * Whether or not the animation is currently running.
	 * 
	 * @attribute running
	 * @type {Boolean}
	 * @default false
	 * @readonly
	 */
	running:
	{
		value:     false,
		validator: Y.Lang.isBoolean,
		readOnly:  true
	},

	/**
	 * Whether or not the animation is currently paused.
	 * 
	 * @attribute paused
	 * @type {Boolean}
	 * @default false
	 * @readonly
	 */
	paused:
	{
		value:     false,
		validator: Y.Lang.isBoolean,
		readOnly:  true
	}
};

/**
 * @event start
 * @description Fires when the sequence begins.
 */
/**
 * @event item
 * @description Fires when an item in the sequence begins.
 * @param index {int} the item index
 */
/**
 * @event end
 * @description Fires after the sequence finishes.
 */

/**
 * @event pause
 * @description Fires when the sequence is paused.
 */
/**
 * @event resume
 * @description Fires when the sequence resumes (after being paused).
 */

function next()
{
	if (this.get('paused'))
	{
		return;
	}

	var reverse = this.get('reverse');
	if ((!reverse && this._index >= this._list.length) ||
		( reverse && this._index < 0))
	{
		var iter_max   = this.get('iterations'),
			iter_count = this.get('iterationCount') + 1;
		if (iter_max == 'infinite' || iter_count < iter_max)
		{
			if (this.get('direction') == 'alternate')
			{
				this.set('reverse', !reverse);
			}

			this._set('iterationCount', iter_count);
			this._index = this.get('reverse') ? this._list.length-1 : 0;
			next.call(this);
		}
		else
		{
			this._set('running', false);
			this.fire('end');
		}
		return;
	}

	var item = this._list[ this._index ];
	if (Y.Lang.isArray(item))
	{
		var tasks = new Y.Parallel();
		Y.each(item, function(a)
		{
			a.once('end', tasks.add());
			a.set('reverse', reverse);
			a.run();
		},
		this);

		tasks.done(Y.bind(next, this));
	}
	else if ((item instanceof Y.Anim) || (item instanceof Y.AnimSequence))
	{
		item.once('end', next, this);
		item.set('reverse', reverse);
		item.run();
	}
	else if (Y.Lang.isFunction(item))
	{
		Y.later(0, this, function()
		{
			item.call(null, reverse);
			next.call(this);
		});
	}
	else if (Y.Lang.isNumber(item))
	{
		Y.later(item, this, next);
	}
	else
	{
		throw Error('unknown item type in sequence: ' + item);
	}

	this._index += reverse ? -1 : +1;
}

Y.extend(AnimSequence, Y.Base,
{
	initializer: function(config)
	{
		if (Y.Lang.isArray(config.sequence))
		{
			this.append.apply(this, config.sequence);
		}
	},

	/**
	 * Append items to the sequence.
	 *
	 * @method append
	 * @param item* {Anim|Function|Array|Number} animation, function, list of animations and functions, or delay in milliseconds
	 */
	append: function(item)
	{
		if (arguments.length == 1)	// even for an array
		{
			this._list.push(item);
		}
		else
		{
			Y.each(Y.Array(arguments), function f(a)
			{
				this._list.push(a);
			},
			this);
		}
	},

	/**
	 * Prepend items to the sequence.
	 *
	 * @method prepend
	 * @param item* {Anim|Function|Array|Number} animation, function, list of animations and functions, or delay in milliseconds
	 */
	prepend: function(item)
	{
		if (arguments.length == 1)	// even for an array
		{
			this._list.unshift(item);
		}
		else
		{
			Y.each(Y.Array(arguments), function f(a)
			{
				this._list.unshift(a);
			},
			this);
		}
	},

	/**
	 * Starts or resumes the sequence.
	 *
	 * @method run
	 * @chainable
	 */
	run: function()
	{
		if (this.get('paused'))
		{
			this._set('paused', false);
			this.fire('resume');
		}
		else
		{
			this._set('iterationCount', 0);
			this._set('running', true);
			this.fire('start');
			this._index = this.get('reverse') ? this._list.length-1 : 0;
		}

		next.call(this);
		return this;
	},

	/**
	 * Stops and resets the sequence.
	 *
	 * @method stop
	 * @chainable
	 * @param finish {Boolean} If true, the animation will move to the last frame.
	 */
	stop: function(finish)
	{
		this._set('running', false);
		this._set('paused', false);

		for (var i=this._index; i<this._list.length; i++)
		{
			var item = this._list[i];
			if (Y.Lang.isArray(item))
			{
				Y.each(item, function(a)
				{
					a.run();	// so items beyond the current item will finish
					a.stop(finish);
				},
				this);
			}
			else if ((item instanceof Y.Anim) || (item instanceof Y.AnimSequence))
			{
				item.run();		// so items beyond the current item will finish
				item.stop(finish);
			}

			if (!finish)
			{
				break;
			}
		}

		this.fire('end');
		return this;
	},

	/**
	 * Pauses the sequence.  If the current item is a delay, the sequence will
	 * pause after the delay interval finishes.
	 *
	 * @method pause
	 * @chainable
	 */
	pause: function()
	{
		this._set('paused', true);

		var item = this._list[ this._index ];
		if (Y.Lang.isArray(item))
		{
			Y.each(item, function(a)
			{
				a.pause();
			},
			this);
		}
		else if ((item instanceof Y.Anim) || (item instanceof Y.AnimSequence))
		{
			item.pause();
		}

		this.fire('pause');
		return this;
	}
});

Y.AnimSequence = AnimSequence;