fractal-core
Version:
A minimalist and well crafted app, content or component is our conviction
439 lines • 19 kB
JavaScript
;
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