Source: chronoman.js

/**
 * @module chronoman
 * 
 * @author Denis Sikuler
 */


/**
 * Utility class to simplify use of timers created by setTimeout.
 * 
 * @param {Object} [initValue]
 *      Specifies initial property values. Keys are property names, their values are values of corresponding properties.
 *      See {@link module:chronoman~Timer#setProperties setProperties} for details.
 * @constructor
 * @see {@link module:chronoman~Timer#setProperties setProperties}
 */
var Timer = function Timer(initValue) {
    
    var that = this;
    
    /**
     * Handle timeout's end.
     *
     * @instance
     * @method
     * @protected
     * @see {@link module:chronoman~Timer#_timeoutId _timeoutId}
     * @see {@link module:chronoman~Timer#execute execute}
     */
    this._onTimeoutEnd = function() {
        that._timeoutId = null;
        that.execute();
    };

    if (initValue && typeof initValue === "object") {
        this.setProperties(initValue);
    }
};


/**
 * Time period in milliseconds.
 * A related action will be executed when the period is elapsed.
 *
 * @protected
 * @type {Integer}
 * @see {@link module:chronoman~Timer#execute execute}
 * @see {@link module:chronoman~Timer#setActive setActive}
 */
Timer.prototype._period = null;

/**
 * Return time period that is used to schedule related action execution.
 *
 * @return {Integer}
 *      Time period in milliseconds.
 * @method
 * @see {@link module:chronoman~Timer#_period _period}
 */
Timer.prototype.getPeriod = function() {
    return this._period;
};

/**
 * Set time period that is used to schedule related action execution.
 *
 * @param {Integer} nPeriod
 *      Time period in milliseconds.
 * @return {Object}
 *      Reference to <code>this</code> object.
 * @method
 * @see {@link module:chronoman~Timer#_period _period}
 */
Timer.prototype.setPeriod = function(nPeriod) {
    this._period = nPeriod;
    return this;
};

/**
 * Indicates whether related action should be executed repeatedly.
 * 
 * @protected
 * @type {Boolean}
 * @see {@link module:chronoman~Timer#execute execute}
 * @see {@link module:chronoman~Timer#setActive setActive}
 */
Timer.prototype._recurrent = false;

/**
 * Test whether related action should be executed repeatedly.
 *
 * @return {Boolean}
 *      <code>true</code>, if related action should be executed repeatedly, otherwise <code>false</code>.
 * @method
 * @see {@link module:chronoman~Timer#_recurrent _recurrent}
 */
Timer.prototype.isRecurrent = function() {
    return this._recurrent;
};

/**
 * Set or cancel repeating of related action execution.
 *
 * @param {Boolean} bRecurrent
 *      <code>true</code>, if action should be executed repeatedly, <code>false</code>, if action repeating should be off.
 * @return {Object}
 *      Reference to <code>this</code> object.
 * @method
 * @see {@link module:chronoman~Timer#_recurrent _recurrent}
 */
Timer.prototype.setRecurrent = function(bRecurrent) {
    this._recurrent = bRecurrent;
    return this;
};

/**
 * Specifies how many times related action should be repeated after first execution.
 *
 * @protected
 * @type {Integer}
 * @see {@link module:chronoman~Timer#execute execute}
 * @see {@link module:chronoman~Timer#setActive setActive}
 */
Timer.prototype._repeatQty = 0;

/**
 * Return the value that indicates how many times related action should be repeated after first execution.
 *
 * @return {Integer}
 *      Value that indicates how many times related action should be repeated after first execution.
 * @method
 * @see {@link module:chronoman~Timer#_repeatQty _repeatQty}
 */
Timer.prototype.getRepeatQty = function() {
    return this._repeatQty;
};

/**
 * Set how many times related action should be repeated after first execution.
 *
 * @param {Integer} nQty
 *      Value that indicates how many times related action should be repeated after first execution.
 * @return {Object}
 *      Reference to <code>this</code> object.
 * @method
 * @see {@link module:chronoman~Timer#_repeatQty _repeatQty}
 */
Timer.prototype.setRepeatQty = function(nQty) {
    this._repeatQty = nQty;
    return this;
};

/**
 * Specifies function that should be called after first action execution to determine
 * whether execution should be repeated.
 * If the function returns a true value it means that execution will be repeated.
 * <br>
 * The timer instance to which the test is associated will be passed as function's parameter.
 *
 * @protected
 * @type {Function}
 * @see {@link module:chronoman~Timer#execute execute}
 * @see {@link module:chronoman~Timer#setActive setActive}
 */
Timer.prototype._repeatTest = null;

/**
 * Return the function that is used to determine whether action execution should be repeated.
 *
 * @return {Function}
 *      Function that is used to determine whether action execution should be repeated.
 * @method
 * @see {@link module:chronoman~Timer#_repeatTest _repeatTest}
 */
