blinx
Version:
The Scalable JavaScript Application Framework
597 lines (487 loc) • 21 kB
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: blinx.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: blinx.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/**This is the major framework file.
* @exports {
* createInstance: creates a new instance of the module.
* destroyModuleInstance: destroys the module instance,
* use: use it if you want to extend Blinx
*
* }
*/
import Utils from "./helpers/utils";
import merge from 'lodash/fp/merge';
import Module from "./interfaces/module.js";
import {moduleS, middleWareFns} from "./interfaces/store";
import CONSTANTS from "./constants";
import Devtool from "./devtool";
/**
*
* @param module [Object] Blinx module
* @param eventName [string]
* @private
*/
let _onBreath = function (module, eventName) {
if (module[CONSTANTS.MODULE_EVENTS.onStatusChange]) {
module[CONSTANTS.MODULE_EVENTS.onStatusChange](eventName);
}
};
/**
* Publishes the event when state of the module changes
* @param moduleDetail [object] module
* @param eventName [string]
* @private
*/
let _emitLifeCycleEvent = function (moduleDetail, eventName) {
moduleDetail.publish(`${moduleDetail.getModuleName()}${eventName}`, {
moduleInstanceId: moduleDetail.getUniqueId()
});
};
/**
* This function resolves the Promise if initOn is true and module is already rendered. Refer {@link lifeCycleFlags} for
* initial lifecycle values.
* Calls {@link _callResolveRenderOn} if either case fails
* @param {Module} module. The module to be rendered.
* @returns {Promise}
* @private
*/
let _listenForInitOn = function (module) {
if (module.instanceConfig.initOn || module.lifeCycleFlags.rendered) {
return Promise.resolve(module.path);
} else {
return _callResolveRenderOn(module);
}
};
// STEP:2
/**
*
* @param {Module} module to be rendered.
* @param {*} data the data to be passed to module while initialization
* <p>Calls {@link Module.resolveRenderOn} method . This method is passed from the config for the module and after
* resolveRenderOn is resolved, lifecycle status is changed to "preRenderResolved". The resolveRenderOn should return
* {Promise}</p>
* @private
*/
let _callResolveRenderOn = function (module, data) {
Module.createModuleArena(module);
if (module[CONSTANTS.MODULE_EVENTS.resolveRenderOn]) {
let moduleResoved = module[CONSTANTS.MODULE_EVENTS.resolveRenderOn](data);
if (moduleResoved && moduleResoved.then && typeof moduleResoved.then === "function") {
let onPromiseComplete = (res)=> {
module.lifeCycleFlags.preRenderResolved = true;
_onBreath(module, CONSTANTS.onStatusChange_EVENTS.resolveRenderOnCalled);
return _lockEvents(module, res);
};
return moduleResoved.then(onPromiseComplete).catch(onPromiseComplete);
} else {
_onBreath(module, CONSTANTS.onStatusChange_EVENTS.resolveRenderOnCalled);
return _lockEvents(module, moduleResoved);
}
} else {
_onBreath(module, CONSTANTS.onStatusChange_EVENTS.resolveRenderOnCalled);
return _lockEvents(module, data);
}
};
// STEP:3 [Hot events]
/**
* Subscribes to all the events of type playAfterRender
* @param {Module} module to be rendered.
* @param placeholderResponse
* @private
*/
let _lockEvents = function (module, placeholderResponse) {
module.instanceConfig.listensTo && module.instanceConfig.listensTo.length && module.instanceConfig.listensTo.filter((evt)=> {
if (evt.type === CONSTANTS.EVENT_ENUM.playAfterRender || !evt.type) {
return evt;
}
}).forEach((listener) => {
module.subscribe({
eventName: listener.eventName,
callback: module[listener.callback],
context: module,
eventPublisher: listener.eventPublisher,
once: listener.once
});
});
_onBreath(module, CONSTANTS.onStatusChange_EVENTS.listensToPlayAfterRenderSubscribed);
return _callRender(module, placeholderResponse);
};
// STEP:4
/**
* <p>Renders the module by calling {@link Module.createModuleArena}
* Changes the life cycle flag to rendered thereafter.</p>
* @param {Module} module to be rendered.
* @param placeholderResponse
* @returns {Promise}
* @private
*/
let _callRender = function (module, placeholderResponse) {
// if initOn is present exec below steps on initOn
// exec resolveRenderOn (if available)
// exec render after resolveRenderOn completes
// throw error is render and template are not provided
return new Promise((res, rej) => {
// Null to be replaced with resolveRenderOn data
let compiledHTML = module[CONSTANTS.MODULE_EVENTS.render](placeholderResponse, compiledHTML);
module.lifeCycleFlags.rendered = true;
_emitLifeCycleEvent(module, "_READY");
_onBreath(module, CONSTANTS.onStatusChange_EVENTS.renderCalled);
if (module[CONSTANTS.MODULE_EVENTS.onRenderComplete]) {
module[CONSTANTS.MODULE_EVENTS.onRenderComplete]();
_onBreath(module, CONSTANTS.onStatusChange_EVENTS.onRenderCompleteCalled);
}
res();
// If there are any queued events , dequeue the events based on modules subscriptions
module.dequeueEvents();
});
};
/**
*<p>Called after the module is registered. Responsible for rendering the module.
* The rendering will wait till the event occurs if initOn option is provided. </p>
* <p>This method contains four steps
* <ul>
* <li>call resolveRenderOn method and wait for the promise to be resolved ({@link _callResolveRenderOn})</li>
* <li>subscribe to the events of type "playAfterRender"</li>
* <li>Render the module</li>
* </ul>
* </p>
*
* @recursive
* @param patchModules {Array} The array of modules to be rendered. Initially list is taken from {@link moduleS}
* @param promiseArr {Array} the array of promise objects
* @private
*/
let _startExec = function (patchModules, promiseArr) {
let rootModules = patchModules.filter((module) => {
return !module.meta.parent.id;
});
if (!rootModules.length) {
rootModules = [patchModules[0]];
}
rootModules.forEach((rootModule) => {
// Render this module
let moduleResolvePromise = new Promise(function (resolve, reject) {
_listenForInitOn(rootModule)
.then(() => {
if(rootModule.meta.children && rootModule.meta.children.length) {
rootModule.meta.children && rootModule.meta.children.forEach((module) => {
if(!module.pointer.lifeCycleFlags.rendered) {
_startExec([module.pointer], promiseArr);
}
})
}
resolve(rootModule.meta.id);
});
});
promiseArr.push(moduleResolvePromise);
});
};
/**
*
* @param module {Module} The Module generated by {@link _registerModule}. If the module listens to any event or if the
* module is instantiated based on an event then module is made to subscribe all the events.
* <p>initOn will have following properties
* <ul>
* <li>eventName {String} The name of the event</li>
* <li>eventPublisher {String} [Optional] CSS selector of the publisher of the event to which the module is subscribing.
* The module listens to event from all the publishers if this field is not provided</li>
* <li>context {Object} [Optional] The context in which event is subscribed</li>
* <li>callback</li> {function} the callback method for the event
* </ul>
* </p>
* <p>
* listensTo {array} this is an array of objects (the events) to which the module subscribes
* <ul>
* <li>eventName {String} The name of the event</li>
* <li>eventPublisher {String} [Optional] CSS selector of the publisher of the event to which the module is subscribing.
* The module listens to event from all the publishers if this field is not provided</li>
* <li>context {Object} [Optional] The context in which event is subscribed</li>
* <li>callback {function} the callback method for the event</li>
* <li>{boolean} [once = false] The callback of the function that can only be called one time if true. Repeated event publish
* will have no effect.</li>
* <li>[type= "PLAY_AFTER_RENDER"] {EVENT_ENUM} the type of the event </li>
* </ul>
* </p>
* @returns {*}
* @private
*/
let _registerSubscription = function (module) {
module.instanceConfig.initOn && module.subscribe({
eventName: module.instanceConfig.initOn.eventName,
eventPublisher: module.instanceConfig.initOn.eventPublisher,
context: module.instanceConfig,
callback: Utils.partial(_callResolveRenderOn, module),
once: true
});
_onBreath(module, CONSTANTS.onStatusChange_EVENTS.initOnSubscribed);
module.instanceConfig.listensTo &&
module.instanceConfig.listensTo.length &&
module.instanceConfig.listensTo.filter((evt)=> {
if (evt.type === CONSTANTS.EVENT_ENUM.keepOn || evt.type === CONSTANTS.EVENT_ENUM.replay) {
return evt;
}
})
.forEach((listener) => {
module.subscribe({
eventName: listener.eventName,
callback: module[listener.callback],
context: module,
eventPublisher: listener.eventPublisher,
once: listener.once,
type: listener.type
});
});
_onBreath(module, CONSTANTS.onStatusChange_EVENTS.keepOnReplaySubscribed);
};
/**
*
* @param config {Object} The configuration of the module to be created. Creates instance of {@link Module} and keeps it
* in {@link Store}.If the module has child modules then the child modules too will be registered.
* <p>It must contain following properties
* <ul style="list-style: none;">
* <li>1. moduleName {String} The unique name in the workspace of the module
* <li>2. module {Object}: It is the reference of module to be consumed
* <li>3. instanceConfig {Object}: the configuration to be passed for that particular module. It must contain following
* properties:
* <ul>
* <li>placeholders {Object}
* <li>container {String} Css selector of the container element. This should be unique.
* <li>listensTo {Array} [Optional] the list of events that the module will listen to.
* </ul>
* </ul>
* </p>
* <p>If the module has already been registered on the same path then registration would be skipped and a warning will
* be generated.</p>
* <p></p>
* @param {String} [moduleName = config.moduleName] The unique name in the workspace of the module
* @param {Object}[instance = config.module]
* @param {Object}[instanceConfig = config.instanceConfig]
* @param {String}[path=""]
* @private
*/
let _registerModule = function (moduleName, config, instance = config.module, instanceConfig = config.instanceConfig, patchModuleArray = [], parent, parentMeta = parent && parent.meta) {
if(typeof parent === "string"){
parent = moduleS.find(function (module) {
return module.name === parent;
});
parentMeta = parent && parent.meta;
}
let parentName = config.name ? config.name.split(".") : undefined,
foundModules;
if(parent && parent.instanceConfig && parent.instanceConfig.modules && parent.instanceConfig.modules.length) {
let configFromParent = parent.instanceConfig.modules.filter(function(parentSibling) {
return parentSibling.moduleName === moduleName;
});
if(configFromParent && configFromParent.length) {
let parentInstance = configFromParent[0].instanceConfig || {};
instanceConfig.placeholders = parentInstance.placeholders || instanceConfig.placeholders;
instanceConfig.listensTo = parentInstance.listensTo || instanceConfig.listensTo;
}
}
if(instanceConfig.placeholders && instance && instance.config && instance.config.placeholders){
instanceConfig.placeholders = merge(instance.config.placeholders, instanceConfig.placeholders);
}
if (this instanceof Module) {
let parentId = this.getUniqueId();
foundModules = moduleS.filter((module) => {
return module.meta.id === parentId;
});
} else if (!parent && parentName && parentName.length === 2) {
foundModules = moduleS.filter((module) => {
return module.name === parentName[0];
});
}
if (foundModules && foundModules.length) {
parent = foundModules[0];
parentMeta = parent.meta;
}
let meta = {
id: Utils.getNextUniqueId(),
parent: {
id: parentMeta && parentMeta.id ? parentMeta.id : undefined,
pointer: parent
},
children: [],
siblings: parentMeta ? [].concat(parentMeta.children) : []
};
let moduleDetail = new Module(config.name, moduleName, CONSTANTS.lifeCycleFlags, instanceConfig, instance, meta);
// Store module
moduleS.insertInstance(moduleDetail);
patchModuleArray.push(moduleDetail);
_registerSubscription(moduleDetail);
_emitLifeCycleEvent(moduleDetail, "_CREATED");
_onBreath(moduleDetail, CONSTANTS.onStatusChange_EVENTS.onCreate);
if (parentMeta) {
meta.siblings = [].concat(parentMeta.children);
parentMeta.children.push({
id: meta.id,
pointer: moduleDetail
});
}
// Has child modules
if (instance.config && instance.config.modules && instance.config.modules.length) {
instance.config.modules.forEach((childModule) => {
_registerModule(childModule.moduleName, childModule, childModule.module, childModule.instanceConfig, patchModuleArray, moduleDetail);
});
} else {
return patchModuleArray;
}
};
/**
*
* Destroys the module . Does the following
* <ul>
* <li>removed DOM element</li>
* <li> Unsubscribes events.It calls {@link Module.unsubscribe}</li>
* <li> Removes the entry of module from module store </li>
* <li> Removes the entry of child modules from module store </li>
* </ul>
* @param module {Array| Object} The module to be destroyed
* @param [context = window] {object} @todo . Reserved for future enhancement
* @returns {boolean} true when module gets deleted successfully
*/
export function destroyModuleInstance(module, context = window) {
/// Remove module DOM and unsubscribe its events
if(Array.isArray(module)) {
let status = [];
module.forEach(function(singleModule) {
status.push(destroyModuleInstance(singleModule));
});
return status;
}
let moduleInstance;
if(typeof module === "string"){
moduleInstance = moduleS.findInstance(module);
} else if(module.meta){
moduleInstance = moduleS.findInstance(module.meta.id);
} else {
moduleInstance = moduleS.findInstance(null, module.name);
}
moduleInstance.forEach((module)=> {
//Call detroy of module
if(module[CONSTANTS.MODULE_EVENTS.destroy]){
module[CONSTANTS.MODULE_EVENTS.destroy]();
}
let container = context.document.querySelector(`#${module.getUniqueId()}`);
// Remove element from DOM
if(container){
container.remove();
container = null;
}
// Remove all subscriptions
let moduleSubscriptions = module.getAllSubscriptions();
moduleSubscriptions.forEach(function (subscription) {
module.unsubscribe(subscription.eventName, subscription.callback);
});
if(module.meta.children && module.meta.children.length){
let childPointers = module.meta.children.map((child)=>{
return child.pointer;
});
module.meta.children = [];
childPointers.forEach((childNode)=>{
destroyModuleInstance(childNode);
});
}
moduleS.deleteInstance(module.meta.id);
});
return true;
}
/**
*
* @param config {Object} The configuration of the module to be created. It must contain following properties
* <ul style="list-style: none;">
* <li>1. moduleName {String} The unique name in the workspace of the module
* <li>2. module {Object}: It is the reference of module to be consumed
* <li>3. instanceConfig: the configuration to be passed for that particular module. It must contain following properties:
* <ul>
* <li>placeholders {Object}
* <li>container {String} Css selector of the container element. This should be unique.
* <li>listensTo {Array} [Optional] the list of events that the module will listen to.
* </ul>
* </ul>
* @returns {Promise|undefined} Resolves when all the modules are rendered.
*/
export function createInstance(config, parentName) {
config = merge({}, config);
if(!Utils.configValidator(config)) return;
let modulesToDestory = moduleS.filter((moduleInstance)=>{
return moduleInstance.instanceConfig.container === config.instanceConfig.container;
});
modulesToDestory.forEach((moduleInstance)=>{
destroyModuleInstance(moduleInstance);
});
let moduleResolvePromiseArr = [],
promise,
patchModules = [];
_registerModule.call(this, config.moduleName, config, config.module, config.instanceConfig, patchModules, parentName);
_startExec.call(this, patchModules, moduleResolvePromiseArr);
return new Promise((res, rej)=> {
Promise.all(moduleResolvePromiseArr).then(res).catch(rej)
});
}
/**
* The middlewares are the additional functionalities that you can create in blinx.
* These are providers which enhances the basic functionalities. You can create your own provider too.
* The provider is generally in format
* export default function (module) {
* return {
* render: function(){
* //your overridden logic
* }
* }
* }
* where module is the instance of the module passed evrytime a new instance is created and any of the default methods provided by the blinx can be overridden
* here example render
* @param middleware
*/
export function use(middleware) {
middleWareFns.push(middleware);
}
/**
* Deprecating destroyModuleInstance for name consistency
* @type {destroyModuleInstance}
*/
export let destroyInstance = destroyModuleInstance;
/**
* used for Development tool for chrome
*/
Devtool.attachListener(function(){
return moduleS;
});
export default {
createInstance,
destroyInstance,
destroyModuleInstance, // Deprecated
use
};
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-interfaces_store.html">interfaces/store</a></li><li><a href="module-Module.html">Module</a></li><li><a href="The%2520publisher%2520subscriber%2520module%2520for%2520Blinx.%250AIt%2520is%2520responsible%2520for%2520the%2520communication%2520between%2520the%2520modules%2520through%2520eventsmodule_.html">The publisher subscriber module for Blinx.
It is responsible for the communication between the modules through events</a></li></ul><h3>Classes</h3><ul><li><a href="module-Module-Module.html">Module</a></li></ul><h3>Global</h3><ul><li><a href="global.html#_callRender">_callRender</a></li><li><a href="global.html#_callResolveRenderOn">_callResolveRenderOn</a></li><li><a href="global.html#_emitLifeCycleEvent">_emitLifeCycleEvent</a></li><li><a href="global.html#_listenForInitOn">_listenForInitOn</a></li><li><a href="global.html#_lockEvents">_lockEvents</a></li><li><a href="global.html#_onBreath">_onBreath</a></li><li><a href="global.html#_registerModule">_registerModule</a></li><li><a href="global.html#_registerSubscription">_registerSubscription</a></li><li><a href="global.html#_startExec">_startExec</a></li><li><a href="global.html#attachListener">attachListener</a></li><li><a href="global.html#checkIfModuleHasInitOn">checkIfModuleHasInitOn</a></li><li><a href="global.html#createInstance">createInstance</a></li><li><a href="global.html#deleteInstance">deleteInstance</a></li><li><a href="global.html#destroyInstance">destroyInstance</a></li><li><a href="global.html#destroyModuleInstance">destroyModuleInstance</a></li><li><a href="global.html#eventQ">eventQ</a></li><li><a href="global.html#findInstance">findInstance</a></li><li><a href="global.html#insertInstance">insertInstance</a></li><li><a href="global.html#isBrowser">isBrowser</a></li><li><a href="global.html#isGlobalPubsub">isGlobalPubsub</a></li><li><a href="global.html#isModuleRendered">isModuleRendered</a></li><li><a href="global.html#isServer">isServer</a></li><li><a href="global.html#lifeCycleFlags">lifeCycleFlags</a></li><li><a href="global.html#PubSubHelper">PubSubHelper</a></li><li><a href="global.html#subscriptions">subscriptions</a></li><li><a href="global.html#use">use</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.0</a> on Mon Feb 06 2017 13:40:46 GMT+0530 (IST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>