@mason-api/javascript-sdk
Version:
Mason component rendering library
127 lines (117 loc) • 4.84 kB
JavaScript
import _ from 'lodash';
import update from 'immutability-helper';
import { COMPONENT, DATASOURCE, HTTP, OBJECT, TREE } from '@mason-api/utils';
const applyData = (config, configSubpath, datasources) => update(config, {
data: {
[configSubpath]: {
tree: {
$set: TREE.interpolate(
_.get(config, `data.${configSubpath}.tree`),
datasources,
collection => collection || [],
value => (_.isNil(value) ? '' : value),
),
},
},
},
});
const fetchDatasource = datasource => HTTP.makeCall({
data: OBJECT.keyValueArrayToObject(datasource.queries),
headers: OBJECT.keyValueArrayToObject(datasource.headers),
url: datasource.url,
verb: HTTP.GET,
});
const store = {
datasources: {},
data: {},
};
let processDatasourceEvents = _.noop;
let initDatasourceEventListener = (getContext) => {
processDatasourceEvents = (e) => {
const form = e.target;
const {
callback, component, components, instance, render,
} = getContext(form);
const configSubpath = instance.target.getAttribute('data-config-subpath') || 'default';
const { data: { [configSubpath]: { tree } } } = instance.config;
const node = TREE.findNodeAtPath(tree, form.getAttribute('data-path'));
const datasourceEvents = _.reduce(_.get(node.p, '_events.success'), (result, event) => {
if (event.type === 'datasource') {
return _.concat(result, {
action: event.action,
data: e.detail.data,
datasourceId: event.id,
path: event.path,
});
}
return result;
}, []);
if (!_.isEmpty(datasourceEvents)) {
const updatedDatasources = {};
const didFetchData = callback('didFetchData', component.id, instance.target);
_.forEach(datasourceEvents, (op) => {
updatedDatasources[op.datasourceId] = true;
const datasource = _.get(store.datasources, `${component.projectId}.${op.datasourceId}`);
const data = didFetchData(op.data, datasource, component.id);
receiveDatasource(op.datasourceId, component.projectId, data, op.path, op.action);
_.forEach(document.querySelector('mason-canvas'), (canvas) => {
const id = canvas.getAttribute('data-id');
const c = _.get(components, id);
if (c && COMPONENT.usesDatasource(c, op.datasourceId)) {
canvas.setAttribute('data-render', !!component.getAttribute('data-render')); // trigger a re-render using the observer
}
});
});
}
};
initDatasourceEventListener = _.noop;
};
const receiveDatasource = (datasourceId, projectId, data, path = '', operation = 'set') => {
const datasource = _.get(store.datasources, `${projectId}.${datasourceId}`);
let nextDatasource;
switch (operation) {
case 'replace':
case 'set':
nextDatasource = DATASOURCE.setData(datasource, data, path);
break;
case 'merge':
nextDatasource = DATASOURCE.mergeData(datasource, data, path);
break;
case 'remove':
nextDatasource = DATASOURCE.removeData(datasource, data, path);
break;
default:
nextDatasource = DATASOURCE.setData(datasource, data, path);
}
store.datasources[projectId][datasourceId] = nextDatasource;
};
export default {
has: _.stubTrue,
init: getContext => next => (config) => {
const { components, projects } = getContext();
const component = _.get(components, config.componentId);
const project = _.get(projects, component.projectId);
if (!_.has(store.datasources, project.id)) {
store.datasources[project.id] = { ...project.datasources };
}
initDatasourceEventListener(getContext);
return next(config);
},
render: getContext => next => (config, configSubpath, target, props) => {
const { callback, components } = getContext();
const component = _.get(components, config.componentId);
target.addEventListener('didReceiveData', processDatasourceEvents);
if (!_.isEmpty(config.data.default.datasources)) {
const datasourcesToFetch = _.pick(store.datasources[component.projectId], _.map(config.data.default.datasources, 'id'));
const before = callback('willFetchData', config.componentId, target);
const after = callback('didFetchData', config.componentId, target);
Promise.all(_.map(datasourcesToFetch, datasource =>
fetchDatasource(before(datasource, config.componentId))
.then(d =>
receiveDatasource(datasource.id, component.projectId, after(d, datasource, config.componentId)))))
.finally(() => next(applyData(config, configSubpath, store.datasources[component.projectId]), configSubpath, target, props));
} else {
next(applyData(config, configSubpath, store.datasources[component.projectId]), configSubpath, target, props);
}
},
};