Timer.prototype.getRepeatTest = function() {
    return this._repeatTest;
};

/**
 * Set the function that should be used to determine whether action execution should be repeated.
 *
 * @param {Function} test
 *      Function that should be used to determine whether action execution should be repeated.
 * @return {Object}
 *      Reference to <code>this</code> object.
 * @method
 * @see {@link module:chronoman~Timer#_repeatTest _repeatTest}
 */
Timer.prototype.setRepeatTest = function(test) {
    this._repeatTest = test;
    return this;
};

/**
 * Specifies how many times action was executed.
 *
 * @protected
 * @type {Integer}
 * @see {@link module:chronoman~Timer#execute execute}
 * @see {@link module:chronoman~Timer#setActive setActive}
 */
Timer.prototype._executionQty = 0;

/**
 * Return the value that indicates how many times action was executed.
 *
 * @return {Integer}
 *      Value that indicates how many times action was executed.
 * @method
 * @see {@link module:chronoman~Timer#_executionQty _executionQty}
 */
Timer.prototype.getExecutionQty = function() {
    return this._executionQty;
};

/**
 * Timer id.
 * 
 * @protected
 * @type {Integer}
 * @see {@link module:chronoman~Timer#_clearTimeout _clearTimeout}
 * @see {@link module:chronoman~Timer#_setTimeout _setTimeout}
 */
Timer.prototype._timeoutId = null;

/**
 * Schedule related action execution.
 *
 * @return {Object}
 *      Reference to <code>this</code> object.
 * @method
 * @protected
 * @see {@link module:chronoman~Timer#_clearTimeout _clearTimeout}
 * @see {@link module:chronoman~Timer#_onTimeoutEnd _onTimeoutEnd}
 * @see {@link module:chronoman~Timer#_timeoutId _timeoutId}
 * @see {@link module:chronoman~Timer#execute execute}
 * @see {@link module:chronoman~Timer#getPeriod getPeriod}
 */
Timer.prototype._setTimeout = function() {
    "use strict";
    var nPeriod = this.getPeriod();
    if (typeof nPeriod === "number") {
        this._timeoutId = setTimeout(this._onTimeoutEnd, nPeriod);
    }
    return this;
};

/**
 * Cancel execution of scheduled action.
 *
 * @return {Object}
 *      Reference to <code>this</code> object.
 * @method
 * @protected
 * @see {@link module:chronoman~Timer#_setTimeout _setTimeout}
 * @see {@link module:chronoman~Timer#_timeoutId _timeoutId}
 */
Timer.prototype._clearTimeout = function() {
    "use strict";
    if (this._timeoutId) {
        clearTimeout(this._timeoutId);
        this._timeoutId = null;
    }
    return this;
};

/**
 * Indicates whether timer is in use.
 * 
 * @protected
 * @type {Boolean}
 * @see {@link module:chronoman~Timer#execute execute}
 */
Timer.prototype._active = false;

/**
 * Test whether timer is in use.
 *
 * @return {Boolean}
 *      <code>true</code>, if timer is in use, otherwise <code>false</code>.
 * @method
 * @see {@link module:chronoman~Timer#_active _active}
 */
Timer.prototype.isActive = function() {
    return this._active;
};

/**
 * Set or cancel timer usage.
 * Depending of this schedules related action execution or cancels action execution.
 * <br>
 * Consecutive calling with <code>bActive = true</code> leads to related action execution delaying.
 *
 * @param {Boolean} bActive
 *      <code>true</code> to schedule related action execution, <code>false</code> to cancel action execution.
 * @return {Object}
 *      Reference to <code>this</code> object.
 * @method
 * @see {@link module:chronoman~Timer#_active _active}
 * @see {@link module:chronoman~Timer#_executionQty _executionQty}
 * @see {@link module:chronoman~Timer#execute execute}
 */
Timer.prototype.setActive = function(bActive) {
    "use strict";
    if (bActive && ! this._active) {
        this._executionQty = 0;
    }
    this._active = bActive;
    // Consecutive calling with bActive = true leads to action execution delaying
    this._clearTimeout();
    if (bActive) {
        this._setTimeout();
    }
    return this;
};

/**
 * Start timer usage (make it active).
 *
 * @param {Integer | Object} [property]
 *      Time period in milliseconds that is used to schedule related action execution
 *      (new value for {@link module:chronoman~Timer#setPeriod period} property)
 *      or object that specifies new values for timer properties (see {@link module:chronoman~Timer#setProperties setProperties}).
 *      The current value of {@link module:chronoman~Timer#getPeriod period} property is used by default.
 * @return {Object}
 *      Reference to <code>this</code> object.
 * @method
 * @see {@link module:chronoman~Timer#setActive setActive}
 * @see {@link module:chronoman~Timer#setPeriod setPeriod}
 * @see {@link module:chronoman~Timer#setProperties setProperties}
 * @see {@link module:chronoman~Timer#stop stop}
 */
