2016-11-29 18:53:31 +09:00
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
2018-05-08 20:09:42 -04:00
import React from 'react' ;
2016-11-29 11:35:20 +09:00
2018-05-02 11:00:58 +09:00
export const Components = { } ; // will be populated on startup (see vulcan:routing)
2017-01-18 15:11:31 +01:00
export const ComponentsTable = { } // storage for infos about components
2016-11-29 11:35:20 +09:00
/ * *
2017-06-13 00:16:52 -07:00
* Register a Vulcan component with a name , a raw component than can be extended
2016-11-29 11:35:20 +09:00
* 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
2017-01-18 12:51:10 +01:00
*
* @ returns Structure of a component in the list :
*
2017-01-18 15:11:31 +01:00
* ComponentsTable . Foo = {
2017-01-18 12:51:10 +01:00
* name : 'Foo' ,
* hocs : [ fn1 , fn2 ] ,
* rawComponent : React . Component ,
* call : ( ) => compose ( ... hocs ) ( rawComponent ) ,
* }
*
2016-11-29 11:35:20 +09:00
* /
2018-08-03 11:44:00 +09:00
export function registerComponent ( name , rawComponent , ... hocs ) {
2018-09-12 10:07:08 +09:00
// support single-argument syntax
2018-06-15 14:30:45 +09:00
if ( typeof arguments [ 0 ] === 'object' ) {
2018-09-12 10:07:08 +09:00
// note: cannot use `const` because name, components, hocs are already defined
// as arguments so destructuring cannot work
2018-09-12 11:59:00 +09:00
// eslint-disable-next-line no-redeclare
2018-09-12 10:07:08 +09:00
var { name , component , hocs = [ ] } = arguments [ 0 ] ;
rawComponent = component
}
// store the component in the table
ComponentsTable [ name ] = {
name ,
rawComponent ,
hocs ,
2018-06-15 14:30:45 +09:00
}
2018-08-05 11:17:46 +09:00
}
2016-11-29 11:35:20 +09:00
/ * *
2016-12-12 16:48:49 +09:00
* Get a component registered with registerComponent ( name , component , ... hocs ) .
2016-11-29 11:35:20 +09:00
*
* @ param { String } name The name of the component to get .
* @ returns { Function | React Component } A ( wrapped ) React component
* /
2017-01-18 12:51:10 +01:00
export const getComponent = ( name ) => {
2017-01-30 19:46:48 +09:00
const component = ComponentsTable [ name ] ;
2017-02-19 16:10:21 +01:00
if ( ! component ) {
throw new Error ( ` Component ${ name } not registered. ` )
}
2018-08-31 07:06:50 +09:00
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 ;
}
2016-11-29 11:35:20 +09:00
} ;
2017-01-18 12:51:10 +01:00
/ * *
* Populate the lookup table for components to be callable
2017-01-18 15:11:31 +01:00
* ℹ ️ Called once on app startup
2017-01-18 12:51:10 +01:00
* * /
2017-01-18 15:11:31 +01:00
export const populateComponentsApp = ( ) => {
2017-01-18 12:51:10 +01:00
// loop over each component in the list
2017-01-18 15:11:31 +01:00
Object . keys ( ComponentsTable ) . map ( name => {
2017-01-18 12:51:10 +01:00
// populate an entry in the lookup table
Components [ name ] = getComponent ( name ) ;
// uncomment for debug
// console.log('init component:', name);
} ) ;
}
2016-11-29 11:35:20 +09:00
/ * *
2016-12-12 16:48:49 +09:00
* Get the * * raw * * ( original ) component registered with registerComponent
2016-11-29 11:35:20 +09:00
* 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
* /
2016-12-06 18:06:29 +01:00
export const getRawComponent = ( name ) => {
2017-01-18 15:11:31 +01:00
return ComponentsTable [ name ] . rawComponent ;
2016-11-29 11:35:20 +09:00
} ;
/ * *
2017-06-13 00:16:52 -07:00
* Replace a Vulcan component with the same name with a new component or
2016-11-29 11:35:20 +09:00
* 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 .
2018-08-10 13:03:15 +02:00
* @ param { React Component } newComponent Interchangeable / extendable component .
* @ param { ... Function } newHocs The HOCs to compose with the raw component .
2017-01-18 15:11:31 +01:00
* @ returns { Function | React Component } A component callable with Components [ name ]
2016-11-29 11:35:20 +09:00
*
* 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
* /
2018-09-12 11:59:58 +09:00
export function replaceComponent ( name , newComponent , ... newHocs ) {
2018-09-12 10:07:08 +09:00
// support single argument syntax
if ( typeof arguments [ 0 ] === 'object' ) {
2018-09-12 11:59:58 +09:00
// eslint-disable-next-line no-redeclare
var { name , component , hocs = [ ] } = arguments [ 0 ] ;
2018-09-12 10:07:08 +09:00
newComponent = component ;
2018-09-12 11:59:58 +09:00
newHocs = hocs ;
2018-09-12 10:07:08 +09:00
}
2017-01-18 15:11:31 +01:00
const previousComponent = ComponentsTable [ name ] ;
2018-09-12 10:07:08 +09:00
const previousHocs = previousComponent && previousComponent . hocs || [ ] ;
2018-08-10 13:03:15 +02:00
if ( ! previousComponent ) {
2018-09-12 11:59:00 +09:00
// eslint-disable-next-line no-console
2018-08-10 13:03:15 +02:00
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.' ,
) ;
}
2016-11-29 11:35:20 +09:00
2018-09-12 10:07:08 +09:00
return registerComponent ( name , newComponent , ... newHocs , ... previousHocs ) ;
2018-09-16 11:48:38 +09:00
}
2016-12-06 18:06:29 +01:00
2016-12-10 21:58:40 +09:00
export const copyHoCs = ( sourceComponent , targetComponent ) => {
return compose ( ... sourceComponent . hocs ) ( targetComponent ) ;
}
2018-05-08 20:09:42 -04:00
/ * *
* 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 ;
}
} ;
2018-06-30 11:21:45 +02:00
/ * *
* 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 } / > ;
} ;
} ;