UNPKG

fractal-core

Version:

A minimalist and well crafted app, content or component is our conviction

439 lines 19 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); require('setimmediate'); // Polyfill setImmediate const _1 = require("."); const pullable_event_bus_1 = require("pullable-event-bus"); // a gap is defined with undefined (optional) exports._ = undefined; exports.handlerTypes = ['interface', 'task', 'group']; function _nest(ctx, name, component) { return __awaiter(this, void 0, void 0, function* () { if (!component) { ctx.error('_nest', `Error when trying to create a component named ${name} in component ${ctx.id}`); } // namespaced name if is a child let id = ctx.id === 'Root' && name === 'Root' ? 'Root' : ctx.id + '$' + name; // state default component.state = component.state || {}; component.state._nest = component.state._nest || {}; component.state._compCounter = 0; // Component state const state = _1.clone(component.state); // Component context let childCtx = { id, name, groups: {}, // delegation eventBus: ctx.eventBus, global: ctx.global, components: ctx.components, groupHandlers: ctx.groupHandlers, interfaceHandlers: ctx.interfaceHandlers, taskHandlers: ctx.taskHandlers, beforeInput: ctx.beforeInput, afterInput: ctx.afterInput, warn: ctx.warn, error: ctx.error, // Component related context state, inputs: {}, actions: component.actions, interfaces: component.interfaces, interfaceHelpers: {}, interfaceValues: {}, }; // Create interface helpers childCtx.interfaceHelpers = _1.makeInterfaceHelpers(childCtx); ctx.components[id] = childCtx; if (component.inputs) { childCtx.inputs = component.inputs(childCtx.state, _1.makeInputHelpers(childCtx)); } else { childCtx.inputs = {}; } if (component.actions) { // reserved inputs: _action and _return if (!childCtx.inputs._action) { // action helper enabled by default childCtx.inputs._action = _1.action(childCtx, component.actions); } if (!childCtx.actions.Set) { childCtx.actions.Set = _1.SetAction; } if (!childCtx.actions._remove) { childCtx.actions._remove = _1._removeAction; } } // composition if (Object.keys(childCtx.state._nest).length > 0) { let components = childCtx.state._nest; for (name in components) { yield _nest(childCtx, name, components[name]); } } childCtx.state._compNames = Object.keys(childCtx.state._nest); if (component.groups) { // Groups are handled automatically only when comoponent are initialized yield handleGroups(childCtx, component); } if (childCtx.inputs.onInit && !childCtx.global.hotSwap) { // component lifecycle hook: onInit yield childCtx.inputs.onInit(); } return childCtx; }); } function handleGroups(ctx, component) { return __awaiter(this, void 0, void 0, function* () { let space; let name; for (name in component.groups) { space = ctx.groupHandlers[name]; if (space) { yield space.handle(ctx.id, component.groups[name]); } else { ctx.error('nest', `module has no group handler for '${name}' of component '${ctx.name}' from space '${ctx.id}'`); } } }); } // add many components to the component index exports.nestAll = (ctx) => (components, isStatic = false) => __awaiter(this, void 0, void 0, function* () { let name; for (name in components) { yield _nest(ctx, name, components[name]); } }); // remove a component to the component index, if name is not defined destroy the root exports.unnest = (ctx) => (name) => __awaiter(this, void 0, void 0, function* () { let id = name !== undefined ? ctx.id + '$' + name : ctx.id; let componentSpace = ctx.components[id]; if (!componentSpace) { return ctx.error('unnest', `there is no component with name '${name}' at component '${ctx.id}'`); } // decomposition let components = componentSpace.components; if (components) { yield exports.unnestAll(componentSpace)(Object.keys(componentSpace.state._nest)); } // component lifecycle hook: onDestroy if (ctx.inputs.onDestroy && !ctx.global.hotSwap) { yield ctx.inputs.onDestroy(); } delete ctx.components[id]; }); // add many components to the component index exports.unnestAll = (ctx) => (components) => __awaiter(this, void 0, void 0, function* () { let _unnest = exports.unnest(ctx); for (let i = 0, len = components.length; i < len; i++) { yield _unnest(components[i]); } }); function propagate(ctx, inputName, data) { return __awaiter(this, void 0, void 0, function* () { // notifies parent if name starts with $ let id = ctx.id; let idParts = (id + '').split('$'); let componentSpace = ctx.components[id]; if (idParts.length > 1) { // is not root? let parentId = idParts.slice(0, -1).join('$'); let parentSpace = ctx.components[parentId]; let parentInputName; let nameParts = componentSpace.name.split('_'); /** Component Input Listeners * - Individual: $CompName_inputName or $GroupName_compName_inputName -> data * - Groupal: $GroupName_inputName -> [name, data] * - Global: $_inputName -> [name, data] */ // Individual parentInputName = `$${componentSpace.name}_${inputName}`; if (parentSpace.inputs[parentInputName]) { yield exports.toIn(parentSpace)(parentInputName, data); } // Groupal if (nameParts.length === 2) { parentInputName = `$${nameParts[0]}_${inputName}`; if (parentSpace.inputs[parentInputName]) { yield exports.toIn(parentSpace)(parentInputName, [nameParts[1], data]); } } // Global parentInputName = `$_${inputName}`; if (parentSpace.inputs[parentInputName]) { yield exports.toIn(parentSpace)(parentInputName, [componentSpace.name, data]); } } }); } exports.propagate = propagate; // send a message to an input of a component from itself exports.toIn = (ctx) => { let id = ctx.id; let componentSpace = ctx.components[id]; return (inputName, data) => __awaiter(this, void 0, void 0, function* () { if (!ctx.global.active) { return; } let input = componentSpace.inputs[inputName]; if (input === undefined) { ctx.error('execute', `there are no input named '${inputName}' in component '${componentSpace.name}' from space '${id}'`); return; } if (ctx.beforeInput) ctx.beforeInput(ctx, inputName, data); let result = yield input(data); if (ctx.afterInput) ctx.afterInput(ctx, inputName, data); yield propagate(ctx, inputName, data); return result; }); }; function performUpdate(compCtx, update) { return __awaiter(this, void 0, void 0, function* () { const state = compCtx.state; const stateUpdates = yield update(state); if (stateUpdates) { let key; for (key in stateUpdates) { state[key] = stateUpdates[key]; } } if (state._compUpdated) { compCtx.global.render = false; let compNames = state._compNames; let newCompNames = Object.keys(state._nest); let newNames = newCompNames.filter(n => compNames.indexOf(n) < 0); let removeNames = compNames.filter(n => newCompNames.indexOf(n) < 0); for (let i = 0, len = newNames.length; i < len; i++) { yield _nest(compCtx, newNames[i], state._nest[newNames[i]]); } for (let i = 0, len = removeNames.length; i < len; i++) { yield exports.unnest(compCtx)(removeNames[i]); } state._compUpdated = false; state._compNames = newCompNames; compCtx.global.render = true; } if (compCtx.global.moduleRender && compCtx.global.render) { calcAndNotifyInterfaces(compCtx); // root context } else { compCtx.interfaceValues = {}; } }); } exports.performUpdate = performUpdate; function performTask(ctx) { return (name, data) => { if (!ctx.taskHandlers[name]) { ctx.error('execute', `there are no task handler for '${name}' in component '${ctx.name}' from space '${ctx.id}'`); return; } return ctx.taskHandlers[name].handle(ctx.id, data); }; } exports.performTask = performTask; function calcAndNotifyInterfaces(ctx) { // calc and caches interfaces let space = ctx.components[ctx.id]; let idParts = (ctx.id + '').split('$'); for (let name in space.interfaces) { setImmediate(() => __awaiter(this, void 0, void 0, function* () { // remove cache of parent component spaces let parts = idParts.slice(0); for (let i = parts.length - 1; i >= 0; i--) { ctx.components[parts.join('$')].interfaceValues[name] = undefined; parts.pop(); } // permorms interface recalculation let rootSpace = ctx.components.Root; for (let name in rootSpace.interfaces) { if (ctx.interfaceHandlers[name]) { ctx.interfaceHandlers[name].handle('Root', yield rootSpace.interfaces[name](rootSpace.state, rootSpace.interfaceHelpers)); } else { // This only can happen when this method is called for a context that is not the root ctx.error('notifyInterfaceHandlers', `module does not have interface handler named '${name}' for component '${space.name}' from space '${ctx.id}'`); } } })); } } exports.calcAndNotifyInterfaces = calcAndNotifyInterfaces; // function for running a root component function run(moduleDef) { return __awaiter(this, void 0, void 0, function* () { // root component let component; let moduleAPI; // root context let ctx; // Prevents cross references moduleDef = _1.clone(moduleDef); // Add event bus as default `ev` task handler moduleDef.tasks = moduleDef.tasks ? moduleDef.tasks : {}; // attach root component function attach(comp, app, middleFn) { return __awaiter(this, void 0, void 0, function* () { // root component, take account of hot swapping component = comp ? comp : moduleDef.Root; // if is hot swapping, do not recalculate context // bootstrap context (level 0 in hierarchy) if (!middleFn) { ctx = { id: 'Root', name: 'Root', groups: {}, global: { record: moduleDef.hasOwnProperty('record') ? moduleDef.record : false, records: [], log: moduleDef.hasOwnProperty('log') ? moduleDef.log : false, moduleRender: moduleDef.hasOwnProperty('render') ? moduleDef.render : true, render: true, active: moduleDef.hasOwnProperty('active') ? moduleDef.active : true, }, eventBus: pullable_event_bus_1.makeEventBus(), // component index components: {}, groupHandlers: {}, taskHandlers: {}, interfaces: {}, interfaceHandlers: {}, inputs: {}, // error and warning handling beforeInput: moduleDef.beforeInput ? moduleDef.beforeInput : exports._, afterInput: moduleDef.afterInput || exports._, warn: moduleDef.warn || exports._, error: moduleDef.error || exports._, }; // API for modules moduleAPI = { on: ctx.eventBus.on, off: ctx.eventBus.off, emit: ctx.eventBus.emit, // dispatch function type used for handlers dispatchEv: _1.dispatchEv(ctx), dispatch: _1.dispatch(ctx), toComp: _1.toComp(ctx), destroy, // set a space of a certain component setGroup: (id, name, space) => { ctx.components[id].groups[name] = space; }, attach, task: performTask(ctx), // delegated methods warn: ctx.warn, error: ctx.error, }; // module lifecycle hook: onBeforeInit if (moduleDef.onBeforeInit && !middleFn) { yield moduleDef.onBeforeInit(moduleAPI); } } // if is not hot swapping if (!middleFn) { // pass ModuleAPI to every Interface, Task and Space HandlerFunction let handlers; for (let c = 0, len = exports.handlerTypes.length; c < len; c++) { handlers = moduleDef[exports.handlerTypes[c] + 's']; if (handlers) { let name; for (name in handlers) { ctx[exports.handlerTypes[c] + 'Handlers'][name] = yield (yield handlers[name])(moduleAPI); } } } } if (middleFn) { ctx.global.hotSwap = true; } let lastModuleRender = ctx.global.moduleRender; ctx.global.moduleRender = false; // Root component let root = yield _nest(ctx, 'Root', component); ctx.global.moduleRender = lastModuleRender; // Root context (level 1) ctx.global.rootCtx = root; // middle function for hot-swapping if (middleFn) { yield middleFn(ctx.global.rootCtx, app); } // pass initial value to each Interface Handler // -- interfaceOrder let interfaceOrder = moduleDef.interfaceOrder; let name; let errorNotHandler = name => ctx.error('InterfaceHandlers', `'$.Root' component has no interface called '${name}', missing interface handler`); let rootCtx = ctx.global.rootCtx; if (interfaceOrder) { for (let i = 0; name = interfaceOrder[i]; i++) { if (ctx.interfaceHandlers[name]) { ctx.interfaceHandlers[name].handle('Root', yield rootCtx.interfaces[name](ctx.components.Root.state, ctx.components.Root.interfaceHelpers)); } else { return errorNotHandler(name); } } } for (name in rootCtx.interfaces) { if (interfaceOrder && interfaceOrder.indexOf(name) !== -1) { continue; // interface evaluated yet } if (ctx.interfaceHandlers[name]) { ctx.interfaceHandlers[name].handle('Root', yield rootCtx.interfaces[name](ctx.components.Root.state, ctx.components.Root.interfaceHelpers)); } else { return errorNotHandler(name); } } // module lifecycle hook: onInit if (moduleDef.onInit && !middleFn) { yield moduleDef.onInit(moduleAPI); } return { moduleDef, // reattach root component, used for hot swapping isDisposed: false, // root context moduleAPI, rootCtx: ctx.global.rootCtx, }; }); } function destroy() { return __awaiter(this, void 0, void 0, function* () { if (moduleDef.onBeforeDestroy) { yield moduleDef.onBeforeDestroy(moduleAPI); } // destroy all handlers let handlers; for (let c = 0, len = exports.handlerTypes.length; c < len; c++) { handlers = ctx[`${exports.handlerTypes[c]}Handlers`]; let name; for (name in handlers) { yield handlers[name].destroy(); } } yield exports.unnest(ctx.global.rootCtx)(); ctx = undefined; this.isDisposed = true; if (moduleDef.onDestroy) { yield moduleDef.onDestroy(moduleAPI); } }); } return yield attach(undefined); }); } exports.run = run; //# sourceMappingURL=module.js.map