Timer.prototype.start = function(property) {
    "use strict";
    if (typeof property === "number") {
        this.setPeriod(property);
    }
    else if (property && typeof property === "object") {
        this.setProperties(property);
    }
    return this.setActive(true);
};

/**
 * Stop timer usage (make it inactive).
 *
 * @return {Object}
 *      Reference to <code>this</code> object.
 * @method
 * @see {@link module:chronoman~Timer#setActive setActive}
 * @see {@link module:chronoman~Timer#start start}
 */
Timer.prototype.stop = function() {
    return this.setActive(false);
};

/**
 * Related action that should be executed after time period is elapsed.
 * <br>
 * The timer instance to which the action is associated will be passed as function's parameter
 * if {@link module:chronoman~Timer#setPassToAction passToAction} property is set to <code>true</code>.
 *
 * @protected
 * @type {Function}
 * @see {@link module:chronoman~Timer#execute execute}
 */
Timer.prototype._action = null;

/**
 * Return function that represents action.
 *
 * @return {Function}
 *      Function that represents action.
 * @method
 * @see {@link module:chronoman~Timer#_action _action}
 */
Timer.prototype.getAction = function() {
    return this._action;
};

/**
 * Set function which represents action that should be executed after time period is elapsed.
 *
 * @param {Function} action
 *      Function that represents action.
 * @return {Object}
 *      Reference to <code>this</code> object.
 * @method
 * @see {@link module:chronoman~Timer#_action _action}
 */
Timer.prototype.setAction = function(action) {
    this._action = action;
    return this;
};

/**
 * Indicates whether the timer instance (<code>this</code>) should be passed into action function when the function is called.
 * 
 * @protected
 * @type {Boolean}
 * @see {@link module:chronoman~Timer#execute execute}
 */
Timer.prototype._passToAction = false;

/**
 * Test whether the timer instance should be passed into action function when the function is called.
 *
 * @return {Boolean}
 *      <code>true</code>, if the timer instance should be passed, otherwise <code>false</code>.
 * @method
 * @see {@link module:chronoman~Timer#_passToAction _passToAction}
 */
Timer.prototype.isPassToAction = function() {
    return this._passToAction;
};

/**
 * Set or cancel passing of timer instance into action function.
 *
 * @param {Boolean} bPass
 *      <code>true</code>, if the timer instance should be passed into action function, 
 *      <code>false</code>, if the instance should not be passed.
 * @return {Object}
 *      Reference to <code>this</code> object.
 * @method
 * @see {@link module:chronoman~Timer#_passToAction _passToAction}
 */
Timer.prototype.setPassToAction = function(bPass) {
    this._passToAction = bPass;
    return this;
};

/**
 * Set timer properties.
 *
 * @param {Object} propMap
 *      Specifies property values. Keys are property names, their values are values of corresponding properties.
 *      The following keys (properties) can be specified:
 *      <table>
 *          <tr>
 *              <th>Name</th>
 *              <th>Type</th>
 *              <th>Description</th>
 *          </tr>
 *          <tr>
 *              <td>action</td>
 *              <td>Function</td>
 *              <td>Related action that should be executed after time period is elapsed.</td>
 *          </tr>
 *          <tr>
 *              <td>active</td>
 *              <td>Boolean</td>
 *              <td>Whether timer usage should be immediately started.</td>
 *          </tr>
 *          <tr>
 *              <td>passToAction</td>
 *              <td>Boolean</td>
 *              <td>Whether the timer instance should be passed into action function when the function is called.</td>
 *          </tr>
 *          <tr>
 *              <td>period</td>
 *              <td>Integer</td>
 *              <td>Time period in milliseconds that is used to schedule related action execution.</td>
 *          </tr>
 *          <tr>
 *              <td>recurrent</td>
 *              <td>Boolean</td>
 *              <td>Whether related action should be executed repeatedly.</td>
 *          </tr>
 *          <tr>
 *              <td>repeatQty</td>
 *              <td>Integer</td>
 *              <td>How many times related action should be repeated after first execution.</td>
 *          </tr>
 *          <tr>
 *              <td>repeatTest</td>
 *              <td>Function</td>
 *              <td>Function that should be used to determine whether action execution should be repeated.</td>
 *          </tr>
 *      </table>
 * @return {Object}
 *      Reference to <code>this</code> object.
 * @method
 * @see {@link module:chronoman~Timer#setAction setAction}
 * @see {@link module:chronoman~Timer#setActive setActive}
 * @see {@link module:chronoman~Timer#setPassToAction setPassToAction}
 * @see {@link module:chronoman~Timer#setPeriod setPeriod}
 * @see {@link module:chronoman~Timer#setRecurrent setRecurrent}
 * @see {@link module:chronoman~Timer#setRepeatQty setRepeatQty}
 * @see {@link module:chronoman~Timer#setRepeatTest setRepeatTest}
 */
