/**
* Losely modeled after AS3's Timer class. Provides a simple interface start,
* pause, resume, and stop a defined timer set with a custom callback method.
* @module timer
* @author Anthony Pipkin
* @version 1.1.0
*/
/**
* Losely modeled after AS3's Timer class. Provides a simple interface start,
* pause, resume, and stop a defined timer set with a custom callback method.
* @class Y.Timer
* @extends Y.Base
*/
// Local constants
var STATUS_RUNNING = 'running',
STATUS_PAUSED = 'paused',
STATUS_STOPPED = 'stopped',
EVENT_START = 'start',
EVENT_STOP = 'stop',
EVENT_PAUSE = 'pause',
EVENT_RESUME = 'resume',
EVENT_TIMER = 'timer';
Y.Timer = Y.Base.create('timer', Y.Base, [] , {
/**
* @event start
* @description The timer has started
* @param {Event.Facade} event An Event Facade object
* @type {Event.Custom}
*/
/**
* @event stop
* @description The timer has stopped
* @param {Event.Facade} event An Event Facade object
* @type {Event.Custom}
*/
/**
* @event pause
* @description The timer has paused
* @param {Event.Facade} event An Event Facade object
* @type {Event.Custom}
*/
/**
* @event resume
* @description The timer has resumed
* @param {Event.Facade} event An Event Facade object
* @type {Event.Custom}
*/
/**
* Fires at every interval of Y.Timer
* @event timer
* @description The timer has reached a reached zero
* @param {Event.Facade} event An Event Facade object
* @type {Event.Custom}
*/
////// P U B L I C //////
/**
* Initializer lifecycle implementation for the Timer class.
* Publishes events and subscribes
* to update after the status is changed.
*
* @method initializer
* @protected
* @param config {Object} Configuration object literal for
* the Timer
* @since 1.0.0
*/
initializer : function(config){
this.after('statusChange',this._afterStatusChange,this);
this.publish(EVENT_START , { defaultFn : this._defStartFn });
this.publish(EVENT_STOP , { defaultFn : this._defStopFn });
this.publish(EVENT_PAUSE , { defaultFn : this._defPauseFn });
this.publish(EVENT_RESUME , { defaultFn : this._defResumeFn });
},
/**
* Interface method to start the Timer. Fires timer:start
*
* @method start
* @public
* @since 1.0.0
*/
start : function() {
Y.log('Timer::start','info');
if(this.get('status') !== STATUS_RUNNING) {
this.fire(EVENT_START);
}
return this;
},
/**
* Interface method to stop the Timer. Fires timer:stop
*
* @method stop
* @public
* @since 1.0.0
*/
stop : function() {
Y.log('Timer::stop','info');
if(this.get('status') === STATUS_RUNNING) {
this.fire(EVENT_STOP);
}
return this;
},
/**
* Interface method to pause the Timer. Fires timer:pause
*
* @method pause
* @public
* @since 1.0.0
*/
pause : function() {
Y.log('Timer::pause','info');
if(this.get('status') === STATUS_RUNNING) {
this.fire(EVENT_PAUSE);
}
return this;
},
/**
* Interface method to resume the Timer. Fires timer:resume
*
* @method resume
* @public
* @since 1.0.0
*/
resume : function() {
Y.log('Timer::resume','info');
if(this.get('status') === STATUS_PAUSED) {
this.fire(EVENT_RESUME);
}
return this;
},
////// P R O T E C T E D //////
/**
* Internal timer
*
* @property {Y.later} _timerObj
* @protected
* @since 1.0.0
*/
_timerObj : null,
/**
* Resume length
*
* @property _remainingLength
* @protected
* @since 1.2.0
*/
_remainingLength: null,
/**
* Checks to see if a new Timer is to be created. If so, calls
* _timer() after a the schedule number of milliseconds. Sets
* Timer pointer to the new Timer id. Sets start to the current
* timestamp.
*
* @method _makeTimer
* @protected
* @since 1.0.0
*/
_makeTimer : function() {
Y.log('Timer::_makeTimer','info');
var timerObj = this._timerObj,
repeat = this.get('repeatCount');
if (timerObj) {
timerObj.cancel();
timerObj = null;
this._timerObj = null;
}
if(repeat === 0 || repeat > this.get('step')) {
timerObj = Y.later(this._remainingLength, this, this._timer);
}
this._timerObj = timerObj;
this.set('timer', timerObj);
this.set('start', (new Date()).getTime());
this.set('stop', this.get('start'));
},
/**
* Resets the Timer.
*
* @method _destroyTimer
* @protected
* @since 1.0.0
*/
_destroyTimer : function() {
Y.log('Timer::_destroyTimer','info');
var timerObj = this._timerObj;
if (timerObj) {
timerObj.cancel();
timerObj = null;
this._timerObj = null;
}
this.set('timer', null);
this.set('stop', (new Date()).getTime());
this.set('step', 0);
this._remainingLength = this._remainingLength - (this.get('stop') - this.get('start'));
},
/**
* Increments the step and either stops or starts a new Timer
* interval. Fires the timer callback method.
*
* @method _timer
* @protected
* @since 1.0.0
*/
_timer : function() {
Y.log('Timer::_timer','info');
this.fire(EVENT_TIMER);
var step = this.get('step'),
repeat = this.get('repeatCount');
this.set('step', ++step);
if(repeat > 0 && repeat <= step) { // repeat at 0 is infinite loop
this._remainingLength = 0;
this.stop();
}else{
this._remainingLength = this.get('length');
this._makeTimer();
}
this._executeCallback();
},
/**
* Internal status change event callback. Allows status changes
* to fire start(), pause(), resume(), and stop() automatically.
*
* @method _statusChanged
* @protcted
* @since 1.0.0
*/
_afterStatusChange : function(e){
Y.log('Timer::_afterStatusChange','info');
switch(e.newVal) {
case STATUS_RUNNING:
this._makeTimer();
break;
case STATUS_STOPPED: // overflow intentional
case STATUS_PAUSED:
this._destroyTimer();
break;
}
},
/**
* Default function for start event.
*
* @method _defStartFn
* @protected
* @since 1.0.0
*/
_defStartFn : function(e) {
Y.log('Timer::_defStartFn','info');
var delay = this.get('startDelay');
this._remainingLength = this.get('length');
if(delay > 0) {
Y.later(delay, this, function(){
this.set('status', STATUS_RUNNING);
});
}else{
this.set('status', STATUS_RUNNING);
}
},
/**
* Default function for stop event.
*
* @method _defStopFn
* @protected
* @since 1.0.0
*/
_defStopFn : function(e) {
Y.log('Timer::_defStopFn','info');
this._remainingLength = 0;
this.set('status', STATUS_STOPPED);
},
/**
* Default function for pause event.
*
* @method _defPauseFn
* @protected
* @since 1.0.0
*/
_defPauseFn : function(e) {
Y.log('Timer::_defPauseFn','info');
this.set('status', STATUS_PAUSED);
},
/**
* Default function for resume event. Starts timer with
* remaining time left after Timer was paused.
*
* @method _defResumeFn
* @protected
* @since 1.0.0
*/
_defResumeFn : function(e) {
Y.log('Timer::_defResumeFn','info');
this.set('status',STATUS_RUNNING);
},
/**
* Abstracted the repeatCount validator into the prototype to
* encourage class extension.
*
* @method _repeatCountValidator
* @protected
* @since 1.1.0
*/
_repeatCountValidator : function(val) {
Y.log('Timer::_repeatCountValidator','info');
return (this.get('status') === STATUS_STOPPED);
},
/**
* Used to fire the internal callback
*
* @method _executeCallback
* @protected
* @since 1.1.0
*/
_executeCallback : function() {
Y.log('Timer::_executeCallback','info');
var callback = this.get('callback');
if (Y.Lang.isFunction(callback)) {
(this.get('callback'))();
}
},
/**
* Returns the time from `now` if the timer is running and returns remaining
* time from `stop` if the timer has stopped.
* @method _remainingGetter
* @protected
* @since 1.2.0
*/
_remainingGetter: function(){
Y.log('Timer::_remainingGetter', 'info');
var status = this.get('status'),
length = this._remainingLength,
maxTime = (new Date()).getTime();
if (status === STATUS_STOPPED) {
return 0;
} else if (status === STATUS_PAUSED) {
return length;
} else {
return length - ( maxTime - this.get('start') );
}
}
},{
/**
* Static property used to define the default attribute
* configuration for the Timer.
*
* @property ATTRS
* @type Object
* @static
*/
ATTRS : {
/**
* @description The callback method that fires when the
* timer interval is reached.
*
* @attribute callback
* @type function
* @since 1.0.0
*/
callback : {
value : null,
validator : Y.Lang.isFunction
},
/**
* Time in milliseconds between intervals
*
* @attribute length
* @type Number
* @since 1.0.0
*/
length : {
value : 3000,
setter : function(val) {
return parseInt(val,10);
}
},
/**
* Get remaining milliseconds
*
* @attribute remaining
* @type Number
* @since 1.2.0
*/
remaining: {
readonly: true,
getter: '_remainingGetter'
},
/**
* Number of times the Timer should fire before it stops
* - 1.1.0 - added lazyAdd false to prevent starting from
* overriding the validator
* @attribute repeatCount
* @type Number
* @since 1.1.0
*/
repeatCount : {
validator : 'repeatCountValidator',
setter : function(val) {
return parseInt(val,10);
},
value : 0,
lazyAdd : false
},
/**
* Timestamp Timer was started
*
* @attribute start
* @type Boolean
* @since 1.0.0
*/
start : {
readonly : true
},
/**
* Time in ms to wait until starting after start() has been called
* @attribute startDelay
* @type Number
* @since 1.1.0
*/
startDelay : {
value : 0
},
/**
* Timer status
* - 1.1.0 - Changed from state to status. state was left
* from legacy code
* @attribute status
* @default STATUS_STOPPED
* @type String
* @since 1.1.0
*/
status : {
value : STATUS_STOPPED,
readonly : true
},
/**
* Number of times the Timer has looped
*
* @attribute step
* @type Boolean
* @since 1.0.0
*/
step : { // number of intervals passed
value : 0,
readonly : true
},
/**
* Timestamp Timer was stoped or paused
*
* @attribute stop
* @type Boolean
* @since 1.0.0
*/
stop : {
readonly : true
},
/**
* Timer id to used during stop()
*
* @attribute timer
* @type Number
* @since 1.0.0
*/
timer : {
readonly : true
}
},
/**
* Static property provides public access to registered timer
* status strings
*
* @property Timer.STATUS
* @type Object
* @static
*/
STATUS : {
RUNNING : STATUS_RUNNING,
PAUSED : STATUS_PAUSED,
STOPPED : STATUS_STOPPED
},
/**
* Static property provides public access to registered timer
* event strings
*
* @property Timer.EVENTS
* @type Object
* @static
*/
EVENTS : {
START : EVENT_START,
STOP : EVENT_STOP,
PAUSE : EVENT_PAUSE,
RESUME : EVENT_RESUME,
TIMER : EVENT_TIMER
}
});