app-actions.js

/**
 * Actions
 *
 * @fileoverview Defines the App Actions
 *
 * @author Deux Huit Huit <https://deuxhuithuit.com>
 * @license MIT <https://deuxhuithuit.mit-license.org>
 *
 * @requires jQuery
 * @namespace App.actions
 */
(function ($, global, undefined) {
	'use strict';
	var keys = {};
	var innerCall = false;
	var stack = [];

	/**
	 * Find the methods that matches with the notify key
	 * @name resolve
	 * @memberof App.actions
	 * @method
	 * @param {Function|Object} actions Object of methods that can be matches with the key's value
	 * @param {String} key Action key
	 * @returns {Function} The function corresponding to the key, if it exists in actions object
	 * @private
	 */
	var resolve = function (actions, key) {
		if ($.isFunction(actions)) {
			actions = actions();
		}
		if (!!actions) {
			// Try the whole key
			var tempFx = actions[key];
			// If not, try JSONPath style...
			if (!$.isFunction(tempFx)) {
				var paths = keys[key] || key.split('.');
				if (paths.length < 2) {
					return;
				}
				keys[key] = paths;
				tempFx = actions;
				paths.every(function eachPath (p) {
					tempFx = tempFx[p];
					if (!$.isPlainObject(tempFx)) {
						return false; // exit
					}
					return true;
				});
			}
			if ($.isFunction(tempFx)) {
				return tempFx;
			}
		}
	};

	/**
	 * Executes all read and write operations present in the actions array.
	 * @name execute
	 * @memberof App.actions
	 * @method
	 * @param {Array} actions Array of read/write objects
	 * @param {String} key Action key
	 * @param {Object} data Bag of data
	 * @returns {undefined}
	 * @private
	 */
	var execute = function (actions, key, data, cb) {
		var sp = 0;
		var outerCall = false;
		var read = function (f) {
			if ($.isFunction(f.read)) {
				f.read(key, data);
			}
		};
		var write = function (f) {
			f.write(key, data);
		};
		if (!innerCall) {
			innerCall = true;
			outerCall = true;
		}
		if (!$.isArray(actions)) {
			actions = [actions];
		}
		if ($.isFunction(data) && !cb) {
			cb = data;
			data = undefined;
		}
		// Push all resolved actions to the stack
		actions.forEach(function eachAction (a, index) {
			var retValue = App.callback(a, [key, data]);
			if (!!cb && retValue !== undefined) {
				App.callback(cb, [index, retValue]);
			}
			if ($.isFunction(retValue)) {
				retValue = {
					read: null,
					write: retValue
				};
			}
			if ($.isPlainObject(retValue) && $.isFunction(retValue.write)) {
				if (App.debug() && !retValue.key) {
					retValue.key = key;
				}
				stack.push(retValue);
			}
		});
		// If outerCall, empty the stack
		while (outerCall && stack.length > sp) {
			// Capture current end
			var sLen = stack.length;
			// Process current range only
			for (var x = sp; x < sLen; x++) {
				read(stack[x]);
			}
			for (x = sp; x < sLen; x++) {
				write(stack[x]);
			}
			// Advance the stack pointer
			sp = sLen;
		}
		if (outerCall) {
			// clean up
			innerCall = false;
			stack = [];
		}
	};

	/** Public Interfaces **/
	global.App = $.extend(true, global.App, {
		/**
		 * @namespace actions
		 * @memberof App
		 */
		actions: {
			/**
			 * Find the methods that matches with the notify key
			 * @name resolve
			 * @memberof App.actions
			 * @method
			 * @param {Function|Object} actions Object of methods that can be matches
			 *   with the key's value
			 * @param {String} key Action key
			 * @returns {Function} The function corresponding to the key, if it exists
			 * @public
			 */
			resolve: resolve,

			/**
			 * Executes all read and write operations present in the actions array.
			 * @name execute
			 * @memberof App.actions
			 * @method
			 * @param {Array} actions Array of read/write objects
			 * @param {String} key Action key
			 * @param {Object} data Bag of data
			 * @returns {undefined}
			 * @public
			 */
			execute: execute,

			/**
			 * @name stack
			 * @memberof App.actions
			 * @method
			 * @returns {Array} All the operations currently in the stack.
			 *   Stack operations can already be executed but still in the stack.
			 * @public
			 */
			stack: function () {
				return stack;
			}
		}
	});
})(jQuery, window);