Timer.prototype.setProperties = function(propMap) {
    if (propMap && typeof propMap === "object") {
        if ("action" in propMap) {
            this.setAction(propMap.action);
        }
        if ("period" in propMap) {
            this.setPeriod(propMap.period);
        }
        if ("recurrent" in propMap) {
            this.setRecurrent(propMap.recurrent);
        }
        if ("repeatQty" in propMap) {
            this.setRepeatQty(propMap.repeatQty);
        }
        if ("repeatTest" in propMap) {
            this.setRepeatTest(propMap.repeatTest);
        }
        if ("active" in propMap) {
            this.setActive(propMap.active);
        }
        if ("passToAction" in propMap) {
            this.setPassToAction(propMap.passToAction);
        }
    }
    return this;
};

/**
 * Function that should be executed after time period is elapsed.
 * <br>
 * The timer instance to which the function is associated will be passed as function's parameter
 * if {@link module:chronoman~Timer#setPassToAction passToAction} property is set to <code>true</code>.
 *
 * @type {Function}
 * @see {@link module:chronoman~Timer#execute execute}
 */
Timer.prototype.onExecute = null;

/**
 * Execute related action (function).
 * <br>
 * The timer instance to which the action is associated will be passed as function's parameter
 * if {@link module:chronoman~Timer#setPassToAction passToAction} property is set to <code>true</code>.
 * <br>
 * Action's next execution will be scheduled when one of the following conditions is true:
 * <ul>
 * <li>timer is set as recurrent (see {@link module:chronoman~Timer#isRecurrent isRecurrent});
 * <li>specified quantity of repeats is not reached (see {@link module:chronoman~Timer#getRepeatQty getRepeatQty});
 * <li>specified repeat test is passed i.e. the test function returns true value (see {@link module:chronoman~Timer#getRepeatTest getRepeatTest});
 * </ul>
 *
 * @return {Object}
 *      Reference to <code>this</code> object.
 * @method
 * @see {@link module:chronoman~Timer#_active _active}
 * @see {@link module:chronoman~Timer#_executionQty _executionQty}
 * @see {@link module:chronoman~Timer#getAction getAction}
 * @see {@link module:chronoman~Timer#getRepeatQty getRepeatQty}
 * @see {@link module:chronoman~Timer#getRepeatTest getRepeatTest}
 * @see {@link module:chronoman~Timer#isActive isActive}
 * @see {@link module:chronoman~Timer#isPassToAction isPassToAction}
 * @see {@link module:chronoman~Timer#isRecurrent isRecurrent}
 * @see {@link module:chronoman~Timer#onExecute onExecute}
 */
Timer.prototype.execute = function() {
    "use strict";
    /*jshint expr:true, laxbreak:true*/
    var action = this.getAction(),
        bPassToAction = this.isPassToAction(),
        repeatTest = this.getRepeatTest(),
        bActive;
    this._clearTimeout();
    if (action) {
        bPassToAction
            ? action(this)
            : action();
    }
    if (typeof this.onExecute === "function") {
        bPassToAction
            ? this.onExecute(this)
            : this.onExecute();
    }
    this._executionQty++;
    bActive = this.isActive();
    if (bActive
            && (this.isRecurrent()
                    || this.getRepeatQty() >= this._executionQty
                    || (repeatTest && repeatTest(this)) ) ) {
        this._setTimeout();
    }
    else if (bActive && ! this._timeoutId) {
        this._active = false;
    }
    return this;
};

/**
 * Free resources that are allocated for object.
 *
 * @method
 */
Timer.prototype.dispose = function() {
    "use strict";
    this._clearTimeout();
    this._action =
        this._repeatTest =
        this.onExecute =
            null;
};

/**
 * Convert object into string.
 *
 * @method
 */
Timer.prototype.toString = function() {
    "use strict";
    return [
            "Timer: ",
            "active - ", this.isActive(),
            ", period - ", this.getPeriod(),
            ", recurrent - ", this.isRecurrent(),
            ", repeat qty - ", this.getRepeatQty(),
            ", repeat test - ", (this.getRepeatTest() ? "specified" : "no"),
            ", pass to action - ", this.isPassToAction(),
            ", action - ", (this.getAction() ? "specified" : "no"),
            ", execution qty - ", this.getExecutionQty()
            ].join("");
};

// Exports

export default Timer;