Source: heartbeat.js

/**
 * @namespace heartbeatjs
 * @author Andrew Riker (ariker)
 * @memberof com.github.ariker
 * @license The MIT License (MIT)
 */
define(['jquery'], function($) {
	var namespace = {
		
		/**
		 * Collects callback functions and executes them at a specified interval
		 * @constructor
		 * @alias Heartbeat
		 * @memberof com.github.ariker.heartbeatjs
		 * @inner
		 */
		Heartbeat: function() {

			/**
			 * Starts the heartbeat
			 * @param {number=} pulse - Callback execution interval (milliseconds)
			 * @public
			 */
			this.start = function(pulse) {
				this.state = 'started';
				this.pulse = (typeof pulse === 'undefined') ? this.pulse : pulse;
				this.preventBeat = false;
				this._beat();
			};
			
			/**
			 * Executes each callback and sets the time for the next beat
			 * @private
			 */
			this._beat = function() {
				if(this.preventBeat) {return;}
				if(this.beatSkips > 0) {this.beatSkips--;}
				else {this.callbacks.execute();}
				
				var obj = this;
				this.timeout = window.setTimeout(
					function() {obj._beat();},
					obj.pulse
				);
			};
			
			/**
			 * Skip a number of beats
			 * @param {number=} [beats=1] - Number of beats to skip
			 * @param {boolean=} [add=true] - Whether to add the skips instead of replacing 
			 * @public
			 */
			this.skip = function(beats, add) {
				beats = (typeof beats === 'undefined') ? 1 : beats;
				add = (typeof add === 'undefined') ? true : add;
				
				if(add) {this.beatSkips += beats;}
				else {this.beatSkips = beats;}
				this.state = 'delayed';
			};
			
			/**
			 * Returns the number of beats to skip
			 * @returns {number}
			 * @public
			 */
			this.getBeatSkips = function() {
				return this.beatSkips;
			};
			
			/**
			 * Stops callback execution
			 * @public
			 */
			this.stop = function() {
				this.state = 'stopped';
				window.clearTimeout(this.timeout);
			};
			
			/**
			 * Returns the pulse interval
			 * @returns {Number}
			 * @public
			 */
		    this.getPulse = function() {
				return this.pulse;
			};
			
			/**
			 * Sets the pulse interval
			 * @param {number} pulse - Callback execution interval (milliseconds)
			 * @public
			 */
			this.setPulse = function(pulse) {
				this.pulse = pulse;
			};
			
			/**
			 * Returns the callback queue
			 * @returns {Callbacks}
			 * @public
			 */
			this.getCallbacks = function() {
				return this.callbacks;
			};
			
			/**
			 * Returns the display name of the class
			 * @returns {String} "stopped", "started", "delayed"
			 * @public
			 */
			this.getClassDisplayName = function() {
				return 'Heartbeat';
			};
			
			/**
			 * Returns the state of the heartbeat
			 * @returns {String}
			 * @public
			 */
			this.getState = function() {
				return this.state;
			};
			
			/**
			 * Window timeout function
			 * @type {function}
			 * @private
			 */
			this.timeout = null;
			
			/**
			 * Callback execution interval (milliseconds)
			 * @type {number}
			 * @private
			 */
			this.pulse = null;
			
			/**
			 * Number of beats to skip
			 * @type {number}
			 * @private
			 */
			this.beatSkips = 0;
			
			/**
			 * State of the heartbeat ("stopped", "started", "delayed")
			 * @type {String}
			 * @private
			 */
			this.state = 'stopped';
			
			/**
			 * Callback queue
			 * @type {Callbacks}
			 * @private
			 */
			this.callbacks = new this.namespace.CallbackJS.Callbacks();
		},
			
			
		/**
		 * Collects and executes callback functions
		 * @constructor
		 * @alias Callbacks
		 * @memberof com.github.ariker.heartbeatjs
		 * @inner
		 */
		Callbacks: function() {
			
			/**
			 * Register a callback function for execution
			 * @param {function} callback - Function to call during execution
			 * @public
			 */
			this.register = function(callback) {
				this.callbacks.push(callback);
			};
			
			/**
			 * Executes all registered callback functions
			 * @param {any=} changedObj - Object that was changed
			 * @param {Array.<string>=} changedAttributes - Attributes of the object that changed
			 * @param {CallbackObject=} prevCallbackObject - Previous callback object in the callback chain
			 * @public
			 */
			this.execute = function(changedObj, changedAttributes, prevCallbackObject) {
				//TODO document namespace at class level
				var CallbackObject = this.namespace.CallbackObject;
				$.each(this.callbacks, function(callbackIndex, callback) {
					try {
						callback(new CallbackObject(changedObj, changedAttributes, prevCallbackObject));
					}
					catch(exception) {return true;}
				});
			};
			
			/**
			 * Returns the display name of the class
			 * @returns {string}
			 * @public
			 */
			this.getClassDisplayName = function() {
				return 'Callbacks';
			};
			
			/**
			 * Callback queue
			 * @type {Array}
			 * @private
			 */
			this.callbacks = [];
		},
		
		/**
		 * Collects and executes callback functions
		 * @constructor
		 * @alias CallbackObject
		 * @param {any} changedObject - Changed object
		 * @param {string[]} changedAttributes - Attributes that changed
		 * @param {CallbackObject=} prevCallbackObject - Previous callback object in the callback chain
		 * @param {CallbackObject=} nextCallbackObject - Next callback object in the callback chain
		 * @memberof com.github.ariker.heartbeatjs
		 * @inner
		 */
		CallbackObject: function(changedObject, changedAttributes, prevCallbackObject, nextCallbackObject) {
			
			/**
			 * Returns the changed object
			 * @returns {any}
			 * @public
			 */
			this.getChangedObject = function() {
				return this.changedObject;
			};
			
			/**
			 * Sets the changed object
			 * @param {any} changedObject - Object that was changed
			 * @public
			 */
			this.setChangedObject = function(changedObject) {
				this.changedObject = changedObject;
			};
			
			/**
			 * Determines whether the specified attribute changed in the object
			 * @param {string} attribute - Attribute name
			 * @returns {Boolean}
			 * @public
			 */
			this.attributeChanged = function(attribute) {
				if($.inArray(attribute, this.changedAttributes) >= 0) {return true;}
				return false;
			};
			
			/**
			 * Returns the previous callback object in the callback chain
			 * @returns {CallbackObject}
			 * @public
			 */
			this.getPrevCallbackObject = function() {
				return this.prevCallbackObject;
			};
			
			/**
			 * Returns the attributes that changed
			 * @returns {string[]}
			 * @public
			 */
			this.getChangedAttributes = function() {
				return this.changedAttributes;
			};
			
			/**
			 * Sets the changed attributes
			 * @param {string[]} changedAttributes - Attributes that changed
			 * @public
			 */
			this.setChangedAttributes = function(changedAttributes) {
				this.changedAttributes = changedAttributes;
			};
			
			/**
			 * Adds a changed attribute
			 * @param {string} changedAttribute - Attribute that changed
			 * @public
			 */
			this.addChangedAttribute = function(changedAttribute) {
				this.changedAttributes.push(changedAttribute);
			};
			
			/**
			 * Sets the previous callback object in the callback chain
			 * @param {CallbackObject} prevCallbackObject - Previous callback object in the callback chain
			 * @param {boolean=} connect - Whether to connect to the next object in the callback chain
			 * @public
			 */
			this.setPrevCallbackObject = function(prevCallbackObject, connect) {
				this.prevCallbackObject = prevCallbackObject;
				connect = (typeof connect === 'undefined') ? true : connect;
				
				if(connect && prevCallbackObject.getNextCallbackObject() !== this) {
					this.prevCallbackObject = prevCallbackObject;
					prevCallbackObject.setNextCallbackObject(this, false);
				}
			};
			
			/**
			 * Gets the next callback object in the callback chain
			 * @returns {CallbackObject}
			 * @public
			 */
			this.getNextCallbackObject = function() {
				return this.nextCallbackObject;
			};
			
			/**
			 * Sets the next callback object in the callback chain
			 * @param {CallbackObject} nextCallbackObject - Next callback object in the callback chain
			 * @param connect
			 * @public
			 */
			this.setNextCallbackObject = function(nextCallbackObject, connect) {
				this.nextCallbackObject = nextCallbackObject;
				connect = (typeof connect === 'undefined') ? true : connect;
				
				if(connect && nextCallbackObject.getPrevCallbackObject() !== this) {
					this.nextCallbackObject = nextCallbackObject;
					nextCallbackObject.setPrevCallbackObject(this, false);
				}
			};
			
			/**
			 * Returns the display name of the class
			 * @returns {string}
			 * @public
			 */
			this.getClassDisplayName = function() {
				return 'Callback Object';
			};
			
			/**
			 * Changed object
			 * @type {any}
			 * @private
			 */
			this.changedObject = changedObject;
			
			/**
			 * Changed attributes
			 * @type {string[]}
			 * @private
			 */
			this.changedAttributes = typeof changedAttributes === 'undefined' ? [] : changedAttributes;
			
			/**
			 * Previous callback object in the callback chain
			 * @type {CallbackObject}
			 * @private
			 */
			this.prevCallbackObject = null;
			
			/**
			 * Next callback object in the callback chain
			 * @type {CallbackObject}
			 * @private
			 */
			this.nextCallbackObject = null;
			
			if(typeof prevCallbackObject !== 'undefined') {this.setPrevCallbackObject(prevCallbackObject);}
			if(typeof nextCallbackObject !== 'undefined') {this.setNextCallbackObject(nextCallbackObject);}
		}
	};
	
	namespace.Heartbeat.prototype.namespace = namespace;
	namespace.Callbacks.prototype.namespace = namespace;
	namespace.CallbackObject.prototype.namespace = namespace;
	
	return namespace;
});