Created dynamic to injectComponent from one plugin into another, write documentation accordingly

This commit is contained in:
cyril lopez 2017-10-24 14:35:08 +02:00
parent d77456e78c
commit 160ffe8626
9 changed files with 281 additions and 5 deletions

View File

@ -127,6 +127,198 @@ export default compose(
***
## Inject design
The `ExtendComponent` allows you to inject design from one plugin into another.
### Example
Let's say that you want to enable another plugin to inject a component into the top area of your plugin's container called `FooPage`;
**Path —** `./plugins/my-plugin/admin/src/containers/FooPage/actions.js`.
```js
import {
ON_TOGGLE_SHOW_LOREM,
} from './constants';
export function onToggleShowLorem() {
return {
type: ON_TOGGLE_SHOW_LOREM,
};
}
```
**Path —** `./plugins/my-plugin/admin/src/containers/FooPage/index.js`.
```js
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators, compose } from 'redux';
import { createStructuredSelector } from 'reselect';
import PropTypes from 'prop-types';
// Import the ExtendComponent
import ExtendComponent from 'components/ExtendComponent';
// Utils
import injectReducer from 'utils/injectReducer';
// Actions
import { onToggleShowLorem } from './action'
import reducer from './reducer';
// Selectors
import { makeSelectShowLorem } from './selectors';
class FooPage extends React.Component {
render() {
const lorem = this.props.showLorem ? <p>Lorem ipsum dolor sit amet, consectetur adipiscing</p> : '';
return (
<div>
<h1>This is FooPage container</h1>
<ExtendComponent
area="top"
container="FooPage"
plugin="my-plugin"
{...props}
/>
{lorem}
</div>
);
}
}
FooPage.propTypes = {
onToggleShowLorem: PropTypes.func.isRequired,
showLorem: PropTypes.bool.isRequired,
};
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
onToggleShowLorem,
},
dispatch,
);
}
const mapStateToProps = createStructuredSelector({
showLorem: makeSelectShowLorem(),
});
const withConnect = connect(mapDispatchToProps, mapDispatchToProps);
const withReducer = injectReducer({ key: 'fooPage', reducer });
export default compose(
withReducer,
withConnect,
)(FooPage);
```
**Path —** `./plugins/my-plugin/admin/src/containers/FooPage/reducer.js`.
```js
import { fromJS } from 'immutable';
import { ON_TOGGLE_SHOW_LOREM } from './constants';
const initialState = fromJS({
showLorem: false,
});
function fooPageReducer(state= initialState, action) {
switch (action.type) {
case ON_TOGGLE_SHOW_LOREM:
return state.set('showLorem', !state.get('showLorem'));
default:
return state;
}
}
export default fooPageReducer;
```
**Path —** `./plugins/my-plugin/admin/src/containers/FooPage/selectors.js`.
```js
import { createSelector } from 'reselect';
/**
* Direct selector to the fooPage state domain
*/
const selectFooPageDomain = () => state => state.get('fooPage');
/**
* Other specific selectors
*/
const makeSelectShowLorem = () => createSelector(
selectFooPageDomain(),
(substate) => substate.get('showLorem'),
);
export { makeSelectShowLorem };
```
That's all now your plugin's container is injectable!
Let's see how to inject some design from another plugin.
### Create your injectedComponent
**Path -** `./plugins/another-plugin/admin/src/extendables/BarContainer/index.js`;
```js
import React from 'react';
import PropTypes from 'prop-types';
// Import our Button component
import Button from 'components/Button';
// Other imports such as actions, selectors, sagas, reducer...
class BarContainer extends React.Component {
render() {
return (
<div>
<Button primary onClick={this.props.onToggleShowLorem}>
Click me to show lorem paragraph
</Button>
</div>
);
}
}
BarContainer.propTypes = {
onToggleShowLorem: PropTypes.func,
};
BarContainer.defaultProps = {
onToggleShowLorem: () => {},
};
export default BarContainer;
```
### Tell the admin that you want to inject some design into another plugin
You have to create a file called `injectedComponents.js` at the root of your `another-plugin` src folder.
**Path —** `./plugins/another-plugin/admin/src/injectedComponents.js`.
```js
import BarContainer from 'extendables/BarContainer';
// export an array containing all the injected components
export default [
{
area: 'top',
container: 'FooPage',
injectedComponent: BarContainer,
plugin: 'my-plugin',
},
];
```
Just by doing so, the `another-plugin` will add a `Button` which toggles the `lorem` paragraph in the `FooPage` view.
***
## Routeless container store injection
See the basic container's store injection [documentation](./development.md#using-redux-sagas).

View File

@ -74,6 +74,14 @@ export default Button;
***
## ExtendComponent
ExtendComponent allows a plugin to injectDesign into another one.
> Refer to the advanced plugin [documentation](./advanced.md#inject-design) to see how to use it.
***
## Ico
Ico components that works with fontAwesome.

View File

@ -64,7 +64,7 @@
padding-left: 1.6rem;
padding-right: 1.6rem;
&:before {
content: '\F02d ';
content: '\F067';
font-family: 'FontAwesome';
font-weight: 600;
font-size: 1.3rem;

View File

@ -4,7 +4,7 @@
"app.components.HomePage.welcome": "Welcome on board!",
"app.components.HomePage.description.part1": "We are happy to have you as one of our users!",
"app.components.HomePage.description.part2": "You are now a member of our community which will help you building your dreamed app.",
"app.components.HomePage.button": "Create your first app",
"app.components.HomePage.button": "Create your first content type",
"app.components.HomePage.feedback": "Feel free to ask questions or give us feedback by using one of the support channels below.",
"app.components.LeftMenuFooter.poweredBy": "Proudly powered by",
"app.components.LeftMenuLinkContainer.configuration": "Configuration",

View File

@ -4,7 +4,7 @@
"app.components.HomePage.welcome": "Bienvenue à bord!",
"app.components.HomePage.description.part1": "Nous somme heureux de vous compter parmi nos utilisateurs",
"app.components.HomePage.description.part2": "Vous faites désormais parti de notre communauté qui peut vous aider à développer votre application.",
"app.components.HomePage.button": "Créez votre application",
"app.components.HomePage.button": "Créez votre premier type de contenu",
"app.components.HomePage.feedback": "N'hésitez pas à utiliser un des channels ci-dessous pour poser vos questions ou nous donner vos retours.",
"app.components.LeftMenuFooter.poweredBy": "Propulsé par",
"app.components.LeftMenuLinkContainer.configuration": "Configuration",

View File

@ -25,6 +25,13 @@ const tryRequire = (bootstrap = false) => {
const bootstrap = tryRequire(true);
const pluginRequirements = tryRequire();
let injectedComponents;
try {
injectedComponents = require('injectedComponents').default;
} catch(err) {
injectedComponents = [];
}
// Plugin identifier based on the package.json `name` value
const pluginPkg = require('../../../../package.json');
@ -77,6 +84,7 @@ window.Strapi.registerPlugin({
pluginRequirements,
preventComponentRendering: false,
blockerComponent: null,
injectedComponents,
blockerComponentProps: {},
});

View File

@ -0,0 +1,62 @@
/*
*
* ExtendComponent
*
*
*/
import React from 'react';
import { get, isEmpty } from 'lodash';
import PropTypes from 'prop-types';
class ExtendComponent extends React.Component {
getInjectedComponent = () => {
const injectedComponent = this.context.plugins.reduce((acc, plugin) => {
if (!isEmpty(plugin.toJS().injectedComponents)) {
const injectedComponents = plugin.toJS().injectedComponents.filter((compo) => {
if (compo.plugin === this.props.plugin && compo.container === this.props.container && compo.area === this.props.area) {
return compo;
}
});
return injectedComponents[0];
}
return acc;
}, {});
return injectedComponent;
}
render() {
const Component = get(this.getInjectedComponent(), 'injectedComponent');
const renderedComponent = Component ? <Component {...this.props} /> : '';
return (
<div>
{renderedComponent}
</div>
);
}
}
ExtendComponent.contextTypes = {
plugins: PropTypes.object,
router: PropTypes.object,
updatePlugin: PropTypes.func,
}
ExtendComponent.propTypes = {
area: PropTypes.string.isRequired,
container: PropTypes.string.isRequired,
children: PropTypes.node,
plugin: PropTypes.string.isRequired,
};
ExtendComponent.defaultProps = {
children: <div />,
};
export default ExtendComponent;

View File

@ -177,7 +177,7 @@ export class Edit extends React.Component {
const mainField = get(this.props.models, `${this.props.currentModelName}.info.mainField`) || primaryKey;
const pluginHeaderTitle = this.props.isCreating ? 'New entry' : templateObject({ mainField }, this.props.record.toJS()).mainField;
const pluginHeaderDescription = this.props.isCreating ? 'New entry' : `#${this.props.record && this.props.record.get(primaryKey)}`;
return (
<div>
<div className={`container-fluid ${styles.containerFluid}`}>
@ -229,6 +229,12 @@ export class Edit extends React.Component {
);
}
}
Edit.contextTypes = {
plugins: PropTypes.object,
updatePlugin: PropTypes.func,
};
/* eslint-disable react/require-default-props */
Edit.propTypes = {
cancelChanges: PropTypes.func.isRequired,

View File

@ -45,4 +45,4 @@
"npm": ">= 5.3.0"
},
"license": "MIT"
}
}