UNPKG

stopwatch-emitter

Version:
476 lines (409 loc) 15.8 kB
/** * EventEmitter v4.0.5 - git.io/ee * Oliver Caldwell * MIT license * @preserve */ ;(function(exports) { // JSHint config - http://www.jshint.com/ /*jshint laxcomma:true*/ /*global define:true*/ // Place the script in strict mode 'use strict'; /** * Class for managing events. * Can be extended to provide event functionality in other classes. * * @class Manages event registering and emitting. */ function EventEmitter(){} // Shortcuts to improve speed and size // Easy access to the prototype var proto = EventEmitter.prototype // Existence of a native indexOf , nativeIndexOf = Array.prototype.indexOf ? true : false; /** * Finds the index of the listener for the event in it's storage array * * @param {Function} listener Method to look for. * @param {Function[]} listeners Array of listeners to search through. * @return {Number} Index of the specified listener, -1 if not found */ function indexOfListener(listener, listeners) { // Return the index via the native method if possible if(nativeIndexOf) { return listeners.indexOf(listener); } // There is no native method // Use a manual loop to find the index var i = listeners.length; while(i--) { // If the listener matches, return it's index if(listeners[i] === listener) { return i; } } // Default to returning -1 return -1; } /** * Fetches the events object and creates one if required. * * @return {Object} The events storage object. */ proto._getEvents = function() { return this._events || (this._events = {}); }; /** * Returns the listener array for the specified event. * Will initialise the event object and listener arrays if required. * * @param {String} evt Name of the event to return the listeners from. * @return {Function[]} All listener functions for the event. * @doc */ proto.getListeners = function(evt) { // Create a shortcut to the storage object // Initialise it if it does not exists yet var events = this._getEvents(); // Return the listener array // Initialise it if it does not exist return events[evt] || (events[evt] = []); }; /** * Adds a listener function to the specified event. * The listener will not be added if it is a duplicate. * If the listener returns true then it will be removed after it is called. * * @param {String} evt Name of the event to attach the listener to. * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling. * @return {Object} Current instance of EventEmitter for chaining. * @doc */ proto.addListener = function(evt, listener) { // Fetch the listeners var listeners = this.getListeners(evt); // Push the listener into the array if it is not already there if(indexOfListener(listener, listeners) === -1) { listeners.push(listener); } // Return the instance of EventEmitter to allow chaining return this; }; /** * Alias of addListener * @doc */ proto.on = proto.addListener; /** * Removes a listener function from the specified event. * * @param {String} evt Name of the event to remove the listener from. * @param {Function} listener Method to remove from the event. * @return {Object} Current instance of EventEmitter for chaining. * @doc */ proto.removeListener = function(evt, listener) { // Fetch the listeners // And get the index of the listener in the array var listeners = this.getListeners(evt) , index = indexOfListener(listener, listeners); // If the listener was found then remove it if(index !== -1) { listeners.splice(index, 1); // If there are no more listeners in this array then remove it if(listeners.length === 0) { this.removeEvent(evt); } } // Return the instance of EventEmitter to allow chaining return this; }; /** * Alias of removeListener * @doc */ proto.off = proto.removeListener; /** * Adds listeners in bulk using the manipulateListeners method. * If you pass an object as the second argument you can add to multiple events at once. The object should contain key value pairs of events and listeners or listener arrays. * You can also pass it an event name and an array of listeners to be added. * * @param {String|Object} evt An event name if you will pass an array of listeners next. An object if you wish to add to multiple events at once. * @param {Function[]} [listeners] An optional array of listener functions to add. * @return {Object} Current instance of EventEmitter for chaining. * @doc */ proto.addListeners = function(evt, listeners) { // Pass through to manipulateListeners return this.manipulateListeners(false, evt, listeners); }; /** * Removes listeners in bulk using the manipulateListeners method. * If you pass an object as the second argument you can remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays. * You can also pass it an event name and an array of listeners to be removed. * * @param {String|Object} evt An event name if you will pass an array of listeners next. An object if you wish to remove from multiple events at once. * @param {Function[]} [listeners] An optional array of listener functions to remove. * @return {Object} Current instance of EventEmitter for chaining. * @doc */ proto.removeListeners = function(evt, listeners) { // Pass through to manipulateListeners return this.manipulateListeners(true, evt, listeners); }; /** * Edits listeners in bulk. The addListeners and removeListeners methods both use this to do their job. You should really use those instead, this is a little lower level. * The first argument will determine if the listeners are removed (true) or added (false). * If you pass an object as the second argument you can add/remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays. * You can also pass it an event name and an array of listeners to be added/removed. * * @param {Boolean} remove True if you want to remove listeners, false if you want to add. * @param {String|Object} evt An event name if you will pass an array of listeners next. An object if you wish to add/remove from multiple events at once. * @param {Function[]} [listeners] An optional array of listener functions to add/remove. * @return {Object} Current instance of EventEmitter for chaining. * @doc */ proto.manipulateListeners = function(remove, evt, listeners) { // Initialise any required variables var i , value , single = remove ? this.removeListener : this.addListener , multiple = remove ? this.removeListeners : this.addListeners; // If evt is an object then pass each of it's properties to this method if(typeof evt === 'object') { for(i in evt) { if(evt.hasOwnProperty(i) && (value = evt[i])) { // Pass the single listener straight through to the singular method if(typeof value === 'function') { single.call(this, i, value); } else { // Otherwise pass back to the multiple function multiple.call(this, i, value); } } } } else { // So evt must be a string // And listeners must be an array of listeners // Loop over it and pass each one to the multiple method i = listeners.length; while(i--) { single.call(this, evt, listeners[i]); } } // Return the instance of EventEmitter to allow chaining return this; }; /** * Removes all listeners from a specified event. * If you do not specify an event then all listeners will be removed. * That means every event will be emptied. * * @param {String} [evt] Optional name of the event to remove all listeners for. Will remove from every event if not passed. * @return {Object} Current instance of EventEmitter for chaining. * @doc */ proto.removeEvent = function(evt) { // Remove different things depending on the state of evt if(evt) { // Remove all listeners for the specified event delete this._getEvents()[evt]; } else { // Remove all listeners in all events delete this._events; } // Return the instance of EventEmitter to allow chaining return this; }; /** * Emits an event of your choice. * When emitted, every listener attached to that event will be executed. * If you pass the optional argument array then those arguments will be passed to every listener upon execution. * Because it uses `apply`, your array of arguments will be passed as if you wrote them out separately. * So they will not arrive within the array on the other side, they will be separate. * * @param {String} evt Name of the event to emit and execute listeners for. * @param {Array} [args] Optional array of arguments to be passed to each listener. * @return {Object} Current instance of EventEmitter for chaining. * @doc */ proto.emitEvent = function(evt, args) { // Get the listeners for the event // Also initialise any other required variables var listeners = this.getListeners(evt) , i = listeners.length , response; // Loop over all listeners assigned to the event // Apply the arguments array to each listener function while(i--) { // If the listener returns true then it shall be removed from the event // The function is executed either with a basic call or an apply if there is an args array response = args ? listeners[i].apply(null, args) : listeners[i](); if(response === true) { this.removeListener(evt, listeners[i]); } } // Return the instance of EventEmitter to allow chaining return this; }; /** * Alias of emitEvent * @doc */ proto.trigger = proto.emitEvent; /** * Subtly different from emitEvent in that it will pass its arguments on to the listeners, as * opposed to taking a single array of arguments to pass on. * * @param {String} evt Name of the event to emit and execute listeners for. * @param {...*} Optional additional arguments to be passed to each listener. * @return {Object} Current instance of EventEmitter for chaining. * @doc */ proto.emit = function(evt) { var args = Array.prototype.slice.call(arguments, 1); return this.emitEvent(evt, args); }; // Expose the class either via AMD or the global object if(typeof define === 'function' && define.amd) { define(function() { return EventEmitter; }); } else { exports.EventEmitter = EventEmitter; } }(this)); /* global EventEmitter, Timer */ (function(exports){ var hasProp = {}.hasOwnProperty , _extends = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; var Stopwatch = (function(_super){ _extends(Stopwatch, _super); function Stopwatch(maxTime) { // Set defaults if (maxTime == null) maxTime = '5m'; this.currentTime = 0; this._stopEmitted = false; this._running = false; this.toQ = []; this.parseTime(maxTime); this._setupEvents(); } var proto = Stopwatch.prototype; // Translate strings into seconds and set maxTime proto.parseTime = function(time) { // Create an override for numbers. We'll expect seconds. if (!isNaN(time)) return this.maxTime = time; time = time.toString().match(/([\d\.]+)(\w{1})/); var timeValue = parseFloat(time[1], 10) , timeInterval = time[2]; switch (timeInterval) { case 's': timeValue *= 1; break; case 'm': timeValue *= 60; break; case 'h': timeValue *= 60 * 60; } this.maxTime = Math.round(timeValue); }; // Setup some instance events proto._setupEvents = function() { var self = this; // Stop emitted tells us we specifically called stop so the we don't // emit twice. self.on('stop', function(){ self._stopEmitted = true; self.clear(); }); // Make sure to toggle stopEmitted we any other event fires // Also, clear any timeouts if we are already running. var stopCb = function() { self._stopEmitted = false; if (self._running) self.clear(); }; self.on('start', stopCb); self.on('pause', stopCb); self.on('restart', stopCb); }; proto.tick = function() { var self = this; if (self.currentTime >= self.maxTime) { if (!self._stopEmitted) self.emit('stop'); // If we reach the end self._running = false; // Make sure we're not running and return return; } // Make sure nothing naughty gets through if (!self._running) return; // Emit the tick self.emit('tick'); // Store the timeout so we can clear it later if necessary var tO = setTimeout(function(){ self.currentTime++; self.tick(); }, 1000); // Push to timeout queue self.toQ.push(tO); }; // Clear the timeout queue proto.clear = function() { var q = this.toQ; while(q.length) { clearTimeout(q[q.length - 1]); q.pop(); } }; proto.pause = function() { if (!this._running) return; this._running = false; this.emit('pause'); }; proto.stop = function() { if (!this._running) return; this._running = false; this.currentTime = 0; this.emit('stop'); }; proto.restart = function() { this.currentTime = 0; this.emit('restart'); this.start(false); }; proto.start = function(emit) { if (emit == null) emit = true; if ((this._running && emit) || this.currentTime >= this.maxTime) return; if (emit) this.emit('start'); this._running = true; this.tick(); }; proto.getCurrentTime = function() { return this.currentTime; }; proto.getRemainingTime = function() { return this.maxTime - this.currentTime; }; proto.getMaxTime = function() { return this.maxTime; }; proto.isRunning = function() { return !!this._running; }; return Stopwatch; }(EventEmitter)); exports.Stopwatch = Stopwatch; }(this));