UNPKG

overtimer

Version:

Mission critical updateable javascript timer. It can handle overtimes also limits.

584 lines (489 loc) 17.4 kB
'use strict'; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; /** * Enum for Overtimer states. * @type {{CREATED: number, WAITING: number, RUNNING: number, PAUSED: number, STOPPED: number}} */ Overtimer.STATES = { CREATED: 0, WAITING: 1, RUNNING: 2, PAUSED: 3, STOPPED: 4 }; /** * Global timer object for performance. */ Overtimer.global = { callbacks: [], timer: null, updateMs: 1, lastId: 0, /** * Joins the global timer list * @param callback {function} Callback function will trigger ~ every ms * @return {Number} Unique timer id for leave. */ join: function join(callback) { var found = Overtimer.global.callbacks.find(function (f) { return f.callback === callback; }); if (typeof found === 'undefined') { Overtimer.global.lastId += 1; Overtimer.global.callbacks.push({ callback: callback, id: Overtimer.global.lastId }); } else { return found.id; } if (Overtimer.global.timer === null) Overtimer.global.timer = setInterval(Overtimer.global.tick, Overtimer.global.updateMs); return Overtimer.global.lastId; }, /** * Leaves from global timer list * @param id {number} Auto generated id from join for leave. */ leave: function leave(id) { Overtimer.global.callbacks = Overtimer.global.callbacks.filter(function (c) { return c.id !== id; }); if (Overtimer.global.callbacks.length === 0 && Overtimer.global.timer !== null) { clearInterval(Overtimer.global.timer); Overtimer.global.timer = null; } }, /** * Callback trigger function for every ms */ tick: function tick() { Overtimer.global.callbacks.forEach(function (cb) { cb.callback(); }); } }; /** * Overtimer constructor. * @param duration {Number} Duration of timer * @param opts {Object} Overtimer options * @param onFinish {Function} Shorthand for on('finish') * @constructor */ function Overtimer() { var duration = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1000; var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var onFinish = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; var finishEvent = null; var defaults = { duration: duration, overtimeLimit: duration, overtimeBump: duration, poll: 100, delay: 0, repeat: 1, debug: false, start: true }; this.eventHandlers = { 'start': [], 'tick': [], 'stop': [], 'pause': [], 'resume': [], 'delaystart': [], 'delayend': [], 'bump': [], 'repeat': [], 'finish': [], 'update': [], 'poll': [] }; if ((typeof opts === 'undefined' ? 'undefined' : _typeof(opts)) === 'object') { this.options = Object.assign({}, defaults, opts); } else { if (typeof opts === 'function') { finishEvent = opts; } this.options = Object.assign({}, defaults); } if (typeof onFinish === 'function') finishEvent = onFinish; var durationError = false; if (typeof duration !== 'number') { this.log('Duration must be number value.', 1000); durationError = true; } else if (duration <= 0) { this.log('Duration must be bigger than 0.', 1001); durationError = true; } if (durationError) { this.options.duration = 1000; this.options.overtimeLimit = 1000; this.options.overtimeBump = 1000; } // Properties this.version = '0.1.6'; this.globalTimerId = null; this.state = Overtimer.STATES.CREATED; this.createdAt = Date.now(); this.startedAt = -1; this.delayStartedAt = -1; this.delayEndedAt = -1; this.repeatedAt = -1; this.tickedAt = -1; this.stoppedAt = -1; this.finishedAt = -1; this.pausedAt = -1; this.resumedAt = -1; this.bumpedAt = -1; this.pausedTime = -1; this.delayedTime = -1; this.overTime = -1; this.elapsedTime = -1; this.remainingTime = -1; this.totalDelayedTime = -1; this.totalElapsedTime = -1; this.totalRemainingTime = -1; this.currentRepeat = -1; this.repeatDuration = -1; this.repeatDurationWithDelay = -1; this.totalDuration = -1; this.totalDurationWithDelay = -1; this.currentRepeatPercentWithDelay = -1; this.currentRepeatPercent = -1; this.totalPercentWithDelay = -1; this.totalPercent = -1; this.timesUpdatedAt = -1; this.lastPollAt = -1; if (typeof finishEvent === 'function') this.on('finish', finishEvent); if (this.options.start) this.start(); } /** * Logs errors if debug enabled * @param msg {String} Message will be displayed * @param code {Number} Code will be displayed */ Overtimer.prototype.log = function () { var msg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'Unexcepted error.'; var code = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : -1; if (this.options.debug) console.log((code !== -1 ? '( ' + code.toString() + ' ): ' : '') + ' ' + msg); }; /** * Registers callback to event. * @param eventName {String} Selected event name from available events. * @param callback {Function} Function to be triggered when the event occurs. * @return {Boolean} true if succeeded, false if not. */ Overtimer.prototype.on = function (eventName, callback) { if (typeof eventName !== 'string') { this.log('Event name must be string.', 1002); return false; } else if (eventName.length < 1) { this.log('Event name length be bigger than 0.', 1003); return false; } else if (typeof this.eventHandlers[eventName] === 'undefined') { this.log('Event name not registered!', 1004); return false; } else if (typeof callback !== 'function') { this.log('Callback is not function!', 1005); return false; } this.eventHandlers[eventName].push(callback); return true; }; /** * Removes all callbacks or spesific function from event * @param eventName {String} Selected event name from available events. * @param func {Function} Function to be removed. * @return {boolean} true if succeeded, false if not. */ Overtimer.prototype.off = function (eventName) { var func = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; if (typeof eventName !== 'string') { this.log('Event name must be string.', 1006); return false; } else if (eventName.length < 1) { this.log('Event name length be bigger than 0.', 1007); return false; } else if (typeof this.eventHandlers[eventName] === 'undefined') { this.log('Event name not registered!', 1008); return false; } if (typeof func === 'function') this.eventHandlers[eventName] = this.eventHandlers[eventName].filter(function (f) { return f !== func; });else this.eventHandlers[eventName] = []; return true; }; /** * Triggers registered event * @param eventName {string} Event name you want to trigger * @param payload {object} payload for trigger. can be array, or can be object * @return {boolean} Returns true if succeeded, false if not */ Overtimer.prototype.trigger = function (eventName) { var _this = this; for (var _len = arguments.length, payload = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { payload[_key - 1] = arguments[_key]; } if (typeof eventName !== 'string') { this.log('Event name must be string.', 1009); return false; } else if (eventName.length < 1) { this.log("Event name's length must bigger than 1.", 1010); return false; } else if (typeof this.eventHandlers[eventName.toLowerCase()] === 'undefined' || !Array.isArray(this.eventHandlers[eventName.toLowerCase()])) { this.log('Event not found in list.', 1011); return false; } this.eventHandlers[eventName.toLowerCase()].forEach(function (evt) { evt.call.apply(evt, [_this].concat(payload)); }); return true; }; /** * Joins main interval for updates. */ Overtimer.prototype.joinToMainInterval = function () { this.globalTimerId = Overtimer.global.join(this.tickMainInterval.bind(this)); }; /** * All time updates happens in this function. */ Overtimer.prototype.tickMainInterval = function () { var now = Date.now(), diff = now - this.timesUpdatedAt; if (this.state === Overtimer.STATES.RUNNING) { this.elapsedTime += diff; this.remainingTime -= diff; this.totalElapsedTime += diff; this.totalRemainingTime -= diff; this.currentRepeatPercentWithDelay = parseFloat((this.elapsedTime + this.delayedTime) / this.repeatDurationWithDelay * 100); this.totalPercentWithDelay = parseFloat((this.totalElapsedTime + this.totalDelayedTime) / this.totalDurationWithDelay * 100); this.currentRepeatPercent = parseFloat(this.elapsedTime / this.repeatDuration * 100); this.totalPercent = parseFloat(this.totalElapsedTime / this.totalDuration * 100); } else if (this.state === Overtimer.STATES.PAUSED) { this.pausedTime += diff; } else if (this.state === Overtimer.STATES.WAITING) { this.delayedTime += diff; this.totalDelayedTime += diff; this.currentRepeatPercentWithDelay = parseFloat((this.elapsedTime + this.delayedTime) / this.repeatDurationWithDelay * 100); this.totalPercentWithDelay = parseFloat((this.totalElapsedTime + this.totalDelayedTime) / this.totalDurationWithDelay * 100); if (this.delayedTime >= this.options.delay) this.endDelay(); } this.timesUpdatedAt = now; this.trigger('update'); if (this.lastPollAt + this.options.poll < Date.now()) { this.trigger('poll'); this.lastPollAt = Date.now(); } if (this.remainingTime < 1) this.tick(); }; /** * Leaves from main interval */ Overtimer.prototype.leaveFromMainInterval = function () { Overtimer.global.leave(this.globalTimerId); this.globalTimerId = null; }; /** * Increases the current timer's duration to within the maximum timeout limit. * @param customValue {number} Custom duration for bump duration. OvertimeBump option will use if not specified. * @return {boolean} true if bump success, false if not */ Overtimer.prototype.bump = function () { var customValue = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : -1; if (this.state === Overtimer.STATES.STOPPED || this.state === Overtimer.STATES.CREATED) { this.log('Can\'t use overtime bump on stopped or created state.', 1011); return false; } else if (typeof this.options.overtimeLimit !== 'number' || this.options.overtimeLimit <= 0) { this.log('Can\'t use overtime bump when overtime limit below 0.', 1012); return false; } var mustBumpFor = typeof customValue === 'number' && customValue > 0 ? customValue : this.options.overtimeBump; if (typeof mustBumpFor !== 'number' || mustBumpFor <= 0) { this.log('Bump value is not valid! Must be number and bigger than 0 for bump:', 1013); this.log(mustBumpFor, 1014); return false; } var maxBump = this.options.overtimeLimit - this.remainingTime; if (maxBump < 0) { this.log('Timer not reached the overtime limit yet.', 1015); return false; } else if (mustBumpFor < maxBump) { this.remainingTime += mustBumpFor; this.totalRemainingTime += mustBumpFor; this.overTime += mustBumpFor; this.repeatDuration += mustBumpFor; this.repeatDurationWithDelay += mustBumpFor; this.totalDuration += mustBumpFor; this.totalDurationWithDelay += mustBumpFor; this.bumpedAt = Date.now(); this.trigger('bump', mustBumpFor, this.remainingTime); } else { this.remainingTime += maxBump; this.totalRemainingTime += maxBump; this.overTime += maxBump; this.repeatDuration += maxBump; this.repeatDurationWithDelay += maxBump; this.totalDuration += maxBump; this.totalDurationWithDelay += maxBump; this.bumpedAt = Date.now(); this.trigger('bump', maxBump, this.remainingTime); } return true; }; /** * Starts the timer * @return {boolean} true if succeeded, false if not */ Overtimer.prototype.start = function () { if (this.state === Overtimer.STATES.RUNNING || this.state === Overtimer.STATES.WAITING) { this.log('Timer is already started.', 1016); return false; } var totalDuration = this.options.duration * this.options.repeat; if (this.options.delay > 0) { this.state = Overtimer.STATES.WAITING; this.delayedTime = 0; this.totalDelayedTime = 0; this.delayStartedAt = Date.now(); this.totalDurationWithDelay = totalDuration + this.options.repeat * this.options.delay; this.repeatDurationWithDelay = this.options.duration + this.options.delay; } else { this.state = Overtimer.STATES.RUNNING; this.totalDurationWithDelay = totalDuration; this.repeatDurationWithDelay = this.options.duration; } this.startedAt = Date.now(); this.elapsedTime = 0; this.totalElapsedTime = 0; this.pausedTime = 0; this.overTime = 0; this.remainingTime = this.options.duration; this.repeatDuration = this.options.duration; this.totalRemainingTime = totalDuration; this.totalDuration = totalDuration; this.currentRepeat = 1; this.currentRepeatPercentWithDelay = 0; this.currentRepeatPercent = 0; this.totalPercentWithDelay = 0; this.totalPercent = 0; this.timesUpdatedAt = Date.now(); this.lastPollAt = Date.now(); this.joinToMainInterval(); this.trigger('start'); if (this.options.delay > 0) this.trigger('delaystart'); return true; }; /** * Pauses the timer * @return {boolean} true if succeeded, false if not */ Overtimer.prototype.pause = function () { if (this.state !== Overtimer.STATES.RUNNING && this.state !== Overtimer.STATES.WAITING) { this.log("Can't pause when timer not running.", 1017); return false; } this.state = Overtimer.STATES.PAUSED; this.pausedAt = Date.now(); return true; }; /** * Ends delay immediately * @return {boolean} return true if succeeded, false if not */ Overtimer.prototype.endDelay = function () { if (this.state !== Overtimer.STATES.WAITING) { this.log("Can't end delay when timer not waiting.", 1018); return false; } else if (typeof this.options.delay !== 'number' || this.options.delay <= 0) { this.log("Can't end delay when delay option not number or delay below 0.", 1019); return false; } var remainingDelay = this.options.delay - this.delayedTime; this.delayEndedAt = Date.now(); this.state = Overtimer.STATES.RUNNING; if (remainingDelay > 0) { this.totalDuration -= remainingDelay; this.totalDurationWithDelay -= remainingDelay; this.repeatDuration -= remainingDelay; this.repeatDurationWithDelay -= remainingDelay; } this.trigger('delayend'); return true; }; /** * Resumes the paused timer * @return {boolean} true if succeeded, false if not */ Overtimer.prototype.resume = function () { if (this.state !== Overtimer.STATES.PAUSED) { this.log("Can't resume when timer not paused.", 1020); return false; } if (this.options.delay > 0 && this.delayedTime < this.options.delay) this.state = Overtimer.STATES.WAITING;else this.state = Overtimer.STATES.RUNNING; this.resumedAt = Date.now(); return true; }; /** * Repeats the loop * @return {boolean} Returns true if succeeded, false if not */ Overtimer.prototype.repeat = function () { if (this.state !== Overtimer.STATES.RUNNING && this.state !== Overtimer.STATES.WAITING) { this.log("Can't repeat when timer not running.", 1021); return false; } this.totalDuration -= this.remainingTime; this.totalDurationWithDelay -= this.remainingTime; this.currentRepeat += 1; this.elapsedTime = 0; this.remainingTime = this.options.duration; this.repeatedAt = Date.now(); this.totalRemainingTime = this.options.repeat * this.options.duration - (this.currentRepeat - 1) * this.options.duration; this.currentRepeatPercentWithDelay = 0; this.currentRepeatPercent = 0; if (this.options.delay > 0) { this.delayedTime = 0; this.state = Overtimer.STATES.WAITING; } else this.state = Overtimer.STATES.RUNNING; this.timesUpdatedAt = Date.now(); this.trigger('repeat'); if (this.options.delay > 0) this.trigger('delaystart'); return true; }; /** * Works when timer tick time comes. */ Overtimer.prototype.tick = function () { this.tickedAt = Date.now(); this.trigger('tick'); if (this.currentRepeat < this.options.repeat) this.repeat();else { this.finishedAt = Date.now(); this.totalRemainingTime = 0; this.remainingTime = 0; this.stoppedAt = Date.now(); this.totalPercentWithDelay = 100; this.totalPercent = 100; this.currentRepeatPercent = 100; this.currentRepeatPercentWithDelay = 100; this.trigger('update'); this.trigger('poll'); this.trigger('finish'); this.stop(); } }; /** * Stops the timer * @return {boolean} Returns true if succeeded, false if not */ Overtimer.prototype.stop = function () { if (this.state === Overtimer.STATES.STOPPED) { this.log('Timer is already stopped.', 1022); return false; } this.leaveFromMainInterval(); this.state = Overtimer.STATES.STOPPED; this.stoppedAt = Date.now(); this.trigger('stop'); return true; }; if (typeof module !== 'undefined') module.exports = Overtimer; if (typeof window !== 'undefined') window.Overtimer = Overtimer; //# sourceMappingURL=overtimer.js.map