2019-05-06 16:17:16 +02:00
# Front-end Development
2019-05-16 18:12:56 +02:00
::: danger
2019-05-24 14:42:53 +02:00
This feature is currently not available (coming soon).
2019-05-06 16:17:16 +02:00
:::
2019-10-23 10:11:53 +02:00
<!--
2019-05-16 18:12:56 +02:00
2019-05-06 17:36:25 +02:00
## Admin panel
2019-05-06 16:17:16 +02:00
Strapi's admin panel and plugins system aim to be an easy and powerful way to create new features.
The admin panel is a [React ](https://facebook.github.io/react/ ) application which can embed other React applications. These other React applications are the `admin` parts of each Strapi's plugins.
2019-05-06 17:36:25 +02:00
### Admin Lifecycle
2019-05-06 16:17:16 +02:00
2019-05-06 17:36:25 +02:00
The admin package has the following lifecycle.
2019-05-06 16:17:16 +02:00
2019-05-06 17:36:25 +02:00
1. Retrieve all the installed plugin and and store them into the main redux store
2. Load until all the plugin emit the event `isReady`
3. Runtime
2019-05-06 16:17:16 +02:00
2019-05-06 17:36:25 +02:00
### Strapi global variable
2019-05-06 16:17:16 +02:00
2019-05-06 17:36:25 +02:00
The administration exposes a global variable thqt is accessible for all the plugins.
2019-05-06 16:17:16 +02:00
2019-05-06 17:36:25 +02:00
#### `strapi.backendURL`
Retrieve the back-end URL. (e.g. `http://localhost:1337` ).
#### `strapi.currentLanguage`
Retrieve the administration panel default language (e.g. `en-US` )
#### `strapi.languages`
Array of the administration panel's supported languages. (e.g. `['ar', 'en', 'fr', ...]` ).
#### `strapi.lockApp()`
Display a loader that will prevent the user from interacting with the application.
#### `strapi.unlockApp()`
Remove the loader so the user can interact with the application
#### `strapi.injectSaga`
Dynamically inject a plugin's saga.
2019-05-06 16:17:16 +02:00
**Path —** `plugins/my-plugin/admin/src/containers/App/index.js` .
2019-05-06 17:36:25 +02:00
2019-05-06 16:17:16 +02:00
```js
import React from 'react';
2019-05-06 17:36:25 +02:00
import { compose } from 'redux';
import pluginId from '../../pluginId';
import saga from './saga';
2019-05-06 16:17:16 +02:00
class App extends React.Component {
render() {
2019-05-06 17:36:25 +02:00
return null;
2019-05-06 16:17:16 +02:00
}
}
2019-05-06 17:36:25 +02:00
const withSaga = strapi.injectSaga({ key: 'app', saga, pluginId });
export default compose(withSaga)(App);
2019-05-06 16:17:16 +02:00
```
2019-05-06 17:36:25 +02:00
#### `strapi.injectReducer`
2019-05-06 16:17:16 +02:00
2019-05-06 17:36:25 +02:00
Dynamically inject a plugin's reducer.
2019-05-06 16:17:16 +02:00
2019-05-06 17:36:25 +02:00
**Path —** `plugins/my-plugin/admin/src/containers/App/index.js` .
2019-05-06 16:17:16 +02:00
2019-05-06 17:36:25 +02:00
```js
import React from 'react';
import { compose } from 'redux';
import pluginId from '../../pluginId';
import reducer from './reducer';
2019-05-06 16:17:16 +02:00
2019-05-06 17:36:25 +02:00
class App extends React.Component {
render() {
return null;
}
}
const withReducer = strapi.injectReducer({ key: 'app', reducer, pluginId });
2019-05-06 16:17:16 +02:00
2019-05-06 17:36:25 +02:00
export default compose(withReducer)(App);
```
#### `strapi.notification`
2019-05-06 16:17:16 +02:00
2019-05-06 17:36:25 +02:00
Display a notification (wor ks with i18n message id). Use this command anywhere in your code.
2019-05-06 16:17:16 +02:00
2019-05-06 17:36:25 +02:00
```js
strapi.notification.error('app.notification.error');
strapi.notification.info('app.notification.info');
strapi.notification.success('app.notification.success');
strapi.notification.warning('app.notification.warning');
2019-05-06 16:17:16 +02:00
```
2019-05-06 17:36:25 +02:00
#### `strapi.remoteURL`
The administration url (e.g. `http://localhost:4000/admin` ).
### Available hooks
The Admin container exposes hooks in which a plugin can run custom code.
(Documentation coming soon).
## Plugin development
(Coming soon).
### Main plugin object
Each plugin exports all its configurations in a object. This object is located in `my-plugin/admin/src/index.js`
Here are its properties:
| key | type | value |
2019-05-06 18:34:31 +02:00
| ------------------------- | -------- | :-------------------------------------------------------------------------------- |
2019-05-06 17:36:25 +02:00
| blockerComponent | node | can be either `null` or React node (e.g. `() => <div />` ) |
| blockerComponentProps | object | `{}` |
| description | string | `My awesome plugin` |
2019-05-06 18:34:31 +02:00
| id | string | Id of the plugin from the `package.json` |
2019-05-06 17:36:25 +02:00
| initializer | node | Refer to the [Initializer documentation ](#initializer ) |
| injectedComponents | array | Refer to the [Injected Component documentation ](#injected-components ) |
| leftMenuLinks | array | `[]` |
| lifecycles | function | Refer to the [Lifecycle documentation ](#lifecycle ) |
| mainComponent | node | The plugin's App container |
| preventComponentRendering | boolean | Wheter or not display the plugin's blockerComponent instead of the main component |
| trads | object | The plugin's translation files |
### Initializer
The component is generated by default when you create a new plugin. Use this component to execute some logic when the app is loading. When the logic has been executed this component should emit the `isReady` event so the user can interact with the application.
:::note
2019-05-06 18:34:31 +02:00
Below is the Initializer component of the content-type-builder plugin.
2019-05-06 17:36:25 +02:00
It checks whether or not the autoreload feature is enabled and depending on this value changes the mainComponent of the plugin.
:::
```js
/**
*
* Initializer
*
*/
import React from 'react';
import PropTypes from 'prop-types';
import pluginId from '../../pluginId';
class Initializer extends React.PureComponent {
// eslint-disable-line react/prefer-stateless-function
componentDidMount() {
const {
admin: { autoReload, currentEnvironment },
} = this.props;
let preventComponentRendering;
let blockerComponentProps;
if (currentEnvironment === 'production') {
preventComponentRendering = true;
blockerComponentProps = {
blockerComponentTitle: 'components.ProductionBlocker.header',
blockerComponentDescription: 'components.ProductionBlocker.description',
blockerComponentIcon: 'fa-ban',
blockerComponentContent: 'renderButton',
};
} else {
// Don't render the plugin if the server autoReload is disabled
preventComponentRendering = !autoReload;
blockerComponentProps = {
blockerComponentTitle: 'components.AutoReloadBlocker.header',
blockerComponentDescription: 'components.AutoReloadBlocker.description',
blockerComponentIcon: 'fa-refresh',
blockerComponentContent: 'renderIde',
};
}
// Prevent the plugin from being rendered if currentEnvironment === PRODUCTION
this.props.updatePlugin(
pluginId,
'preventComponentRendering',
preventComponentRendering,
);
this.props.updatePlugin(
pluginId,
'blockerComponentProps',
blockerComponentProps,
);
// Emit the event plugin ready
this.props.updatePlugin(pluginId, 'isReady', true);
}
render() {
return null;
}
2019-05-06 16:17:16 +02:00
}
2019-05-06 17:36:25 +02:00
Initializer.propTypes = {
admin: PropTypes.object.isRequired,
updatePlugin: PropTypes.func.isRequired,
};
export default Initializer;
2019-05-06 16:17:16 +02:00
```
2019-05-06 17:36:25 +02:00
### Lifecycle
(Coming soon)
### Injected Components
(Coming soon)
### Routing
The routing is based on the [React Router V4 ](https://reacttraining.com/react-router/web/guides/philosophy ), due to it's implementation each route is declared in the `containers/App/index.js` file.
2019-05-06 16:17:16 +02:00
2019-11-07 12:05:39 +01:00
::: tip
2019-05-06 17:36:25 +02:00
Each route defined in a plugin must be prefixed by the plugin's id.
2019-05-06 16:17:16 +02:00
:::
2019-05-06 17:36:25 +02:00
**Route declaration :**
Let's say that you want to create a route `/user` with params `/:id` associated with the container UserPage.
The declaration would be as followed :
**Path —** `plugins/my-plugin/admin/src/containers/App/index.js` .
2019-05-06 16:17:16 +02:00
```js
2019-05-06 17:36:25 +02:00
import React from 'react';
import pluginId from '../../pluginId';
import UserPage from '../UserPage';
2019-05-06 16:17:16 +02:00
// ...
2019-05-06 17:36:25 +02:00
class App extends React.Component {
// ...
2019-05-06 16:17:16 +02:00
2019-05-06 17:36:25 +02:00
render() {
return (
< div className = {styles.myPlugin} >
< Switch >
< Route
exact
path={`/plugins/${pluginId}/user/:id` }
component={UserPage}
/>
< / Switch >
< / div >
);
}
}
2019-05-06 16:17:16 +02:00
2019-05-06 17:36:25 +02:00
// ...
2019-05-06 16:17:16 +02:00
```
2019-05-06 17:36:25 +02:00
### i18n
2019-05-06 16:17:16 +02:00
[React Intl ](https://github.com/yahoo/react-intl ) provides React components and an API to format dates, numbers, and strings, including pluralization and handling translations.
**Usage**
We recommend to set all your components text inside the translations folder.
The example below shows how to use i18n inside your plugin.
**Define all your ids with the associated message:**
**Path —** `./plugins/my-plugin/admin/src/translations/en.json` .
2019-05-06 17:36:25 +02:00
2019-05-06 16:17:16 +02:00
```json
{
"notification.error.message": "An error occurred"
}
```
**Path —** `./plugins/my-plugin/admin/src/translations/fr.json`
2019-05-06 17:36:25 +02:00
2019-05-06 16:17:16 +02:00
```json
{
"notification.error.message": "Une erreur est survenue"
}
```
**Usage inside a component**
**Path —** `./plugins/my-plugin/admin/src/components/Foo/index.js` .
2019-05-06 17:36:25 +02:00
2019-05-06 16:17:16 +02:00
```js
import { FormattedMessage } from 'react-intl';
import SomeOtherComponent from 'components/SomeOtherComponent';
2019-05-06 17:36:25 +02:00
const Foo = props => (
2019-05-06 16:17:16 +02:00
< div className = {styles.foo} >
< FormattedMessage id = "my-plugin.notification.error.message" / >
< SomeOtherComponent { . . . props } / >
< / div >
2019-05-06 17:36:25 +02:00
);
2019-05-06 16:17:16 +02:00
export default Foo;
```
2019-10-23 10:11:53 +02:00
See [the documentation ](https://github.com/yahoo/react-intl/wiki/Components#formattedmessage ) for more extensive usage.
2019-05-16 18:12:56 +02:00
-->