(function(root, factory) {
if(typeof define === 'function' && define.amd) {
define(['jquery'], factory);
} else {
root.TA = factory(window.jQuery);
}
}(this, function($) {
"use strict";
var TA = {};
/**
* The central TA.App object
*
* This object cannot be instantiated, it is just a global object
* @constructor TA.App
*/
/**
* Registers an event handler
*
* @method TA.App.on
* @param {String} event
* @param {Function} callback
*/
/**
* Unregisters an event handler
*
* @method TA.App.off
* @param {String} event
* @param {Function} callback
*/
/**
* Registers a regex event handler
*
* @method TA.App.onRegex
* @param {String} regex
* @param {Function} callback
*/
/**
* Unregisters a regex event handler
*
* @method TA.App.offRegex
* @param {String} regex
* @param {Function} callback
*/
/**
* Triggers an event
*
* @method TA.App.trigger
* @param {String} event
*/
/**
* Triggers a start event
*
* @method TA.App.start
* @param {String} event
*/
TA.App = (function() {
var $app = $('#taapp');
var regex = [];
function ensureNodeExists() {
if(!$app.length) {
$('<div id="taapp"></div>').appendTo($('body'));
$app = $('#taapp');
}
}
function matchRegex(evt) {
$.each(regex, function(idx, e) {
var matches= evt.match(e.regex);
if(matches) {
e.handler(evt, matches);
}
});
return this;
}
//TODO: check if own event bus might be better
function trigger(evt) {
ensureNodeExists();
TA.StatusHandler.notify(evt);
matchRegex(evt);
$app.triggerHandler(evt);
return this;
}
function start(evt) {
trigger(evt+":start");
return this;
}
function on(evt, func) {
ensureNodeExists();
$app.on(evt, func);
return this;
}
function off(evt, func) {
ensureNodeExists();
$app.off(evt, func);
return this;
}
function onRegex(evt, func) {
ensureNodeExists();
regex.push({regex: evt, handler: func});
return this;
}
function offRegex(evt, func) {
for(var i=0, c=regex.length; i<c; ++i) {
if(regex[i].regex==evt && regex[i].handler==func) {
regex.splice(i, 1);
}
}
return this;
}
return {
on: on,
off: off,
onRegex: onRegex,
offRegex: offRegex,
trigger: trigger,
start: start,
};
})();
//Errorhandling code copied from handlebars
//https://github.com/wycats/handlebars.js/blob/4bed826d0e210c336fb9e500835b1c1926562da5/lib/handlebars/exception.js
TA.Error = {};
var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
TA.Error.Exception = function(name, message) {
var tmp = Error.prototype.constructor.call(this, message);
for(var i=0,c=errorProps.length; i<c; ++i) {
this[errorProps[i]] = tmp[errorProps[i]];
}
if(Error.captureStackTrace) {
Error.captureStackTrace(this, TA.Error.Exception);
}
this.name = name;
return this;
};
TA.Error.Exception.prototype = new Error();
TA.Error.ArgumentException = function(arg, expected, got) {
return new TA.Error.Exception('TA.Error.ArgumentException', 'Unexpected Argument "'+arg+'", Expected: "'+expected+'", Got: "'+got+'"');
};
TA.Error.DOMNodeNotFoundException = function(msg, node) {
return new TA.Error.Exception('TA.Error.DOMNodeNotFoundException', msg+' ('+node+')');
};
TA.Error.StateException = function(unit, msg) {
return new TA.Error.Exception('TA.Error.StateException', 'Unexpected State in "'+unit+'": '+msg);
};
TA.Error.InvalidNameException = function(unit, val) {
return new TA.Error.Exception('TA.Error.InvalidNameException', 'Invalid Name: '+unit+': "'+val+'"');
};
var forbiddenAnimNames = [
"in", "out", "start", "stop", "finish", "pause", "break", "unknown"
];
TA.checkAnimName = function(name) {
$.each(forbiddenAnimNames, function(idx, o) {
if(o===name) {
throw new TA.Error.InvalidNameException("Animation Name", name);
}
});
};
var createCallNFunction = function(times, func) {
var callN = function() {
++callN.count;
if(callN.count >= times) {
func();
}
};
callN.count=0;
return callN;
};
/**
* StatusHandler knows all about in which status which object currently is
*
* This object cannot be instantiated, it is just a global object
* @constructor TA.StatusHandler
*/
/**
* Notifies the object of a status change, do not call it yourself
*
* @method TA.StatusHandler.notify
* @param {String} event
*/
/**
* Checks if the status is currently active
*
* @method TA.StatusHandler.check
* @param {String} event - like "text:in"
* @return {Boolean}
*/
/**
* Returns the current status of the object
*
* @method TA.StatusHandler.getStatus
* @return {String} The status or "unknown"
*/
TA.StatusHandler = (function() {
var statuses = {};
var defaultStatus = 'unknown';
function splitEvent(evt) {
//split text:in
var parts = evt.split(':');
if(parts.length<2) return null;
var state=null;
if(parts.length==3) {
state = parts[1]+':'+parts[2];
} else {
state = parts[1];
}
var name = trimObjectName(parts[0]);
return {
name: name,
state: state
};
}
function trimObjectName(name) {
//split composition/object
var parts = name.split('/');
return parts[parts.length-1];
}
function notify(evt) {
var state = splitEvent(evt);
if(!state) return;
statuses[state.name]=state.state;
return this;
}
function check(name) {
var evt = splitEvent(name);
if(!evt) return false;
return getStatus(evt.name) === evt.state;
}
function getStatus(name) {
name = trimObjectName(name);
if(!statuses[name]) return defaultStatus;
return statuses[name];
}
return {
getStatus: getStatus,
notify: notify,
check: check
};
})();
/**
* Interface for Animation objects
*
* @interface TA.Animation
*/
/**
* Starts the animation
*
* @function
* @name TA.Animation#start
* @param {TA.Object} obj - the TA.Object to apply the Animation to
* @param {Function} [complete] - function to be called when the animation is finished
*/
/**
* Interface for general TA.Objects
*
* @interface TA.BaseObject
*/
/**
* Clones this TA.Object and overrides some settings
*
* @function
* @name TA.Object#clone
* @param {Object} [overrideSettings] - settings object containing name and optionally $e, anis and settings
* @return {TAObject}
*/
/**
* Returns the name of the object
*
* @function
* @name TA.BaseObject#getName
* @return {String}
*/
/**
* Returns the DOM object
*
* @function
* @name TA.BaseObject#getElement
* @return {Object}
*/
/**
* Starts the "in" Animation
*
* @function
* @name TA.BaseObject#startIn
* @param {Function} [complete] - function to be called once all animations are finished
*/
/**
* Starts the "out" Animation
*
* @function
* @name TA.BaseObject#startOut
* @param {Function} [complete] - function to be called once all animations are finished
*/
/**
* Starts the name Animation
*
* @function
* @name TA.BaseObject#start
* @param {String} name - name of animation to start
* @param {Function} [complete] - function to be called once all animations are finished
*/
/**
* Interface for general TA.Compositions
*
* @interface TA.BaseComposition
*/
/**
* Returns the name of the object
*
* @function
* @name TA.BaseComposition#getName
* @return {String}
*/
/**
* Register a TA.BaseObject in this composition
*
* @function
* @name TA.BaseComposition#register
* @param {TA.BaseObject} obj
*/
/**
* Starts the "in" Animation
*
* @function
* @name TA.BaseComposition#startIn
* @param {Function} [complete] - function to be called once all animations are finished
*/
/**
* Starts the "out" Animation
*
* @function
* @name TA.BaseComposition#startOut
* @param {Function} [complete] - function to be called once all animations are finished
*/
/**
* Interface for TA.ObjectSettings
*
* @interface TA.ObjectSettings
*/
/**
* Applys the initializing Setting to an object
*
* @function
* @name TA.ObjectSettings#applyInit
* @param {jQuery} e - DOM Element to apply Settings to
*/
/**
* Applys the deinitializing Setting to an object
*
* @function
* @name TA.ObjectSettings#applyDeinit
* @param {jQuery} e - DOM Element to apply Settings to
*/
/**
* Interface for TA.TimelineActions
*
* @interface TA.TimelineAction
*/
/**
* Runs the action
*
* @function
* @name TA.TimelineAction#run
* @param {TA.Timeline} tl - the current timeline object
*/
/**
* Gets the description
*
* @function
* @name TA.TimelineAction#getDescription
* @returns {String}
*/
/**
* TA.EventConverter listens for multiple events and once all where triggered triggers a new one
*
* @constructor TA.EventConverter
* @param {String[]} sources - Array of events to listen for
* @param {String} target - event to trigger
*/
TA.EventConverter = function(sources, target) {
this.events = [];
var that = this;
function listen(evt) {
if(!$.inArray(that.events, evt)) {
return;
}
that.events.push(evt);
if(that.events.length === sources.length) {
that.events = [];
TA.App.trigger(target);
}
return this;
}
/**
* activates the converter (done implicitly by the constructor)
*
* @method TA.EventConverter#activate
*/
this.activate = function() {
$.each(sources, function(idx, s) {
TA.App.on(s,listen);
});
return this;
};
/**
* deactivates the converter
*
* @method TA.EventConverter#deactivate
*/
this.deactivate = function() {
$.each(sources, function(idx, s) {
TA.App.off(s,listen);
});
return this;
};
this.activate();
return this;
};
/**
* Settings class to apply multiple Settings to an object
*
* @implements TA.Settings
* @param {TA.Settings[]} [settings] - Array of settings objects
* @constructor TA.CombinedSettings
*/
TA.CombinedSettings = function(settings) {
this.list = [];
/**
* add a settings object
*
* @method TA.CombinedSettings#addSettings
* @param {TA.Settings|TA.Settings[]} [settings]
*/
this.addSettings = function(settings) {
if(settings) {
if($.isArray(settings)) {
var that = this;
$.each(settings, function(idx, o) {
that.addSettings(o);
});
} else {
if(!settings.applyInit || !settings.applyDeinit) {
throw TA.Error.ArgumentException('settings', 'TA.Settings[]|TA.Settings', 'settings.applyInit and/or settings.applyDeinit not callable in ' + typeof settings);
}
this.list.push(settings);
}
}
return this;
};
/**
* @method TA.CombinedSettings#applyInit
* @inheritdoc
*/
this.applyInit = function($e) {
$.each(this.list, function(idx, o) {
o.applyInit($e);
});
return this;
};
/**
* @method TA.CombinedSettings#applyDeinit
* @inheritdoc
*/
this.applyDeinit = function($e) {
$.each(this.list, function(idx, o) {
o.applyDeinit($e);
});
return this;
};
this.addSettings(settings);
return this;
};
/**
* Settings class to apply CSS Settings to an object
*
* @implements TA.ObjectSettings
* @param {Object} [init] - CSS settings
* @param {Object} [deinit] - CSS settings
* @constructor TA.CssSettings
*/
TA.CssSettings = function(init, deinit) {
this.init = init || {};
this.deinit = deinit || {};
if($.type(this.init) !== 'object') {
throw new TA.Error.ArgumentException('init', 'Object', typeof this.init);
}
if($.type(this.deinit) !== 'object') {
throw new TA.Error.ArgumentException('deinit', 'Object', typeof this.deinit);
}
/**
* @method TA.CssSettings#applyInit
* @inheritdoc
*/
this.applyInit = function($e) {
if(!$.isFunction($e.css)) {
throw new TA.Error.ArgumentException('$e', 'jQuery object', '$e.css not callable in in ' + typeof $e);
}
$e.css(this.init);
return this;
};
/**
* @method TA.CssSettings#applyDeinit
* @inheritdoc
*/
this.applyDeinit = function($e) {
if(!$.isFunction($e.css)) {
throw new TA.Error.ArgumentException('$e', 'jQuery object', '$e.css not callable in ' + typeof $e);
}
$e.css(this.deinit);
return this;
};
return this;
};
/**
* Settings class to apply velocity Settings to an object
*
* @implements TA.ObjectSettings
* @param {Object} [init] - CSS settings
* @param {Object} [deinit] - CSS settings
* @constructor TA.VelocitySettings
*/
TA.VelocitySettings = function(init, deinit) {
this.init = init || {};
this.deinit = deinit || {};
if($.type(this.init) !== 'object') {
throw new TA.Error.ArgumentException('init', 'Object', typeof this.init);
}
if($.type(this.deinit) !== 'object') {
throw new TA.Error.ArgumentException('deinit', 'Object', typeof this.deinit);
}
/**
* @method TA.VelocitySettings#applyInit
* @inheritdoc
*/
this.applyInit = function($e) {
if(!$.isFunction($e.velocity)) {
throw new TA.Error.ArgumentException('$e', 'jQuery Object', '$e.velocity not callable in ' + typeof $e);
}
$e.velocity(this.init);
return this;
};
/**
* @method TA.VelocitySettings#applyDeinit
* @inheritdoc
*/
this.applyDeinit = function($e) {
if(!$.isFunction($e.velocity)) {
throw new TA.Error.ArgumentException('$e', 'jQuery Object', '$e.velocity not callable in ' + typeof $e);
}
$e.velocity(this.deinit);
return this;
};
return this;
};
/**
* A TA.ObjectSettings object that does nothing
*
* @implements TA.ObjectSettings
* @constructor TA.DummySettings
*/
TA.DummySettings = function() {
this.applyInit = this.applyDeinit = function(e) { return this; };
return this;
};
/**
* A TA.Animation object that does nothing
*
* @implements TA.Animation
* @constructor TA.DummyAnimation
*/
TA.DummyAnimation = function() {
this.start = function(obj, complete) { if(complete)complete(this); return this; };
return this;
};
/**
* A TA.Animation object that just defers the start call to the supplied user defined function
*
* @implements TA.Animation
* @param {Function} func - user defined function that gets the start() call forwarded (it needs to call complete after it's finished)
* @constructor TA.FunctionAnimation
*/
TA.FunctionAnimation = function(func) {
if(!$.isFunction(func)) {
throw new TA.Error.ArgumentException('func', 'function', typeof func);
}
/**
* @method TA.FunctionAnimation#start
* @inheritdoc
*/
this.start = function(obj, complete) {
func(obj, complete);
return this;
};
return this;
};
/**
* A TA.Animation object that chains multiple animations sequentially
*
* @implements TA.Animation
* @param {TA.Animation[]} animations - array of TA.Animation objects
* @constructor TA.ChainedAnimation
*/
TA.ChainedAnimation = function(animations) {
this.animations = animations || [];
if(!$.isArray(this.animations)) {
throw new TA.Error.ArgumentException('animations', 'TA.Animation[]', typeof this.animations);
}
$.each(this.animations, function(idx, e) {
if(!$.isFunction(e.start)) {
throw new TA.Error.ArgumentException('animations[i]', 'TA.Animation', 'animations[i].start not callable in ' + typeof e);
}
});
/**
* adds another animation to the queue
*
* @method TA.ChainedAnimation#addAnimation
* @param {TA.Animation} animation - the animation to add
*/
this.addAnimation = function(animation) {
if(!$.isFunction(animation.start)) {
throw new TA.Error.ArgumentException('animation', 'TA.Animation', 'animation.start not callable in ' + typeof animation);
}
this.animations.push(animation);
return this;
};
/**
* @method TA.ChainedAnimation#start
* @inheritdoc
*/
this.start = function(obj, complete) {
var idx = -1;
var count = this.animations.length;
var that = this;
var func = function() {
++idx;
if(idx >= count) {
if(complete) complete(that);
return;
}
that.animations[idx].start(obj, func);
};
func();
return this;
};
return this;
};
/**
* A TA.Animation object that executes multiple animations in parallel
*
* Keep in mind that some animation libraries need extra parameter to allow parallel animation execution
*
* @param {TA.Animation[]} animations - array of TA.Animation objects
* @constructor TA.ParallelAnimation
*/
TA.ParallelAnimation = function(animations) {
this.animations = animations || [];
if(!$.isArray(this.animations)) {
throw new TA.Error.ArgumentException('animations', 'TA.Animation[]', typeof this.animations);
}
$.each(this.animations, function(idx, e) {
if(!$.isFunction(e.start)) {
throw new TA.Error.ArgumentException('animations[i]', 'TA.Animation', 'animations[i].start not callable in ' + typeof e);
}
});
/**
* adds another animation to the queue
*
* @method TA.ParallelAnimation#addAnimation
* @param {TA.Animation} animation - the animation to add
*/
this.addAnimation = function(animation) {
if(!$.isFunction(animation.start)) {
throw new TA.Error.ArgumentException('animation', 'TA.Animation', 'animation.start not callable in ' + typeof animation);
}
this.animations.push(animation);
return this;
};
/**
* @method TA.ParallelAnimation#start
* @inheritdoc
*/
this.start = function(obj, complete) {
var animCount = this.animations.length;
var that = this;
var subComplete = createCallNFunction(animCount, function() {
if(complete)complete(that);
});
$.each(this.animations, function(idx, o) {
o.start(obj, subComplete);
});
return this;
};
return this;
};
/**
* TA.Animation object that delays the animation
*
* @implements TA.Animation
* @param {TA.Animation} animation - animation to execute
* @param {Integer} delay - delay in milliseconds
* @constructor TA.DelayedAnimation
*/
TA.DelayedAnimation = function(animation, delay) {
if(!$.isFunction(animation.start)) {
throw new TA.Error.ArgumentException('animation', 'TA.Animation', 'animation.start not callable in ' + typeof animation);
}
/**
* @method TA.DelayedAnimation#start
* @inheritdoc
*/
this.start = function(obj, complete) {
setTimeout(
function() {
animation.start(obj, complete);
}, delay
);
return this;
};
return this;
};
/**
* TA.Animation object that starts the animation in the background and completes instantly
*
* @implements TA.Animation
* @param {TA.Animation} animation - the animation to start in the background
* @constructor TA.BackgroundAnimation
*/
TA.BackgroundAnimation = function(animation) {
/**
* @method TA.BackgroundAnimation#start
* @inheritdoc
*/
this.start = function(obj, complete) {
complete(this);
animation.start(obj, function(){});
return this;
};
return this;
};
/**
* TA.Animation object that repeats the animation count number times
*
* @implements TA.Animation
* @param {Integer} count - number of times to repeat the animation
* @param {TA.Animation} animation - the animation to repeat
* @constructor TA.RepeatAnimation
*/
TA.RepeatAnimation = function(count, animation) {
if(!$.isFunction(animation.start)) {
throw new TA.Error.ArgumentException('animation', 'TA.Animation', 'animation.start not callable in ' + typeof animation);
}
/**
* @method TA.RepeatAnimation#start
* @inheritdoc
*/
this.start = function(obj, complete) {
var idx = 0;
var that = this;
var subComplete = function() {
if(idx >= count) {
if(complete)complete(that);
return;
}
++idx;
animation.start(obj, subComplete);
};
subComplete();
return this;
};
return this;
};
/**
* TA.Animation object that repeats the animation while the predicate returns true
*
* @implements TA.Animation
* @param {Function} predicate - function that returns true to repeat the animation or false to complete it
* @param {TA.Animation} animation - the animation to repeat
* @constructor TA.RepeatWhileAnimation
*/
TA.RepeatWhileAnimation = function(predicate, animation) {
if(!$.isFunction(predicate)) {
throw new TA.Error.ArgumentException('predicate', 'Function', typeof predicate);
}
if(!$.isFunction(animation.start)) {
throw new TA.Error.ArgumentException('animation', 'TA.Animation', 'animation.start not callable in ' + typeof animation);
}
/**
* @method TA.RepeatWhileAnimation#start
* @inheritdoc
*/
this.start = function(obj, complete) {
var that = this;
var subComplete = function() {
if(!predicate()) {
complete(that);
return;
}
animation.start(obj, subComplete);
};
subComplete();
return this;
};
return this;
};
/**
* TA.Animation object that uses jQuery.animate to do the animation
*
* @param {Object} properties - jQuery.animate animation properties
* @param {Object} [options] - jQuery.animate animation options
* @param {TA.Settings|TA.Settings[]} [settings] - TA.Settings to apply before and/or after the animation
* @constructor TA.JQueryAnimation
*/
TA.JQueryAnimation = function(properties, options, settings) {
this.properties = properties || {};
this.options = options || {};
this.settings = settings ? new TA.CombinedSettings([settings]) : new TA.DummySettings();
if($.type(this.properties) !== 'object') {
throw new TA.Error.ArgumentException('properties', 'Object', typeof this.properties);
}
if($.type(this.options) !== 'object') {
throw new TA.Error.ArgumentException('options', 'Object', typeof this.options);
}
/**
* @method TA.JQueryAnimation#start
* @inheritdoc
*/
this.start = function(obj, complete) {
var tempOptions = this.options;
var that = this;
this.settings.applyInit(obj.getElement());
//TODO: chaining with current complete, dont overwrite user data
tempOptions.complete = function() {
that.settings.applyDeinit(obj.getElement());
if(complete) complete(that);
};
obj.getElement().animate(this.properties, tempOptions);
return this;
};
return this;
};
/**
* TA.Animation object that uses velocity.js to do the animation
*
* @param {Object} properties - velocity.js animation properties
* @param {Object} [options] - velocity.js animation options
* @param {TA.Settings|TA.Settings[]} [settings] - TA.Settings to apply before and/or after the animation
* @constructor TA.VelocityAnimation
*/
TA.VelocityAnimation = function(properties, options, settings) {
this.properties = properties || {};
this.options = options || {};
this.settings = settings ? new TA.CombinedSettings([settings]) : new TA.DummySettings();
if($.type(this.properties) !== 'object') {
throw new TA.Error.ArgumentException('properties', 'Object', typeof this.properties);
}
if($.type(this.options) !== 'object') {
throw new TA.Error.ArgumentException('options', 'Object', typeof this.options);
}
/**
* @method TA.VelocityAnimation#start
* @inheritdoc
*/
this.start = function(obj, complete) {
var tempOptions = this.options;
var that = this;
this.settings.applyInit(obj.getElement());
//TODO: chaining with current complete, dont overwrite user data
tempOptions.complete = function() {
that.settings.applyDeinit(obj.getElement());
if(complete) complete(that);
};
obj.getElement().velocity(this.properties, tempOptions);
return this;
};
return this;
};
/**
* TA.BaseObject that represents a real object
*
* @implements TA.BaseObject
* @param {String} name - name of the object
* @param {Object} $e - DOM element of the object
* @param {Object} anis - object that contains TA.Animation (anis.in and anis.out - both are options)
* @param {TA.ObjectSettings|TA.ObjectSettings[]} settings - object settings
* @constructor TA.Object
*/
TA.Object = function(name, $e, anis, settings) {
this.name = name;
this.anis = anis || {};
this.$e = $e;
this.settings = new TA.CombinedSettings([settings]);
if(!this.anis['in']) {
this.anis['in'] = new TA.DummyAnimation();
}
if(!this.anis['out']) {
this.anis['out'] = new TA.DummyAnimation();
}
if(name === "") {
throw new TA.Error.ArgumentException('name', 'String', 'empty value');
}
//TODO: check if $e is jQuery Object
if(!$.isFunction(this.anis['in'].start)) {
throw new TA.Error.ArgumentException('anis.in', 'TA.Animation', 'anis.in.start not callable in ' + typeof this.anis['in']);
}
if(!$.isFunction(this.anis['out'].start)) {
throw new TA.Error.ArgumentException('anis.out', 'TA.Animation', 'anis.out.start not callable in ' + typeof this.anis['out']);
}
/**
* @method TA.Object#clone
* @inheritdoc
*/
this.clone = function(overrideSettings) {
var s = {
name: overrideSettings.name,
anis: $.extend({}, this.anis, overrideSettings.anis),
$e: overrideSettings.$e || $('#'+overrideSettings.name),
settings: overrideSettings.settings || settings
};
if(s.$e.length===0) {
throw new TA.Error.ArgumentException('$e', 'jQuery Node', 'Element is empty');
}
return new TA.Object(s.name, s.$e, s.anis, s.settings);
};
/**
* @method TA.Object#getName
* @inheritdoc
*/
this.getName = function() {
return this.name;
};
/**
* @method TA.Object#getElement
* @inheritdoc
*/
this.getElement = function() {
return this.$e;
};
/**
* @method TA.Object#addSettings
* @param {TA.Settings} [settings] - settings object to add
*/
this.addSettings = function(settings) {
this.settings.addSettings(settings);
};
var that = this;
function startAni(obj, ani, name) {
return function(complete) {
ani.start(obj, function() {
TA.App.trigger(obj.getName() + ":" + name);
if(complete) complete(obj);
});
return that;
};
}
/**
* @method TA.Object#startIn
* @inheritdoc
*/
this.startIn = function(complete) {
this.settings.applyInit(this.getElement());
startAni(this, this.anis['in'], "in")(complete);
return this;
};
/**
* @method TA.Object#startOut
* @inheritdoc
*/
this.startOut = function(complete) {
startAni(this, this.anis['out'], "out")(function(obj) {
that.settings.applyDeinit(that.getElement());
if(complete)complete(obj);
});
return this;
};
/**
* @method TA.Object#start
* @inheritdoc
*/
this.start = function(name, complete) {
if(!this.anis[name]) return;
if(name === 'in') {
this.startIn(complete);
return;
} else if (name === 'out') {
this.startOut(complete);
return;
}
startAni(this, this.anis[name], name)(complete);
return this;
};
TA.App.on(this.name+":in:start", function() { that.startIn(); });
TA.App.on(this.name+":out:start", function() { that.startOut(); });
for(var key in this.anis) {
if(key === 'in' || key === 'out') continue;
if(!this.anis.hasOwnProperty(key)) continue;
TA.checkAnimName(key);
var f=function(key) {
return function() {
that.start(key);
}
};
TA.App.on(this.name+":"+key+":start", f(key));
}
return this;
};
/**
* Creates a TA.Object and uses its name as ID selector
*
* @method TA.createObjectFromId
* @param {String} id - name of the object (also used as ID selector for the DOM element of the object)
* @param {Object} anis - object that contains TA.Animation (anis.in and anis.out - both are options)
* @param {TA.ObjectSettings|TA.ObjectSettings[]} settings - object settings
* @return TA.Object
*/
TA.createObjectFromId = function(id, anis, settings) {
var $e = $('#'+id);
if($e.length===0) {
throw new TA.Error.ArgumentException('id', 'existing DOM Node ID', 'DOM Node not found');
}
return new TA.Object(id, $e, anis, settings);
};
/**
* TA.BaseObject that delays animation execution
*
* @implements TA.BaseObject
* @param {String} name - the object name
* @param {TA.BaseObject} obj - the object to delay
* @param {Object} delays - object with delays in milliseconds (delays.in and delays.out)
* @constructor TA.DelayedObject
*/
TA.DelayedObject = function(name, obj, delays) {
this.obj = obj;
this.name = name;
this.delays = delays || {};
if(name === "") {
throw new TA.Error.ArgumentException('name', 'String', 'empty value');
}
if(!$.isFunction(this.obj.getName)) {
throw new TA.Error.ArgumentException('obj', 'TA.Object', 'obj.getName not callable in ' + typeof this.obj);
}
if($.type(this.delays) !== 'object') {
throw new TA.Error.ArgumentException('delays', 'Object', typeof this.delays);
}
/**
* @method TA.DelayedObject#startIn
* @inheritdoc
*/
this.startIn = function(complete) {
this.start("in", complete);
return this;
};
/**
* @method TA.DelayedObject#startOut
* @inheritdoc
*/
this.startOut = function(complete) {
this.start("out", complete);
return this;
};
/**
* @method TA.DelayedObject#start
* @inheritdoc
*/
this.start = function(name, complete) {
var delay = this.delays[name] || 0;
var that = this;
if(name=='in') {
this.obj.settings.applyInit(this.obj.getElement());
}
setTimeout(function() {
that.obj.start(name, complete);
}, delay);
return this;
};
var that = this;
TA.App.onRegex(new RegExp('^'+this.name+':(.*?):start$'), function(evt, matches) {
that.start(matches[1]);
});
/**
* @method TA.DelayedObject#getName
* @inheritdoc
*/
this.getName = function() {
return this.obj.getName();
};
/**
* @method TA.DelayedObject#getElement
* @inheritdoc
*/
this.getElement = function() {
return this.obj.getElement();
};
return this;
};
/**
* TA.BaseComposition object that handles basic composition
*
* @implements TA.BaseComposition
* @param {String} name - name of this composition
* @constructor TA.Composition
*/
TA.Composition = function(name) {
this.name = name;
this.objects = [];
if(name === "") {
throw new TA.Error.ArgumentException('name', 'String', 'empty value');
}
/**
* @method TA.Composition#register
* @inheritdoc
*/
this.register = function(obj) {
//TODO: check if obj is TA.Object
this.objects.push(obj);
return this;
};
/**
* @method TA.Composition#getName
* @inheritdoc
*/
this.getName = function() {
return this.name;
};
/**
* @method TA.Composition#startIn
* @inheritdoc
*/
this.startIn = function(complete) {
this.start("in", complete);
return this;
};
/**
* @method TA.Composition#startOut
* @inheritdoc
*/
this.startOut = function(complete) {
this.start("out", complete);
return this;
};
this.start = function(name, complete) {
var objCount = this.objects.length;
var that = this;
var subComplete = createCallNFunction(objCount, function() {
TA.App.trigger(that.getName()+":"+name);
if(complete)complete(that);
});
$.each(that.objects, function(idx, o) {
o.start(name, subComplete);
});
return this;
};
var that = this;
TA.App.onRegex(new RegExp('^'+this.name+':(.*?):start$'), function(evt, matches) {
that.start(matches[1]);
});
return this;
};
/**
* @implements TA.TimelineAction
* @constructor TA.TimelineAction_start
*/
TA.TimelineAction_start = function(action) {
this.getDescription = function() {
return "start("+action+")";
};
this.run = function(tl) {
TA.App.start(action);
tl.next();
};
};
/**
* @implements TA.TimelineAction
* @constructor TA.TimelineAction_trigger
*/
TA.TimelineAction_trigger = function(action) {
this.getDescription = function() {
return "start("+action+")";
};
this.run = function(tl) {
TA.App.trigger(action);
tl.next();
};
};
/**
* @implements TA.TimelineAction
* @constructor TA.TimelineAction_waitFor
*/
TA.TimelineAction_waitFor = function(action) {
this.getDescription = function() {
return "waitFor("+action+")";
};
this.run = function(tl) {
var func = function() {
TA.App.off(action, func);
tl.next();
};
TA.App.on(action, func);
};
};
/**
* @implements TA.TimelineAction
* @constructor TA.TimelineAction_delay
*/
TA.TimelineAction_delay = function(msecs) {
this.getDescription = function() {
return "delay("+msecs+")";
};
this.run = function(tl) {
setTimeout(function() {
tl.next();
}, msecs);
};
};
/**
* @implements TA.TimelineAction
* @constructor TA.TimelineAction_loop
*/
TA.TimelineAction_loop = function() {
this.getDescription = function() {
return "loop()";
};
this.run = function(tl) {
tl.rewind();
tl.execute();
};
};
/**
* @implements TA.TimelineAction
* @constructor TA.TimelineAction_loopN
*/
TA.TimelineAction_loopN = function(times) {
this.count = 0;
this.getDescription = function() {
return "loopN("+times+")";
};
this.run = function(tl) {
++this.count;
if(this.count < times) {
tl.rewind();
tl.execute();
} else {
tl.next();
}
};
};
/**
* @implements TA.TimelineAction
* @constructor TA.TimelineAction_step
*/
TA.TimelineAction_step = function(steps) {
this.getDescription = function() {
return "step("+steps+")";
};
this.run = function(tl) {
tl.step(steps);
tl.execute();
};
};
/**
* @implements TA.TimelineAction
* @constructor TA.TimelineAction_label
*/
TA.TimelineAction_label = function(name) {
this.getDescription = function() {
return "label("+name+")";
};
this.getLabel = function() {
return name;
};
this.run = function(tl) {
tl.next();
};
};
/**
* @implements TA.TimelineAction
* @constructor TA.TimelineAction_jumpTo
*/
TA.TimelineAction_jumpTo = function(label) {
this.getDescription = function() {
return "jumpTo("+label+")";
};
this.run = function(tl) {
tl.jumpToLabel(label);
};
};
/**
* @implements TA.TimelineAction
* @constructor TA.TimelineAction_startAndWaitFor
*/
TA.TimelineAction_startAndWaitFor = function(action) {
this.getDescription = function() {
return "startAndWaitFor("+action+")";
};
this.run = function(tl) {
var func = function() {
TA.App.off(action, func);
tl.next();
};
TA.App.on(action, func);
TA.App.start(action);
};
};
/**
* @implements TA.TimelineAction
* @constructor TA.TimelineAction_execute
*/
TA.TimelineAction_execute = function(func) {
this.getDescription = function() {
return "execute(userFunc)";
};
this.run = function(tl) {
func(tl);
};
};
/**
* @implements TA.TimelineAction
* @constructor TA.TimelineAction_if
*/
TA.TimelineAction_if = function(func, action) {
this.getDescription = function() {
return "if("+action.getDescription()+")";
};
this.run = function(tl) {
if(func()) {
action.run(tl);
} else {
tl.next();
}
};
};
/**
* @implements TA.TimelineAction
* @constructor TA.TimelineAction_stop
*/
TA.TimelineAction_stop = function() {
this.getDescription = function() {
return "stop()";
};
this.run = function(tl) {
};
};
/**
* @implements TA.TimelineAction
* @constructor TA.TimelineAction_finish
*/
TA.TimelineAction_finish = function() {
this.getDescription = function() {
return "finish()";
};
this.run = function(tl) {
TA.App.trigger(tl.getName()+":finish");
};
};
/**
* @implements TA.TimelineAction
* @constructor TA.TimelineAction_playTimeline
*/
TA.TimelineAction_playTimeline = function(name) {
this.getDescription = function() {
return "playTimeline("+name+")";
};
this.run = function(tl) {
var f = function() {
TA.App.off(name+":finish", f);
tl.next();
};
TA.App.on(name+":finish", f);
TA.App.start(name);
};
};
/**
* @implements TA.TimelineAction
* @constructor TA.TimelineAction_playTimelineAsync
*/
TA.TimelineAction_playTimelineAsync = function(name) {
this.getDescription = function() {
return "playTimelineAsync("+name+")";
};
this.run = function(tl) {
TA.App.start(name);
tl.next();
};
};
/**
* Describer for a TA.Timeline actions
*
* This is for convenience. It create TA.TimelineAction objects for ease of use
*
* @constructor TA.TimelineDescriber
*/
TA.TimelineDescriber = function() {
/**
* Triggers a start event
*
* @method TA.TimelineDescriber#start
* @param {String} action - event name
* @returns {TA.TimelineAction}
*/
this.start = function(action) {
return new TA.TimelineAction_start(action);
};
/**
* Triggers an event
*
* @method TA.TimelineDescriber#trigger
* @param {String} action - event name
* @returns {TA.TimelineAction}
*/
this.trigger = function(action) {
return new TA.TimelineAction_trigger(action);
};
/**
* Waits for an event to be triggered
*
* @method TA.TimelineDescriber#waitFor
* @param {String} action - event name
* @returns {TA.TimelineAction}
*/
this.waitFor = function(action) {
return new TA.TimelineAction_waitFor(action);
};
/**
* Triggers a start event and waits for it to complete
*
* @method TA.TimelineDescriber#startAndWaitFor
* @param {String} action - event name
* @returns {TA.TimelineAction}
*/
this.startAndWaitFor = function(action) {
return new TA.TimelineAction_startAndWaitFor(action);
};
/**
* Delays the next action
*
* @method TA.TimelineDescriber#delay
* @param {Integer} msecs - time in milliseconds
* @returns {TA.TimelineAction}
*/
this.delay = function(msecs) {
return new TA.TimelineAction_delay(msecs);
};
/**
* Steps to another event in the timeline
*
* @method TA.TimelineDescriber#step
* @param {Integer} steps - positive or negative amount of steps to take
* @returns {TA.TimelineAction}
*/
this.step = function(steps) {
return new TA.TimelineAction_step(steps);
};
/**
* Defines a label
*
* @method TA.TimelineDescriber#label
* @param {String} name - label name
* @returns {TA.TimelineAction}
*/
this.label = function(name) {
return new TA.TimelineAction_label(name);
};
/**
* Jumps to a label (skipping all other steps)
*
* @method TA.TimelineDescriber#jumpTo
* @param {String} label - label name
* @returns {TA.TimelineAction}
*/
this.jumpTo = function(label) {
return new TA.TimelineAction_jumpTo(label);
};
/**
* Executes a user defined function
*
* this function gets the current TA.Timeline object as only parameter and needs to call tl.next() on it (otherwise the execution will halt)
* @method TA.TimelineDescriber#execute
* @param {Function} func - a user defined function
* @returns {TA.TimelineAction}
*/
this.execute = function(func) {
return new TA.TimelineAction_execute(func);
};
/**
* Rewinds the timeline to the start and continues from there
*
* @method TA.TimelineDescriber#loop
* @returns {TA.TimelineAction}
*/
this.loop = function() {
return new TA.TimelineAction_loop();
};
/**
* Rewinds the timeline to the start and continues from there, but only times times
*
* @method TA.TimelineDescriber#loopN
* @returns {TA.TimelineAction}
*/
this.loopN = function(times) {
return new TA.TimelineAction_loopN(times);
};
/**
* Stops the timeline
*
* @method TA.TimelineDescriber#stop
* @returns {TA.TimelineAction}
*/
this.stop = function() {
return new TA.TimelineAction_stop();
};
/**
* Stops the timeline and triggers the finish event
*
* @method TA.TimelineDescriber#finish
* @returns {TA.TimelineAction}
*/
this.finish = function() {
return new TA.TimelineAction_finish();
};
/**
* Plays a timeline
*
* @method TA.TimelineDescriber#playTimeline
* @param {String} name - name of timeline
* @returns {TA.TimelineAction}
*/
this.playTimeline = function(name) {
return new TA.TimelineAction_playTimeline(name);
};
/**
* Plays a timeline
*
* @method TA.TimelineDescriber#playTimelineAsync
* @param {String} name - name of timeline
* @returns {TA.TimelineAction}
*/
this.playTimelineAsync = function(name) {
return new TA.TimelineAction_playTimelineAsync(name);
};
/**
* Executes an event if a user defined function returns true
*
* @method TA.TimelineDescriber#executeIf
* @param {Function} func - a user defined predicate
* @param {TA.TimelineAction} action - a TA.TimelineAction to execute of the predicate returns true
* @returns {TA.TimelineAction}
*/
this.executeIf = function(func, action) {
return new TA.TimelineAction_if(func, action);
};
};
/**
* A basic Timeline object
*
* @param {String} name - name of the timeline
* @constructor TA.Timeline
*/
TA.Timeline = function(name) {
//TODO: refactor so we don't expose everything to the user
this.name = name;
this.steps = [];
this.curPos = 0;
this.debug = false;
this.honorReqs = false;
this.breakOnExecute = false;
this.singleStepMode = false;
this.requires = [];
this.setReqs = function(reqs) {
if(!$.isArray(reqs)) {
throw new TA.Error.ArgumentException('reqs', 'Array', typeof reqs);
}
this.requires = reqs;
return this;
};
this.addReq = function(req) {
this.requires.push(req);
return this;
};
this.reqsMet = function() {
for(var i=0,c=this.requires.length; i<c; ++i) {
if(!TA.StatusHandler.check(this.requires[i])) {
return false;
}
}
return true;
};
this.forceReqs = function(complete) {
var missing = [];
$.each(this.requires, function(idx, o) {
if(!TA.StatusHandler.check(o)) {
missing.push(o);
}
});
var that=this;
var subComplete = createCallNFunction(missing.length, function() {
if(complete)complete(that);
});
$.each(missing, function(idx, o) {
var f=function() {
TA.App.off(o, f);
subComplete();
};
TA.App.on(o, f);
TA.App.start(o);
});
return this;
};
this.forceReqsAndGo = function() {
this.forceReqs(function(tl) {
tl.go();
});
};
this.setHonorReqs = function(honor) {
this.honorReqs = honor;
return this;
};
/**
* Sets the debug value
*
* @method TA.Timeline#setDebug
* @param {Boolean} dbg
*/
this.setDebug = function(dbg) {
this.debug = dbg;
return this;
};
/**
* Sets the single step value
*
* @method TA.Timeline#setSingleStep
* @param {Boolean} singleStepValue
*/
this.setSingleStep = function(singleStepValue) {
this.singleStepMode = singleStepValue;
return this;
};
/**
* Returns a TA.TimelineDescriber object for convenience
*
* @method TA.Timeline#getDescriber
* @returns {TA.TimelineDescriber}
*/
this.getDescriber = function() {
return new TA.TimelineDescriber();
};
/**
* Returns the timeline name
*
* @method TA.Timeline#getName
* @returns {String}
*/
this.getName = function() {
return this.name;
};
/**
* Starts the execution
*
* @method TA.Timeline#go
*/
/**
* Starts the execution
*
* @method TA.Timeline#play
*/
this.go = this.play = function() {
if(this.honorReqs && !this.reqsMet()) {
throw new TA.Error.StateException('Timeline', 'Requirements are not met');
}
var that = this;
setTimeout(function() {
that.execute();
}, 0);
return this;
};
/**
* Halts the execution
*
* @method TA.Timeline#pause
*/
this.pause = function() {
this.breakOnExecute = true;
return this;
};
/**
* Jumps to a label and starts the execution from there
*
* @method TA.Timeline#jumpToLabel
* @param {String} label
*/
this.jumpToLabel = function(label) {
var that = this;
setTimeout(function() {
for (var i = 0, c = that.steps.length; i < c; ++i) {
var action = that.steps[i];
if (action.getLabel && action.getLabel() == label) {
that.curPos = i;
that.execute();
return;
}
}
throw new TA.Error.ArgumentException("label", "correct label name", "Unknown Label '"+label+"'");
},0);
return this;
};
this.step = function(offset) {
this.curPos += offset;
return this;
};
this.getCurPos = function() {
return this.curPos;
};
this.getLength = function() {
return this.steps.length;
};
/**
* Rewinds the timeline
*
* @method TA.Timeline#rewind
*/
this.rewind = function() {
this.curPos = 0;
return this;
};
this.next = function() {
this.step(1);
this.execute();
};
this.execute = function() {
if(this.curPos >= this.steps.length) {
TA.App.trigger(this.name+":finish");
return;
}
if(this.breakOnExecute) {
this.breakOnExecute = false;
TA.App.trigger(this.name+":break");
return;
}
if(this.singleStepMode) {
this.breakOnExecute = true;
}
var action = this.steps[this.curPos];
if(this.debug) console.log(this.name+": "+action.getDescription());
TA.App.trigger(this.name+":step");
action.run(this);
};
/**
* Adds Actions to the Timeline
*
* @method TA.Timeline#add
* @param {TA.TimelineAction[]|TA.TimelineAction} action - either a TA.TimelineAction or an array of TA.TimelineActions
*/
this.add = function(action) {
if($.isArray(action)) {
var that = this;
$.each(action, function(idx, e) {
that.add(e);
});
} else {
if(!$.isFunction(action.run)) {
throw new TA.Error.ArgumentException('action', 'TA.TimelineAction', 'action.run not callable in ' + typeof action);
}
this.steps.push(action);
}
return this;
};
/**
* Displays controls to control this timeline in your Webpage
*
* @method TA.Timeline#displayControls
* @param [jQuery|String] $e - either a jQuery Node object or an ID. The Element gets created if it does not exist.
* @return {jQuery} the jQuery Node the controls reside in
*/
this.displayControls = function($e) {
if(!$e) {
$e = this.name+'_controls';
}
if($.type($e) === 'string') {
if($($e).length===0) {
$e = $('<div id="'+$e+'"></div>').appendTo('body');
} else {
$e = $($e);
}
}
$e.append('<button href="#" class="tapause">Pause</button>');
$e.append('<button href="#" class="tastep">Step</button>');
$e.append('<button href="#" class="taskip">Skip</button>');
$e.append('<button href="#" class="taplay">Play</button>');
var that = this;
$e.on('click', '.tapause', function(evt) {
that.pause();
}).on('click', '.tastep', function(evt) {
that.setSingleStep(true);
that.play();
}).on('click', '.taskip', function(evt) {
that.step(1);
that.setSingleStep(true);
that.play();
}).on('click', '.taplay', function(evt) {
that.setSingleStep(false);
that.breakOnExecute = false;
that.play();
});
return $e;
};
var that=this;
TA.App.on(this.name+":pause", function() {
that.breakOnExecute = true;
});
TA.App.on(this.name+":start", function() {
that.go();
});
return this;
};
//expose
return TA;
}));