Show:

src\appHandlers.js File


	                			import dom from './utils/dom';
	                import domify from 'domify';
	                import utils from './utils';
	                
	                // the hidden token that we will check against every time someone tries to add, remove, fire handler
	                var _ct = utils.guid();
	                var _f2t = utils.guid();
	                
	                var _handlerCollection = {
	                	appManifestRequestFail: [],
	                	appCreateRoot: [],
	                	appRenderBefore: [],
	                	appDestroyBefore: [],
	                	appRenderAfter: [],
	                	appDestroyAfter: [],
	                	appRender: [],
	                	appDestroy: [],
	                	appScriptLoadFailed: []
	                };
	                
	                var _defaultMethods = {
	                	appRender: function (appConfig, appHtml) {
	                		// if no app root is defined use the app's outer most node
	                		if (!dom.isNativeNode(appConfig.root)) {
	                			appConfig.root = domify(appHtml);
	                		} else {
	                			// append the app html to the root
	                			appConfig.root.appendChild(domify(appHtml));
	                		}
	                
	                		// append the root to the body by default.
	                		document.body.appendChild(appConfig.root);
	                	},
	                	appDestroy: function (appInstance) {
	                		// call the apps destroy method, if it has one
	                		if (
	                			appInstance &&
	                			appInstance.app &&
	                			appInstance.app.destroy &&
	                			typeof appInstance.app.destroy == 'function'
	                		) {
	                			appInstance.app.destroy();
	                		}
	                		// warn the Container and App Developer that even though they have a destroy method it hasn't been
	                		else if (appInstance && appInstance.app && appInstance.app.destroy) {
	                			utils.log(
	                				appInstance.config.appId +
	                					' has a destroy property, but destroy is not of type function and as such will not be executed.'
	                			);
	                		}
	                
	                		// remove the root
	                		if (appInstance.config.root && appInstance.config.root.parentNode) {
	                			appInstance.config.root.parentNode.removeChild(appInstance.config.root);
	                		}
	                	}
	                };
	                
	                var _createHandler = function (
	                	token,
	                	sNamespace,
	                	func_or_element,
	                	bDomNodeAppropriate
	                ) {
	                	// will throw an exception and stop execution if the token is invalid
	                	_validateToken(token);
	                
	                	// create handler structure. Not all arguments properties will be populated/used.
	                	var handler = {
	                		func: func_or_element || null,
	                		namespace: sNamespace,
	                		domNode: dom.isNativeNode(func_or_element) ? func_or_element : null
	                	};
	                
	                	if (!handler.func && !handler.domNode) {
	                		throw 'Invalid or null argument passed. Handler will not be added to collection. A valid dom element or callback function is required.';
	                	}
	                
	                	if (handler.domNode && !bDomNodeAppropriate) {
	                		throw 'Invalid argument passed. Handler will not be added to collection. A callback function is required for this event type.';
	                	}
	                
	                	return handler;
	                };
	                
	                var _validateToken = function (sToken) {
	                	// check token against F2 and container
	                	if (_ct != sToken && _f2t != sToken) {
	                		throw 'Invalid token passed. Please verify that you have correctly received and stored token from F2.AppHandlers.getToken().';
	                	}
	                };
	                
	                var _removeHandler = function (sToken, eventKey, sNamespace) {
	                	// will throw an exception and stop execution if the token is invalid
	                	_validateToken(sToken);
	                
	                	if (!sNamespace && !eventKey) {
	                		return;
	                	}
	                	// remove by event key
	                	else if (!sNamespace && eventKey) {
	                		_handlerCollection[eventKey] = [];
	                	}
	                	// remove by namespace only
	                	else if (sNamespace && !eventKey) {
	                		sNamespace = sNamespace.toLowerCase();
	                
	                		for (var currentEventKey in _handlerCollection) {
	                			var eventCollection = _handlerCollection[currentEventKey];
	                			var newEvents = [];
	                
	                			for (var i = 0, ec = eventCollection.length; i < ec; i++) {
	                				var currentEventHandler = eventCollection[i];
	                				if (currentEventHandler) {
	                					if (
	                						!currentEventHandler.namespace ||
	                						currentEventHandler.namespace.toLowerCase() != sNamespace
	                					) {
	                						newEvents.push(currentEventHandler);
	                					}
	                				}
	                			}
	                
	                			eventCollection = newEvents;
	                		}
	                	} else if (sNamespace && _handlerCollection[eventKey]) {
	                		sNamespace = sNamespace.toLowerCase();
	                
	                		var newHandlerCollection = [];
	                
	                		for (
	                			var iCounter = 0, hc = _handlerCollection[eventKey].length;
	                			iCounter < hc;
	                			iCounter++
	                		) {
	                			var currentHandler = _handlerCollection[eventKey][iCounter];
	                			if (currentHandler) {
	                				if (
	                					!currentHandler.namespace ||
	                					currentHandler.namespace.toLowerCase() != sNamespace
	                				) {
	                					newHandlerCollection.push(currentHandler);
	                				}
	                			}
	                		}
	                
	                		_handlerCollection[eventKey] = newHandlerCollection;
	                	}
	                };
	                
	                /**
	                 * The `AppHandlers` functionality provides Container Developers a higher level of control over configuring app rendering and interaction.
	                 *
	                 * ### Order of Execution
	                 *
	                 * **App Rendering**
	                 *
	                 * 0. {{#crossLink "F2/registerApps"}}F2.registerApps(){{/crossLink}} method is called by the Container Developer and the following methods are run for *each* {{#crossLink "F2.AppConfig"}}{{/crossLink}} passed.
	                 * 1. **'appCreateRoot'** (*{{#crossLink "F2.Constants.AppHandlers"}}{{/crossLink}}.APP\_CREATE\_ROOT*) handlers are fired in the order they were attached.
	                 * 2. **'appRenderBefore'** (*{{#crossLink "F2.Constants.AppHandlers"}}{{/crossLink}}.APP\_RENDER\_BEFORE*) handlers are fired in the order they were attached.
	                 * 3. Each app's `manifestUrl` is requested asynchronously; on success the following methods are fired.
	                 * 3. **'appRender'** (*{{#crossLink "F2.Constants.AppHandlers"}}{{/crossLink}}.APP\_RENDER*) handlers are fired in the order they were attached.
	                 * 4. **'appRenderAfter'** (*{{#crossLink "F2.Constants.AppHandlers"}}{{/crossLink}}.APP\_RENDER\_AFTER*) handlers are fired in the order they were attached.
	                 *
	                 *
	                 * **App Removal**
	                
	                 * 0. {{#crossLink "F2/removeApp"}}F2.removeApp(){{/crossLink}} with a specific {{#crossLink "F2.AppConfig/instanceId "}}{{/crossLink}} or {{#crossLink "F2/removeAllApps"}}F2.removeAllApps(){{/crossLink}} method is called by the Container Developer and the following methods are run.
	                 * 1. **'appDestroyBefore'** (*{{#crossLink "F2.Constants.AppHandlers"}}{{/crossLink}}.APP\_DESTROY\_BEFORE*) handlers are fired in the order they were attached.
	                 * 2. **'appDestroy'** (*{{#crossLink "F2.Constants.AppHandlers"}}{{/crossLink}}.APP\_DESTROY*) handlers are fired in the order they were attached.
	                 * 3. **'appDestroyAfter'** (*{{#crossLink "F2.Constants.AppHandlers"}}{{/crossLink}}.APP\_DESTROY\_AFTER*) handlers are fired in the order they were attached.
	                 *
	                 * **Error Handling**
	                
	                 * 0. **'appScriptLoadFailed'** (*{{#crossLink "F2.Constants.AppHandlers"}}{{/crossLink}}.APP\_SCRIPT\_LOAD\_FAILED*) handlers are fired in the order they were attached.
	                 *
	                 * @class F2.AppHandlers
	                 */
	                export default {
	                	/**
	                	 * Allows Container Developer to retrieve a unique token which must be passed to
	                	 * all `on` and `off` methods. This function will self destruct and can only be called
	                	 * one time. Container Developers must store the return value inside of a closure.
	                	 * @method getToken
	                	 **/
	                	getToken: function () {
	                		// delete this method for security that way only the container has access to the token 1 time.
	                		// kind of Ethan Hunt-ish, this message will self destruct immediately.
	                		delete this.getToken;
	                		// return the token, which we validate against.
	                		return _ct;
	                	},
	                	/**
	                	 * Allows F2 to get a token internally. Token is required to call {{#crossLink "F2.AppHandlers/\_\_trigger:method"}}{{/crossLink}}.
	                	 * This function will self destruct to eliminate other sources from using the {{#crossLink "F2.AppHandlers/\_\_trigger:method"}}{{/crossLink}}
	                	 * and additional internal methods.
	                	 * @method __f2GetToken
	                	 * @private
	                	 **/
	                	__f2GetToken: function () {
	                		// delete this method for security that way only the F2 internally has access to the token 1 time.
	                		// kind of Ethan Hunt-ish, this message will self destruct immediately.
	                		delete this.__f2GetToken;
	                		// return the token, which we validate against.
	                		return _f2t;
	                	},
	                	/**
	                	 * Allows F2 to trigger specific events internally.
	                	 * @method __trigger
	                	 * @private
	                	 * @chainable
	                	 * @param {String} token The token received from {{#crossLink "F2.AppHandlers/\_\_f2GetToken:method"}}{{/crossLink}}.
	                	 * @param {String} eventKey The event to fire. The complete list of event keys is available in {{#crossLink "F2.Constants.AppHandlers"}}{{/crossLink}}.
	                	 **/
	                	__trigger: function (
	                		token,
	                		eventKey // additional arguments will likely be passed
	                	) {
	                		// will throw an exception and stop execution if the token is invalid
	                		if (token != _f2t) {
	                			throw 'Token passed is invalid. Only F2 is allowed to call F2.AppHandlers.__trigger().';
	                		}
	                
	                		if (_handlerCollection && _handlerCollection[eventKey]) {
	                			// create a collection of arguments that are safe to pass to the callback.
	                			var passableArgs = [];
	                
	                			// populate that collection with all arguments except token and eventKey
	                			for (var i = 2, j = arguments.length; i < j; i++) {
	                				passableArgs.push(arguments[i]);
	                			}
	                
	                			if (
	                				_handlerCollection[eventKey].length === 0 &&
	                				_defaultMethods[eventKey]
	                			) {
	                				_defaultMethods[eventKey].apply(this, passableArgs);
	                				return this;
	                			} else if (
	                				_handlerCollection[eventKey].length === 0 &&
	                				!_handlerCollection[eventKey]
	                			) {
	                				return this;
	                			}
	                
	                			// fire all event listeners in the order that they were added.
	                			for (
	                				var iCounter = 0, hcl = _handlerCollection[eventKey].length;
	                				iCounter < hcl;
	                				iCounter++
	                			) {
	                				var handler = _handlerCollection[eventKey][iCounter];
	                
	                				// appRender where root is already defined
	                				if (
	                					handler.domNode &&
	                					arguments[2] &&
	                					arguments[2].root &&
	                					arguments[3]
	                				) {
	                					arguments[2].root.appendChild(domify(arguments[3]));
	                					handler.domNode.appendChild(arguments[2].root);
	                				} else if (
	                					handler.domNode &&
	                					arguments[2] &&
	                					!arguments[2].root &&
	                					arguments[3]
	                				) {
	                					// set the root to the actual HTML of the app
	                					arguments[2].root = domify(arguments[3]);
	                					// appends the root to the dom node specified
	                					handler.domNode.appendChild(arguments[2].root);
	                				} else {
	                					handler.func.apply(this, passableArgs);
	                				}
	                			}
	                		} else {
	                			throw 'Invalid EventKey passed. Check your inputs and try again.';
	                		}
	                
	                		return this;
	                	},
	                	/**
	                	 * Allows Container Developer to easily tell all apps to render in a specific location. Only valid for eventType `appRender`.
	                	 * @method on
	                	 * @chainable
	                	 * @param {String} token The token received from {{#crossLink "F2.AppHandlers/getToken:method"}}{{/crossLink}}.
	                	 * @param {String} eventKey{.namespace} The event key used to determine which event to attach the listener to. The namespace is useful for removal
	                	 * purposes. At this time it does not affect when an event is fired. Complete list of event keys available in
	                	 * {{#crossLink "F2.Constants.AppHandlers"}}{{/crossLink}}.
	                	 * @params {HTMLElement} element Specific DOM element to which app gets appended.
	                	 * @example
	                	 *	var _token = F2.AppHandlers.getToken();
	                	 *	F2.AppHandlers.on(
	                	 *		_token,
	                	 *		'appRender',
	                	 *		document.getElementById('my_app')
	                	 *	);
	                	 *
	                	 * Or:
	                	 * @example
	                	 *	F2.AppHandlers.on(
	                	 *		_token,
	                	 *		'appRender.myNamespace',
	                	 *		document.getElementById('my_app')
	                	 *	);
	                	 **/
	                	/**
	                	 * Allows Container Developer to add listener method that will be triggered when a specific event occurs.
	                	 * @method on
	                	 * @chainable
	                	 * @param {String} token The token received from {{#crossLink "F2.AppHandlers/getToken:method"}}{{/crossLink}}.
	                	 * @param {String} eventKey{.namespace} The event key used to determine which event to attach the listener to. The namespace is useful for removal
	                	 * purposes. At this time it does not affect when an event is fired. Complete list of event keys available in
	                	 * {{#crossLink "F2.Constants.AppHandlers"}}{{/crossLink}}.
	                	 * @params {Function} listener A function that will be triggered when a specific event occurs. For detailed argument definition refer to {{#crossLink "F2.Constants.AppHandlers"}}{{/crossLink}}.
	                	 * @example
	                	 *	var _token = F2.AppHandlers.getToken();
	                	 *	F2.AppHandlers.on(
	                	 *		_token,
	                	 *		'appRenderBefore'
	                	 *		function() { F2.log('before app rendered!'); }
	                	 *	);
	                	 *
	                	 * Or:
	                	 * @example
	                	 *	F2.AppHandlers.on(
	                	 *		_token,
	                	 *		'appRenderBefore.myNamespace',
	                	 *		function() { F2.log('before app rendered!'); }
	                	 *	);
	                	 **/
	                	on: function (token, eventKey, func_or_element) {
	                		var sNamespace = null;
	                
	                		if (!eventKey) {
	                			throw 'eventKey must be of type string and not null. For available appHandlers check F2.Constants.AppHandlers.';
	                		}
	                
	                		// we need to check the key for a namespace
	                		if (eventKey.indexOf('.') > -1) {
	                			var arData = eventKey.split('.');
	                			eventKey = arData[0];
	                			sNamespace = arData[1];
	                		}
	                
	                		if (_handlerCollection && _handlerCollection[eventKey]) {
	                			_handlerCollection[eventKey].push(
	                				_createHandler(
	                					token,
	                					sNamespace,
	                					func_or_element,
	                					eventKey == 'appRender'
	                				)
	                			);
	                		} else {
	                			throw 'Invalid EventKey passed. Check your inputs and try again.';
	                		}
	                
	                		return this;
	                	},
	                	/**
	                	 * Allows Container Developer to remove listener methods for specific events
	                	 * @method off
	                	 * @chainable
	                	 * @param {String} token The token received from {{#crossLink "F2.AppHandlers/getToken:method"}}{{/crossLink}}.
	                	 * @param {String} eventKey{.namespace} The event key used to determine which event to attach the listener to. If no namespace is provided all
	                	 *  listeners for the specified event type will be removed.
	                	 *  Complete list available in {{#crossLink "F2.Constants.AppHandlers"}}{{/crossLink}}.
	                	 * @example
	                	 *	var _token = F2.AppHandlers.getToken();
	                	 *	F2.AppHandlers.off(_token,'appRenderBefore');
	                	 *
	                	 **/
	                	off: function (token, eventKey) {
	                		var sNamespace = null;
	                
	                		if (!eventKey) {
	                			throw 'eventKey must be of type string and not null. For available appHandlers check F2.Constants.AppHandlers.';
	                		}
	                
	                		// we need to check the key for a namespace
	                		if (eventKey.indexOf('.') > -1) {
	                			var arData = eventKey.split('.');
	                			eventKey = arData[0];
	                			sNamespace = arData[1];
	                		}
	                
	                		if (_handlerCollection && _handlerCollection[eventKey]) {
	                			_removeHandler(token, eventKey, sNamespace);
	                		} else {
	                			throw 'Invalid EventKey passed. Check your inputs and try again.';
	                		}
	                
	                		return this;
	                	}
	                };