mirror of
https://github.com/vale981/Vulcan
synced 2025-03-07 02:21:43 -05:00
205 lines
7.2 KiB
JavaScript
205 lines
7.2 KiB
JavaScript
import { compose } from 'react-apollo'; // note: at the moment, compose@react-apollo === compose@redux ; see https://github.com/apollostack/react-apollo/blob/master/src/index.ts#L4-L7
|
||
import React from 'react';
|
||
|
||
export const Components = {}; // will be populated on startup (see vulcan:routing)
|
||
export const ComponentsTable = {}; // storage for infos about components
|
||
|
||
/**
|
||
* Register a Vulcan component with a name, a raw component than can be extended
|
||
* and one or more optional higher order components.
|
||
*
|
||
* @param {String} name The name of the component to register.
|
||
* @param {React Component} rawComponent Interchangeable/extendable component.
|
||
* @param {...Function} hocs The HOCs to compose with the raw component.
|
||
*
|
||
* Note: when a component is registered without higher order component, `hocs` will be
|
||
* an empty array, and it's ok!
|
||
* See https://github.com/reactjs/redux/blob/master/src/compose.js#L13-L15
|
||
*
|
||
* @returns Structure of a component in the list:
|
||
*
|
||
* ComponentsTable.Foo = {
|
||
* name: 'Foo',
|
||
* hocs: [fn1, fn2],
|
||
* rawComponent: React.Component,
|
||
* call: () => compose(...hocs)(rawComponent),
|
||
* }
|
||
*
|
||
*/
|
||
export function registerComponent(name, rawComponent, ...hocs) {
|
||
// support single-argument syntax
|
||
if (typeof arguments[0] === 'object') {
|
||
// note: cannot use `const` because name, components, hocs are already defined
|
||
// as arguments so destructuring cannot work
|
||
// eslint-disable-next-line no-redeclare
|
||
var { name, component, hocs = [] } = arguments[0];
|
||
rawComponent = component;
|
||
}
|
||
// store the component in the table
|
||
ComponentsTable[name] = {
|
||
name,
|
||
rawComponent,
|
||
hocs,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Get a component registered with registerComponent(name, component, ...hocs).
|
||
*
|
||
* @param {String} name The name of the component to get.
|
||
* @returns {Function|React Component} A (wrapped) React component
|
||
*/
|
||
export const getComponent = (name) => {
|
||
const component = ComponentsTable[name];
|
||
if (!component) {
|
||
throw new Error(`Component ${name} not registered.`);
|
||
}
|
||
if (component.hocs) {
|
||
const hocs = component.hocs.map(hoc => {
|
||
if(!Array.isArray(hoc)) return hoc;
|
||
const [actualHoc, ...args] = hoc;
|
||
return actualHoc(...args);
|
||
});
|
||
return compose(...hocs)(component.rawComponent);
|
||
} else {
|
||
return component.rawComponent;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Populate the lookup table for components to be callable
|
||
* ℹ️ Called once on app startup
|
||
**/
|
||
export const populateComponentsApp = () => {
|
||
// loop over each component in the list
|
||
Object.keys(ComponentsTable).map(name => {
|
||
|
||
// populate an entry in the lookup table
|
||
Components[name] = getComponent(name);
|
||
|
||
// uncomment for debug
|
||
// console.log('init component:', name);
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Get the **raw** (original) component registered with registerComponent
|
||
* without the possible HOCs wrapping it.
|
||
*
|
||
* @param {String} name The name of the component to get.
|
||
* @returns {Function|React Component} An interchangeable/extendable React component
|
||
*/
|
||
export const getRawComponent = (name) => {
|
||
return ComponentsTable[name].rawComponent;
|
||
};
|
||
|
||
/**
|
||
* Replace a Vulcan component with the same name with a new component or
|
||
* an extension of the raw component and one or more optional higher order components.
|
||
* This function keeps track of the previous HOCs and wrap the new HOCs around previous ones
|
||
*
|
||
* @param {String} name The name of the component to register.
|
||
* @param {React Component} newComponent Interchangeable/extendable component.
|
||
* @param {...Function} newHocs The HOCs to compose with the raw component.
|
||
* @returns {Function|React Component} A component callable with Components[name]
|
||
*
|
||
* Note: when a component is registered without higher order component, `hocs` will be
|
||
* an empty array, and it's ok!
|
||
* See https://github.com/reactjs/redux/blob/master/src/compose.js#L13-L15
|
||
*/
|
||
export function replaceComponent(name, newComponent, ...newHocs) {
|
||
|
||
// support single argument syntax
|
||
if (typeof arguments[0] === 'object') {
|
||
// eslint-disable-next-line no-redeclare
|
||
var { name, component, hocs = [] } = arguments[0];
|
||
newComponent = component;
|
||
newHocs = hocs;
|
||
}
|
||
|
||
const previousComponent = ComponentsTable[name];
|
||
const previousHocs = previousComponent && previousComponent.hocs || [];
|
||
|
||
if (!previousComponent) {
|
||
// eslint-disable-next-line no-console
|
||
console.warn(
|
||
`Trying to replace non-registered component ${name}. The component is ` +
|
||
'being registered. If you were trying to replace a component defined by ' +
|
||
'another package, make sure that you haven\'t misspelled the name. Check ' +
|
||
'also if the original component is still being registered or that it ' +
|
||
'hasn\'t been renamed.',
|
||
);
|
||
}
|
||
|
||
return registerComponent(name, newComponent, ...newHocs, ...previousHocs);
|
||
}
|
||
|
||
export const copyHoCs = (sourceComponent, targetComponent) => {
|
||
return compose(...sourceComponent.hocs)(targetComponent);
|
||
};
|
||
|
||
/**
|
||
* Returns an instance of the given component name of function
|
||
* @param {string|function} component A component or registered component name
|
||
* @param {Object} [props] Optional properties to pass to the component
|
||
*/
|
||
//eslint-disable-next-line react/display-name
|
||
export const instantiateComponent = (component, props) => {
|
||
if (!component) {
|
||
return null;
|
||
} else if (typeof component === 'string') {
|
||
const Component = getComponent(component);
|
||
return <Component {...props}/>;
|
||
} else if (typeof component === 'function' && component.prototype && component.prototype.isReactComponent) {
|
||
const Component = component;
|
||
return <Component {...props}/>;
|
||
} else if (typeof component === 'function') {
|
||
return component(props);
|
||
} else {
|
||
return component;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* Creates a component that will render the registered component with the given name.
|
||
*
|
||
* This function may be useful when in need for some registered component, but in contexts
|
||
* where they have not yet been initialized, for example at compile time execution. In other
|
||
* words, when using `Components.ComponentName` is not allowed (because it has not yet been
|
||
* populated, hence would be `undefined`), then `delayedComponent('ComponentName')` can be
|
||
* used instead.
|
||
*
|
||
* @example Create a container for a registered component
|
||
* // SomeContainer.js
|
||
* import compose from 'recompose/compose';
|
||
* import { delayedComponent } from 'meteor/vulcan:core';
|
||
*
|
||
* export default compose(
|
||
* // ...some hocs with container logic
|
||
* )(delayedComponent('ComponentName')); // cannot use Components.ComponentName in this context!
|
||
*
|
||
* @example {@link dynamicLoader}
|
||
* @param {String} name Component name
|
||
* @return {Function}
|
||
* Functional component that will render the given registered component
|
||
*/
|
||
export const delayedComponent = name => {
|
||
return props => {
|
||
const Component = Components[name] || null;
|
||
return Component && <Component {...props} />;
|
||
};
|
||
};
|
||
|
||
|
||
// Example with Proxy (might be unstable/hard to reason about)
|
||
//const mergeWithComponents = (myComponents = {}) => {
|
||
// const handler = {
|
||
// get: function(target, name) {
|
||
// return name in target ? target[name] : Components[name];
|
||
// }
|
||
// };
|
||
// const proxy = new Proxy(myComponents, handler);
|
||
// return proxy;
|
||
//};
|
||
export const mergeWithComponents = myComponents => (myComponents ? { ...Components, ...myComponents } : Components);
|
||
|