mirror of
https://github.com/strapi/strapi.git
synced 2025-09-01 04:42:58 +00:00
Update summary and redo customize-admin file s structure
This commit is contained in:
parent
52a582f3f5
commit
ac100d1a27
@ -34,7 +34,7 @@
|
|||||||
* [Advanced usage](plugins/advanced.md)
|
* [Advanced usage](plugins/advanced.md)
|
||||||
|
|
||||||
### Guides - Advanced
|
### Guides - Advanced
|
||||||
* [Admin panel](guides-advanced/customize-admin.md)
|
* [Admin panel](advanced/customize-admin.md)
|
||||||
* [Hooks](advanced/hooks.md)
|
* [Hooks](advanced/hooks.md)
|
||||||
* [Logging](advanced/logging.md)
|
* [Logging](advanced/logging.md)
|
||||||
* [Middlewares](advanced/middlewares.md)
|
* [Middlewares](advanced/middlewares.md)
|
||||||
|
@ -7,26 +7,29 @@ See the [Contributing Guide](https://github.com/strapi/strapi/blob/master/.githu
|
|||||||
## Files structure
|
## Files structure
|
||||||
|
|
||||||
The entire logic of the admin panel is located in a single folder named `./admin/`. This directory contains the following structure:
|
The entire logic of the admin panel is located in a single folder named `./admin/`. This directory contains the following structure:
|
||||||
- `admin/`:
|
```
|
||||||
- `build/`: build of the front-end part generated by Webpack.
|
/admin
|
||||||
- `src/`: source code of the front-end part.
|
└─── admin
|
||||||
- `app.js`: entry point of the React application.
|
| └─── build // Webpack generated build of the front-end
|
||||||
- `assets/`: contains necessary assets (images…).
|
| └─── src // Front-end directory
|
||||||
- `components/`: contains components used by the admin panel.
|
| └─── app.js // Entry point of the Reacr application
|
||||||
- `containers/`: contains high level components.
|
| └─── assets // Assets directory containing images,...
|
||||||
- `favicon.ico`: favicon displayed in the web browser.
|
| └─── components // Admin's React components directory
|
||||||
- `i18n.js`: logic for internationalization.
|
| └─── containers // Admin's high level components directory
|
||||||
- `index.html`: basic html file in which are injected necessary styles and scripts.
|
| └─── favicon.ico // Favicon displayed in web browser
|
||||||
- `reducers.js`: reducers logic.
|
| └─── i18n.js // Internalization logic
|
||||||
- `store.js` store logic.
|
| └─── index.html // Basic html file in which are injected necessary styles and scripts
|
||||||
- `styles/`: contains the global styles. Specific styles are defined at the component level.
|
| └─── reducers.js // Redux reducers logic
|
||||||
- `translations/`: text messages for each supported languages.
|
| └─── store.js // Redux store logic
|
||||||
- `config/`:
|
| └─── styles // Directory containing the global styles. Specific styles are defined at the component level
|
||||||
- `routes.json`: admin panel API routes.
|
| └─── translations // Directory containing text messages for each supported languages
|
||||||
- `admin.json`: contains admin panel specific settings.
|
└─── config
|
||||||
- `controllers/`: admin panel API controllers.
|
| └─── routes.json // Admin's API routes
|
||||||
- `package.json`: contains admin panel dependencies list.
|
| └─── admin.json // Admin's specific settings
|
||||||
- `services/`: admin panel API services.
|
└─── controllers // Admin's API controllers
|
||||||
|
└─── services // Admin's API services
|
||||||
|
└─── packages.json // Admin's npm dependencies
|
||||||
|
```
|
||||||
|
|
||||||
## Customization
|
## Customization
|
||||||
|
|
@ -319,6 +319,8 @@ const shouldRenderCompo = (plugin) => new Promise((resolve, request) => {
|
|||||||
export default shouldRenderCompo;
|
export default shouldRenderCompo;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
## Using React/Redux and sagas
|
## Using React/Redux and sagas
|
||||||
|
|
||||||
If your application is going to interact with some back-end application for data, we recommend using redux saga for side effect management.
|
If your application is going to interact with some back-end application for data, we recommend using redux saga for side effect management.
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
# Button library
|
|
||||||
|
|
||||||
Button library based on bootstrap classes
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
| Property | Type | Required | Description
|
|
||||||
:---| :---| :---| :---
|
|
||||||
| `children`| node | no | Ex: `<Button primary>Click me</Button>` |
|
|
||||||
| `className`| any | no | Sets a custom className. Ex: `<Button className={styles.myCustomClass} label="Click me" />` |
|
|
||||||
| `kind` | string | no | Sets the built-in className to the button. Ex: `<Button kind="primaryAddShape" label="Click me" />` |
|
|
||||||
| `label` | string | no | Sets the button label with i18n Ex: `<Button label="myPlugin.button.label" primary/>` |
|
|
||||||
| `labelValue` | string | no | Sets the button label with i18n and a dynamic value Ex: `<Button label="myPlugin.button.label" labelValue={{ foo: 'bar' }} primary/>` |
|
|
||||||
| `loader` | bool | no | Displays a button loader. Ex: `<Button loader />` |
|
|
||||||
| `primary` | bool | no | [Bootstrap className](https://v4-alpha.getbootstrap.com/components/buttons/) |
|
|
||||||
| `primaryAddShape` | bool | no | Inserts fontAwesone plus icon inside the button. Ex: `<Button primaryAddShape>Click me</Button>` |
|
|
||||||
| `secondary`| bool | no | [Bootstrap className](https://v4-alpha.getbootstrap.com/components/buttons/) |
|
|
||||||
| `secondaryHotline` | bool | no | Sets className |
|
|
||||||
| `secondaryHotlineAdd` | bool | no | Inserts fontAwesone plus icon inside the button. Ex: `<Button secondaryHotlineAdd>Click me</Button>` |
|
|
||||||
| `type` | string | no | Sets the button type |
|
|
@ -1,46 +0,0 @@
|
|||||||
# i18n
|
|
||||||
|
|
||||||
[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
|
|
||||||
|
|
||||||
`// my-plugin/admin/src/translations/en.json`
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"notification.error.message": "An error occurred"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`// my-plugin/admin/src/translations/fr.json`
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"notification.error.message": "Une erreur est survenue"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Usage inside a component
|
|
||||||
|
|
||||||
Basic usage :
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
const Foo = (props) => (
|
|
||||||
<div className={styles.foo}>
|
|
||||||
<FormattedMessage id="my-plugin.notification.error.message" />
|
|
||||||
<SomeOtherComponent {...props} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default Foo;
|
|
||||||
```
|
|
||||||
|
|
||||||
[Check out the documentation](https://github.com/yahoo/react-intl/wiki/Components#formattedmessage) for more extensive usage.
|
|
@ -1,86 +0,0 @@
|
|||||||
# Input Library
|
|
||||||
|
|
||||||
Strapi provides a built-in input library which includes :
|
|
||||||
- All kind of inputs
|
|
||||||
- Front-End validations
|
|
||||||
- Error highlight
|
|
||||||
- i18n
|
|
||||||
- ...
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```js
|
|
||||||
import React from 'react';
|
|
||||||
// The library is available under node_modules/strapi-helper-plugin/src/components/Input
|
|
||||||
import Input from 'components/Input';
|
|
||||||
|
|
||||||
class Foo extends React.Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state {
|
|
||||||
data: {
|
|
||||||
foo: 'bar',
|
|
||||||
},
|
|
||||||
error: false,
|
|
||||||
errors: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChange = ({ target }) => {
|
|
||||||
const value = target.type === 'number' ? Number(target.value) : target.value;
|
|
||||||
|
|
||||||
const error = target.value.length === 0;
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
this.setState({ error: !this.state.error, errors: [{ id: 'This input is required ' }] });
|
|
||||||
} else {
|
|
||||||
this.setState({ data[target.name]: value });
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className={styles.foo}>
|
|
||||||
<Input
|
|
||||||
type="string"
|
|
||||||
value={this.state.data.foo}
|
|
||||||
label="This is a string input"
|
|
||||||
name="foo"
|
|
||||||
handleChange={this.handleChange}
|
|
||||||
validations={{ required: true }}
|
|
||||||
didCheckErrors={this.state.error}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
|
|
||||||
| Property | Type | Required | Description
|
|
||||||
:---| :---| :---| :---
|
|
||||||
| `addon` | string | no | Allows to add a string addon in your input, based on [Bootstrap](https://v4-alpha.getbootstrap.com/components/input-group/#basic-example). Ex: `<Input {...this.props} addon="@" />` |
|
|
||||||
| `addRequiredInputDesign` | bool | no | Allows to add an asterix on the input. Ex: `<Input {...this.props} addRequiredInputDesign />` |
|
|
||||||
| `customBootstrapClass` | string | no | Allows to override the input bootstrap col system. Ex: `<Input {...this.props} customBootstrapClass="col-md-6 offset-md-6 pull-md-6" />` |
|
|
||||||
| `deactivateErrorHighlight` | bool | no | Prevents from displaying error highlight in the input: Ex: `<Input {...this.props} deactivateErrorHighlight />` |
|
|
||||||
| `didCheckErrors` | bool | no | Use this props to display errors after submitting a form. Ex: `<Input {...this.props} didCheckErrors={this.state.error} />` |
|
|
||||||
| `disabled` | bool | no | Disable the input. Ex: `<Input {...this.props} disabled />` |
|
|
||||||
| `errors` | array | no | Allows to display custom error messages. Ex: `<Input {...this.props} errors={[{ id: 'The value is not correct' }]} />` |
|
|
||||||
| `handleBlur` | func or bool | no | Overrides the default onBlur behavior. If bool passed to the component it will disabled the input validations checking. |
|
|
||||||
| `handleChange` | func | yes | Sets your reducer state. |
|
|
||||||
| `handFocus` | func | no | Adds an onFocus event to the input. |
|
|
||||||
| `inputDescription` | string | no | Allows to add an input description that is displayed like [bootstrap](https://v4-alpha.getbootstrap.com/components/forms/#defining-states). |
|
|
||||||
| `label` | string | yes | Displays the input's label with i18n |
|
|
||||||
| `linkContent` | object | no | Allows to display a link within the input's description. Ex: `<Input {...this.props} linkContent={{ description: 'check out our', link: 'tutorial video' }} />`|
|
|
||||||
| `name` | string | yes | The key to update your reducer. |
|
|
||||||
| `noErrorsDescription` | bool | no | Prevents from displaying built-in errors. |
|
|
||||||
| `placeholder` | string | no | Allows to set a placeholder. |
|
|
||||||
| `pluginId` | string | no | Use to display name, placeholder... with i18n. |
|
|
||||||
| `selectOptions` | array | no | Options for the select. |
|
|
||||||
| `tabIndex` | string | no | Sets the order in which the inputs are focused on tab key press. |
|
|
||||||
| `title` | string | no | This props can only be used for checkboxes, it allows to add a title on top of the input, the label will be on the right side of the checkbox. |
|
|
||||||
| `validations` | object | yes | Allows to have the built-in input's validations. If set to {} the validations will be ignored. Ex: `<Input {...this.props} validations={{ required: true }} />` |
|
|
||||||
| `value` | string or bool or number | yes | The input's value. |
|
|
@ -1,127 +0,0 @@
|
|||||||
# Prevent a plugin from being rendered
|
|
||||||
|
|
||||||
You can prevent your plugin from being rendered if some conditions aren't met.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
To disable your plugin's rendering, you can simply create `requirements.js` file at the root of your `src` plugin's folder.
|
|
||||||
This file must contain a default function that returns a `Promise`;
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
Let's say that you want to disable your plugin if the server autoReload config is disabled in development mode.
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
// config/environments/development/server.json
|
|
||||||
{
|
|
||||||
"host": "localhost",
|
|
||||||
"port": 1337,
|
|
||||||
"cron": {
|
|
||||||
"enabled": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
You'll first create a request to check if the `autoReload` config is enabled.
|
|
||||||
|
|
||||||
```json
|
|
||||||
// plugins/my-plugin/config/routes.json
|
|
||||||
{
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/autoReload",
|
|
||||||
"handler": "MyPlugin.autoReload",
|
|
||||||
"config": {
|
|
||||||
"policies": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Then the associated handler:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// plugins/my-plugin/controllers/MyPlugin.js
|
|
||||||
|
|
||||||
const _ = require('lodash');
|
|
||||||
const send = require('koa-send');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
autoReload: async ctx => {
|
|
||||||
ctx.send({ autoReload: _.get(strapi.config.environments, 'development.server.autoReload', false) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Finally, you'll create a file called `requirements.js`at the root of your plugin's src folder.
|
|
||||||
|
|
||||||
The default function exported must return a `Promise`.
|
|
||||||
If you wan't to prevent the plugin from being rendered you'll have to set `plugin.preventComponentRendering = true;`.
|
|
||||||
In this case, you'll have to set:
|
|
||||||
```js
|
|
||||||
plugin.blockerComponentProps = {
|
|
||||||
blockerComponentTitle: 'my-plugin.blocker.title',
|
|
||||||
blockerComponentDescription: 'my-plugin.blocker.description',
|
|
||||||
blockerComponentIcon: 'fa-refresh',
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
To follow the example above:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// plugin/my-plugin/admin/src/requirements.js
|
|
||||||
|
|
||||||
// Use our request helper
|
|
||||||
import request from 'utils/request';
|
|
||||||
|
|
||||||
const shouldRenderCompo = (plugin) => new Promise((resolve, request) => {
|
|
||||||
request('/my-plugin/autoReload')
|
|
||||||
.then(response => {
|
|
||||||
// If autoReload is enabled the response is `{ autoReload: true }`
|
|
||||||
plugin.preventComponentRendering = !response.autoReload;
|
|
||||||
// Set the BlockerComponent props
|
|
||||||
plugin.blockerComponentProps = {
|
|
||||||
blockerComponentTitle: 'my-plugin.blocker.title',
|
|
||||||
blockerComponentDescription: 'my-plugin.blocker.description',
|
|
||||||
blockerComponentIcon: 'fa-refresh',
|
|
||||||
blockerComponentContent: 'renderIde', // renderIde will add an ide section that shows the development environment server.json config
|
|
||||||
};
|
|
||||||
|
|
||||||
return resolve(plugin);
|
|
||||||
})
|
|
||||||
.catch(err => reject(err));
|
|
||||||
});
|
|
||||||
|
|
||||||
export default shouldRenderCompo;
|
|
||||||
```
|
|
||||||
## Customization
|
|
||||||
|
|
||||||
You can render your own custom blocker by doing as follows:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// plugin/my-plugin/admin/src/requirements.js
|
|
||||||
|
|
||||||
// Use our request helper
|
|
||||||
import request from 'utils/request';
|
|
||||||
|
|
||||||
// Your custom blockerComponentProps
|
|
||||||
import MyCustomBlockerComponent from 'components/MyCustomBlockerComponent';
|
|
||||||
|
|
||||||
const shouldRenderCompo = (plugin) => new Promise((resolve, request) => {
|
|
||||||
request('/my-plugin/autoReload')
|
|
||||||
.then(response => {
|
|
||||||
// If autoReload is enabled the response is `{ autoReload: true }`
|
|
||||||
plugin.preventComponentRendering = !response.autoReload;
|
|
||||||
|
|
||||||
// Tell which component to be rendered instead
|
|
||||||
plugin.blockerComponent = MyCustomBlockerComponent;
|
|
||||||
|
|
||||||
return resolve(plugin);
|
|
||||||
})
|
|
||||||
.catch(err => reject(err));
|
|
||||||
});
|
|
||||||
|
|
||||||
export default shouldRenderCompo;
|
|
||||||
```
|
|
@ -1,279 +0,0 @@
|
|||||||
# Using the React Router V4
|
|
||||||
|
|
||||||
User navigation within your plugin can be managed by two different ways :
|
|
||||||
- Using the [React Router V4 API](https://reacttraining.com/react-router/web/guides/philosophy)
|
|
||||||
- Using the main router from the app
|
|
||||||
|
|
||||||
## Routing declaration
|
|
||||||
|
|
||||||
### Routing
|
|
||||||
|
|
||||||
The routing is based on [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.
|
|
||||||
|
|
||||||
Also, we chose to use the [Switch Router](https://reacttraining.com/react-router/web/api/Switch) because it renders a route exclusively.
|
|
||||||
|
|
||||||
Route declaration :
|
|
||||||
|
|
||||||
Let's say that you want to create a route /user with params /:id associated with the container User.
|
|
||||||
|
|
||||||
The declaration would be as followed :
|
|
||||||
|
|
||||||
|
|
||||||
```js
|
|
||||||
// File : `plugins/my-plugin/admin/src/containers/App/index.js`.
|
|
||||||
|
|
||||||
import UserPage from 'containers/UserPage'
|
|
||||||
|
|
||||||
// ...
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className={styles.myPlugin}>
|
|
||||||
<Switch>
|
|
||||||
<Route exact path="/plugins/my-plugin/user/:id" component={UserPage} />
|
|
||||||
</Switch>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Using Redux/sagas
|
|
||||||
|
|
||||||
Due to React Router V4 your container's store is not directly injected.
|
|
||||||
To inject your container store if it's associated with a router you have to do it manually.
|
|
||||||
|
|
||||||
As an example, you created a Foo container associated with the route `/plugins/my-plugin/bar`, and you want to use redux/action/reducer/sagas.
|
|
||||||
|
|
||||||
Your `plugins/my-plugin/admin/src/containers/App/index.js` file will look as followed :
|
|
||||||
|
|
||||||
```js
|
|
||||||
// plugins/my-plugin/admin/src/containers/App/index.js
|
|
||||||
|
|
||||||
import FooPage from 'containers/FooPage'
|
|
||||||
|
|
||||||
// ...
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className={styles.myPlugin}>
|
|
||||||
<Switch>
|
|
||||||
<Route exact path="/plugins/my-plugin/bar" component={FooPage} />
|
|
||||||
</Switch>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```js
|
|
||||||
// plugins/my-plugin/admin/src/containers/FooPage/index.js
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { createStructuredSelector } from 'reselect';
|
|
||||||
import { bindActionCreators, compose } from 'redux';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
// Utils to create your container store
|
|
||||||
import injectReducer from 'utils/injectReducer';
|
|
||||||
import injectSaga from 'utils/injectSaga';
|
|
||||||
|
|
||||||
import {
|
|
||||||
foo,
|
|
||||||
bar,
|
|
||||||
} from './actions';
|
|
||||||
|
|
||||||
import reducer from './reducer';
|
|
||||||
|
|
||||||
import saga from './sagas';
|
|
||||||
import { makeSelectFooPage } from './selectors';
|
|
||||||
|
|
||||||
// Styles
|
|
||||||
import styles from './styles.scss';
|
|
||||||
|
|
||||||
export class FooPage extends React.Component {
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className={styles.fooPage}>
|
|
||||||
Awesome container
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FooPage.propTypes = {
|
|
||||||
fooPage: PropTypes.any,
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
|
||||||
return bindActionCreators(
|
|
||||||
{
|
|
||||||
foo,
|
|
||||||
bar,
|
|
||||||
},
|
|
||||||
dispatch
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
fooPage: makeSelectFooPage(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const withConnect = connect(mapStateToProps, mapDispatchToProps);
|
|
||||||
|
|
||||||
// This is where you create your container store
|
|
||||||
// the key must correspond to your container name in lowerCase
|
|
||||||
const withSagas = injectSaga({ key: 'fooPage', saga });
|
|
||||||
const withReducer = injectReducer({ key: 'fooPage', reducer });
|
|
||||||
|
|
||||||
export default compose(
|
|
||||||
withReducer,
|
|
||||||
withSagas,
|
|
||||||
withConnect,
|
|
||||||
)(FooPage);
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Important : If you have a container which can be a child of several other containers (i.e. it doesn't have a route), in order to create the store
|
|
||||||
you'll have to inject it directly in the `plugins/my-plugin/admin/src/containers/App/index.js` file as follows :
|
|
||||||
|
|
||||||
```js
|
|
||||||
// containers/App/index.js
|
|
||||||
|
|
||||||
// ...
|
|
||||||
|
|
||||||
import fooPageReducer from 'containers/FooPage/reducer';
|
|
||||||
import fooPageSagas from 'container/FooPage/sagas';
|
|
||||||
|
|
||||||
import reducer from './reducer';
|
|
||||||
import saga from './sagas';
|
|
||||||
|
|
||||||
// ...
|
|
||||||
|
|
||||||
export class App extends React.Component {
|
|
||||||
render() {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ...
|
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
|
||||||
return bindActionCreators(
|
|
||||||
{},
|
|
||||||
dispatch
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
// ...
|
|
||||||
});
|
|
||||||
|
|
||||||
const withConnect = connect(mapStateToProps, mapDispatchToProps);
|
|
||||||
|
|
||||||
// FooPage reducer
|
|
||||||
const withFooPageReducer = injectReducer({ key: 'fooPage', reducer: fooPageReducer });
|
|
||||||
// Global reducer
|
|
||||||
const withReducer = injectReducer({ key: 'global', reducer });
|
|
||||||
// FooPage sagas
|
|
||||||
const withFooPageSagas = injectSaga({ key: 'fooPage', saga: fooPageSagas });
|
|
||||||
// Global saga
|
|
||||||
const withSagas = injectSaga({ key: 'global', saga });
|
|
||||||
|
|
||||||
export default compose(
|
|
||||||
withFooPageReducer,
|
|
||||||
withReducer,
|
|
||||||
withFooPageSagas,
|
|
||||||
withSagas,
|
|
||||||
withConnect,
|
|
||||||
)(App);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Using React Router
|
|
||||||
|
|
||||||
[Link](https://reacttraining.com/react-router/web/api/Link) provides declarative, accessible navigation around your application :
|
|
||||||
|
|
||||||
```js
|
|
||||||
<Link to={{
|
|
||||||
pathname: `/plugins/my-plugin/foo/${this.props.bar}`,
|
|
||||||
search: '?foo=bar',
|
|
||||||
hash: '#the-hash',
|
|
||||||
}} />
|
|
||||||
|
|
||||||
// Same as
|
|
||||||
|
|
||||||
<Link to=`/plugins/my-plugin/foo/${this.props.bar}?foo=bar#the-hash` />
|
|
||||||
```
|
|
||||||
|
|
||||||
[NavLink](https://reacttraining.com/react-router/web/api/NavLink) will add styling attributes to the rendered element when it matches the current URL.
|
|
||||||
|
|
||||||
|
|
||||||
```js
|
|
||||||
<NavLink
|
|
||||||
to="/faq"
|
|
||||||
activeClassName="selected"
|
|
||||||
>FAQs</NavLink>
|
|
||||||
```
|
|
||||||
## Using the App Router
|
|
||||||
|
|
||||||
We use the app router if we want to make a redirection after some user's action (ex: after submitting a form).
|
|
||||||
|
|
||||||
```js
|
|
||||||
import React from 'react';
|
|
||||||
import { bindActionCreators } from 'redux';
|
|
||||||
import { connect, compose } from 'react-redux';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
// App router
|
|
||||||
import { router } from 'app';
|
|
||||||
|
|
||||||
// Utils
|
|
||||||
import injectSaga from 'utils/injectSaga';
|
|
||||||
import injectReducer from 'utils/injectReducer';
|
|
||||||
|
|
||||||
// Actions
|
|
||||||
import { foo, bar } from './actions';
|
|
||||||
// Sagas
|
|
||||||
import saga from './sagas';
|
|
||||||
// Selectors
|
|
||||||
import selectFoo from './Selectors';
|
|
||||||
// Reducer
|
|
||||||
import reducer from './reducer';
|
|
||||||
|
|
||||||
export class Foo extends React.Component {
|
|
||||||
componentWillReceiveProps(nextProps) {
|
|
||||||
if (nextProps.foo !== this.props.foo) {
|
|
||||||
const hash = this.props.location.hash;
|
|
||||||
const pathname = this.props.match.pathname;
|
|
||||||
const search = '?foo=bar';
|
|
||||||
router.push({ pathname, search, hash });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return <div>Hello</div>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Foo.propTypes = {
|
|
||||||
foo: PropTypes.bool.isRequired,
|
|
||||||
location: PropTypes.object.isRequired,
|
|
||||||
match: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = selectFoo();
|
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
|
||||||
return bindActionCreators({}, dispatch);
|
|
||||||
}
|
|
||||||
|
|
||||||
const withConnect = connect(mapStateToProps, mapDispatchToProps);
|
|
||||||
const withReducer = injectReducer({ key: 'foo', reducer });
|
|
||||||
const withSagas = injectSaga({ key: 'foo', saga });
|
|
||||||
|
|
||||||
export default compose(
|
|
||||||
withReducer,
|
|
||||||
withSagas,
|
|
||||||
withConnect,
|
|
||||||
)(Foo);
|
|
||||||
```
|
|
@ -1,282 +0,0 @@
|
|||||||
# Redux saga
|
|
||||||
|
|
||||||
If your application is going to interact with some back-end application for data, we recommend using redux saga for side effect management.
|
|
||||||
|
|
||||||
## Example : fetching data on router change
|
|
||||||
|
|
||||||
This example will show how to fetch data using actions/reducer/sagas.
|
|
||||||
|
|
||||||
### Constants declaration
|
|
||||||
|
|
||||||
```js
|
|
||||||
// containers/Foo/constants.js
|
|
||||||
export const DATA_FETCH = 'MyPlugin/Foo/DATA_FETCH';
|
|
||||||
export const DATA_FETCH_ERROR = 'MyPlugin/Foo/DATA_FETCH_ERROR';
|
|
||||||
export const DATA_FETCH_SUCCEEDED = 'MyPlugin/Foo/DATA_FETCH_SUCCEEDED';
|
|
||||||
```
|
|
||||||
|
|
||||||
### Actions declaration
|
|
||||||
|
|
||||||
```js
|
|
||||||
// containers/Foo/actions.js
|
|
||||||
import {
|
|
||||||
DATA_FETCH,
|
|
||||||
DATA_FETCH_ERROR,
|
|
||||||
DATA_FETCH_SUCCEEDED,
|
|
||||||
} from './constants';
|
|
||||||
|
|
||||||
export function dataFetch(params) {
|
|
||||||
return {
|
|
||||||
type: DATA_FETCH,
|
|
||||||
params,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function dataFetchError(errorMessage) {
|
|
||||||
return {
|
|
||||||
type: DATA_FETCH_ERROR,
|
|
||||||
errorMessage,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function dataFetchSucceeded(data) {
|
|
||||||
return {
|
|
||||||
type: DATA_FETCH_SUCCEEDED,
|
|
||||||
data,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Reducer
|
|
||||||
|
|
||||||
We strongly recommend to use [Immutable.js](https://facebook.github.io/immutable-js/) to structure your data.
|
|
||||||
|
|
||||||
```js
|
|
||||||
// containers/Foo/reducer.js
|
|
||||||
import { fromJS, Map } from 'immutable';
|
|
||||||
import {
|
|
||||||
DATA_FETCH_ERROR,
|
|
||||||
DATA_FETCH_SUCCEEDED,
|
|
||||||
} from './constants';
|
|
||||||
|
|
||||||
const initialState = fromJS({
|
|
||||||
data: Map({}),
|
|
||||||
error: false,
|
|
||||||
errorMessage: '',
|
|
||||||
loading: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
function fooReducer(state = initialState, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case DATA_FETCH_ERROR:
|
|
||||||
return state
|
|
||||||
.set('error', true)
|
|
||||||
.set('errorMessage', action.errorMessage)
|
|
||||||
.set('loading', false);
|
|
||||||
case DATA_FETCH_SUCCEEDED:
|
|
||||||
return state
|
|
||||||
.set('data', Map(action.data))
|
|
||||||
.set('error', false)
|
|
||||||
.set('errorMessage', '')
|
|
||||||
.set('loading', false);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default fooReducer;
|
|
||||||
```
|
|
||||||
### Sagas
|
|
||||||
|
|
||||||
```js
|
|
||||||
// container/Foo/sagas.js
|
|
||||||
import { takeLatest } from 'redux-saga';
|
|
||||||
import { LOCATION_CHANGE } from 'react-router-redux';
|
|
||||||
import { put, fork, call, take, cancel } from 'redux-saga/effects';
|
|
||||||
|
|
||||||
// Use our request helper
|
|
||||||
import { request } from 'utils/request';
|
|
||||||
|
|
||||||
// Actions
|
|
||||||
import { dataFetchError, dataFetchSucceeded } from './actions';
|
|
||||||
import { DATA_FETCH } from './constants';
|
|
||||||
|
|
||||||
export function* fetchData(action) {
|
|
||||||
try {
|
|
||||||
const requestUrl = `/baseUrl/${action.params}`;
|
|
||||||
const opts = {
|
|
||||||
method: 'GET',
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fetch data
|
|
||||||
const response = yield call(request, requestUrl, opts);
|
|
||||||
|
|
||||||
// Pass the response to the reducer
|
|
||||||
yield put(dataFetchSucceeded(response));
|
|
||||||
|
|
||||||
} catch(error) {
|
|
||||||
yield put(dataFetchError(error));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Individual export for testing
|
|
||||||
function* defaultSaga() {
|
|
||||||
// Listen to DATA_FETCH event
|
|
||||||
const fetchDataWatcher = yield fork(takeLatest, DATA_FETCH, fetchData);
|
|
||||||
|
|
||||||
// Cancel watcher
|
|
||||||
yield take(LOCATION_CHANGE);
|
|
||||||
|
|
||||||
yield cancel(fetchDataWatcher);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defaultSaga;
|
|
||||||
```
|
|
||||||
|
|
||||||
N.B. You can use a selector in your saga :
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { put, select, fork, call, take, cancel } from 'redux-saga/effects';
|
|
||||||
import { makeSelectUserName } from './selectors';
|
|
||||||
|
|
||||||
export function* foo() {
|
|
||||||
try {
|
|
||||||
const userName = yield select(makeSelectUserName());
|
|
||||||
|
|
||||||
// ...
|
|
||||||
} catch(error) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function defaultSaga() {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defaultSaga;
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Selectors
|
|
||||||
|
|
||||||
[Reselect](https://github.com/reactjs/reselect) is a library used for slicing your redux state and providing only the relevant sub-tree to a react component. It has three key features:
|
|
||||||
|
|
||||||
1. Computational power
|
|
||||||
2. Memoization
|
|
||||||
3. Composability
|
|
||||||
|
|
||||||
Creating a selector
|
|
||||||
```js
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Direct selector to the foo state domain
|
|
||||||
*/
|
|
||||||
const selectFooDomain = () => state => state.get('foo');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Other specific selectors
|
|
||||||
*/
|
|
||||||
|
|
||||||
const makeSelectLoading = () => createSelector(
|
|
||||||
selectFooDomain(),
|
|
||||||
(substate) => substate.get('loading'),
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default selector used by ModelPage
|
|
||||||
*/
|
|
||||||
|
|
||||||
const selectFoo = () => createSelector(
|
|
||||||
selectFooDomain(),
|
|
||||||
(substate) => substate.toJS()
|
|
||||||
);
|
|
||||||
|
|
||||||
export default selectFoo;
|
|
||||||
export { makeSelectLoading };
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### INDEX.js
|
|
||||||
|
|
||||||
```js
|
|
||||||
// containers/Foo/index.js
|
|
||||||
import React from 'react';
|
|
||||||
import { bindActionCreators } from 'redux';
|
|
||||||
import { connect, compose } from 'react-redux';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
// Main router
|
|
||||||
import { router } from 'app';
|
|
||||||
|
|
||||||
// Utils
|
|
||||||
import injectSaga from 'utils/injectSaga';
|
|
||||||
import injectReducer from 'utils/injectReducer';
|
|
||||||
|
|
||||||
// Actions
|
|
||||||
import { dataFetch } from './actions';
|
|
||||||
// sagas
|
|
||||||
import saga from './sagas';
|
|
||||||
// Selectors
|
|
||||||
import selectFoo from './selectors';
|
|
||||||
// Reducer
|
|
||||||
import reducer from './reducer';
|
|
||||||
|
|
||||||
export class Foo extends React.Component {
|
|
||||||
componentWillReceiveProps(nextProps) {
|
|
||||||
if (this.props.error !== nextProps.error && nextProps.error) {
|
|
||||||
window.Strapi.notification.error(this.props.errorMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
|
||||||
if (prevProps.match.pathname !== this.props.pathname) {
|
|
||||||
this.props.dataFetch(this.props.match.params.bar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (this.props.error) return <div>An error occurred</div>;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h4>Data display</h4>
|
|
||||||
<span>{this.props.data.foo}</span>
|
|
||||||
<span>{this.props.data.bar}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Foo.propTypes = {
|
|
||||||
data: PropTypes.object.isRequired,
|
|
||||||
dataFetch: PropTypes.func.isRequired,
|
|
||||||
error: PropTypes.bool.isRequired,
|
|
||||||
errorMessage: PropTypes.string.isRequired,
|
|
||||||
match: PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = selectFoo();
|
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
|
||||||
return bindActionCreators(
|
|
||||||
{
|
|
||||||
dataFetch,
|
|
||||||
},
|
|
||||||
dispatch
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const withConnect = connect(mapStateToProps, mapDispatchToProps);
|
|
||||||
const withReducer = injectReducer({ key: 'foo', reducer });
|
|
||||||
const withSagas = injectSaga({ key: 'foo', saga });
|
|
||||||
|
|
||||||
export default compose(
|
|
||||||
withReducer,
|
|
||||||
withSagas,
|
|
||||||
withConnect,
|
|
||||||
)(Foo);
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
@ -1,60 +0,0 @@
|
|||||||
# Component Button
|
|
||||||
|
|
||||||
Button library based on bootstrap classes
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
| Property | Type | Required | Description |
|
|
||||||
| -------- | ---- | -------- | ----------- |
|
|
||||||
| `children`| node | no | Ex: `<Button primary>Click me</Button>` |
|
|
||||||
| `className` | any | no | Sets a custom className. Ex: `<Button className={styles.myCustomClass} label="Click me" />` |
|
|
||||||
| `kind` | string | no | Sets the built-in className to the button. Ex: `<Button kind="primaryAddShape" label="Click me" />` |
|
|
||||||
| `label` | string | no | Sets the button label with i18n Ex: `<Button label="myPlugin.button.label" primary />` |
|
|
||||||
| `labelValue` | string | no | Sets the button label with i18n and a dynamic value Ex: {% raw %} ```<Button label="myPlugin.button.label" labelValue={{ foo: 'bar' }} primary />``` {% endraw %} |
|
|
||||||
| `loader` | bool | no | Displays a button loader. Ex: `<Button loader />` |
|
|
||||||
| `primary` | bool | no | [Bootstrap className](https://v4-alpha.getbootstrap.com/components/buttons/) |
|
|
||||||
| `primaryAddShape` | bool | no | Inserts fontAwesone plus icon inside the button. Ex: `<Button primaryAddShape>Click me</Button>` |
|
|
||||||
| `secondary`| bool | no | [Bootstrap className](https://v4-alpha.getbootstrap.com/components/buttons/) |
|
|
||||||
| `secondaryHotline` | bool | no | Sets className |
|
|
||||||
| `secondaryHotlineAdd` | bool | no | Inserts fontAwesone plus icon inside the button. Ex: `<Button secondaryHotlineAdd>Click me</Button>` |
|
|
||||||
| `type` | string | no | Sets the button type |
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
**Path —** `./plugins/my-plugin/admin/src/translations/en.json`.
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"myPlugin.button.label": "Add a new"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Path —** `./plugins/my-plugin/admin/src/components/Foo/index.js`.
|
|
||||||
```js
|
|
||||||
import Button from 'components/Button';
|
|
||||||
|
|
||||||
const Foo = () => {
|
|
||||||
// Define your buttons
|
|
||||||
const buttons = [
|
|
||||||
{
|
|
||||||
label: 'myPlugin.button.label',
|
|
||||||
labelValues: {
|
|
||||||
foo: 'Bar',
|
|
||||||
},
|
|
||||||
kind: 'primaryAddShape',
|
|
||||||
onClick: () => console.log('Click'),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.foo}>
|
|
||||||
{buttons.map((buttonProps) => <Button {...buttonProps} key={buttonProps.label})} />}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Will display a primaryAddShape button with label: 'Add a new Bar'
|
|
||||||
|
|
||||||
export default Button;
|
|
||||||
```
|
|
@ -1,85 +0,0 @@
|
|||||||
# Input Library
|
|
||||||
|
|
||||||
Strapi provides a built-in input library which includes :
|
|
||||||
- All kind of inputs
|
|
||||||
- Front-End validations
|
|
||||||
- Error highlight
|
|
||||||
- i18n
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
| Property | Type | Required | Description |
|
|
||||||
| -------- | ---- | -------- | ----------- |
|
|
||||||
| `addon` | string | no | Allows to add a string addon in your input, based on [Bootstrap](https://v4-alpha.getbootstrap.com/components/input-group/#basic-example). Ex: `<Input {...this.props} addon="@" />` |
|
|
||||||
| `addRequiredInputDesign` | bool | no | Allows to add an asterix on the input. Ex: `<Input {...this.props} addRequiredInputDesign />` |
|
|
||||||
| `customBootstrapClass` | string | no | Allows to override the input bootstrap col system. Ex: `<Input {...this.props} customBootstrapClass="col-md-6 offset-md-6 pull-md-6" />` |
|
|
||||||
| `deactivateErrorHighlight` | bool | no | Prevents from displaying error highlight in the input: Ex: `<Input {...this.props} deactivateErrorHighlight />` |
|
|
||||||
| `didCheckErrors` | bool | no | Use this props to display errors after submitting a form. Ex: `<Input {...this.props} didCheckErrors={this.state.error} />` |
|
|
||||||
| `disabled` | bool | no | Disable the input. Ex: `<Input {...this.props} disabled />` |
|
|
||||||
| `errors` | array | no | Allows to display custom error messages. Ex: `<Input {...this.props} errors={[{ id: 'The value is not correct' }]} />` |
|
|
||||||
| `handleBlur` | func or bool | no | Overrides the default onBlur behavior. If bool passed to the component it will disabled the input validations checking. |
|
|
||||||
| `handleChange` | func | yes | Sets your reducer state. |
|
|
||||||
| `handFocus` | func | no | Adds an onFocus event to the input. |
|
|
||||||
| `inputDescription` | string | no | Allows to add an input description that is displayed like [bootstrap](https://v4-alpha.getbootstrap.com/components/forms/#defining-states). |
|
|
||||||
| `label` | string | yes | Displays the input's label with i18n. |
|
|
||||||
| `linkContent` | object | no | Allows to display a link within the input's description. Ex: {% raw %} ``` <Input {...this.props} linkContent={{ description: 'check out our', link: 'tutorial video' }} />``` {% endraw %} |
|
|
||||||
| `name` | string | yes | The key to update your reducer. |
|
|
||||||
| `noErrorsDescription` | bool | no | Prevents from displaying built-in errors. |
|
|
||||||
| `placeholder` | string | no | Allows to set a placeholder. |
|
|
||||||
| `pluginId` | string | no | Use to display name, placeholder... with i18n. |
|
|
||||||
| `selectOptions` | array | no | Options for the select. |
|
|
||||||
| `tabIndex` | string | no | Sets the order in which the inputs are focused on tab key press. |
|
|
||||||
| `title` | string | no | This props can only be used for checkboxes, it allows to add a title on top of the input, the label will be on the right side of the checkbox. |
|
|
||||||
| `validations` | object | yes | Allows to have the built-in input's validations. If set to {} the validations will be ignored. Ex: {% raw %} ``` <Input {...this.props} validations={{ required: true }} />``` {% endraw %} |
|
|
||||||
| `value` | string or bool or number | yes | The input's value. |
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
```js
|
|
||||||
import React from 'react';
|
|
||||||
// The library is available under node_modules/strapi-helper-plugin/src/components/Input
|
|
||||||
import Input from 'components/Input';
|
|
||||||
|
|
||||||
class Foo extends React.Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state {
|
|
||||||
data: {
|
|
||||||
foo: 'bar',
|
|
||||||
},
|
|
||||||
error: false,
|
|
||||||
errors: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChange = ({ target }) => {
|
|
||||||
const value = target.type === 'number' ? Number(target.value) : target.value;
|
|
||||||
|
|
||||||
const error = target.value.length === 0;
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
this.setState({ error: !this.state.error, errors: [{ id: 'This input is required ' }] });
|
|
||||||
} else {
|
|
||||||
this.setState({ data[target.name]: value, error: false, errors: [] });
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className={styles.foo}>
|
|
||||||
<Input
|
|
||||||
type="string"
|
|
||||||
value={this.state.data.foo}
|
|
||||||
label="This is a string input"
|
|
||||||
name="foo"
|
|
||||||
handleChange={this.handleChange}
|
|
||||||
validations={{ required: true }}
|
|
||||||
didCheckErrors={this.state.error}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
@ -1,74 +0,0 @@
|
|||||||
# PopUp Warning
|
|
||||||
|
|
||||||
PopUp warning library based on [reactstrap](https://reactstrap.github.io/components/modals/).
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
| Property | Type | Required | Description |
|
|
||||||
| -------- | ---- | -------- | ----------- |
|
|
||||||
| bodyMessage | string | no | Body message of the pop up (works with i18n). |
|
|
||||||
| handleConfirm | func | yes | Function executed when the user clicks on the `Confirm button`. |
|
|
||||||
| isOpen | bool | yes | Show or hide the popup. |
|
|
||||||
| popUpWarningType | string | yes | Sets the popup body icon. Available types: `danger`, `info`, `notFound`, `success`, `warning` |
|
|
||||||
| toggleModal | func | yes | Function to toggle the modal. |
|
|
||||||
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
**Path —** `./plugins/my-plugin/admin/src/translations/en.json`.
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"button.label": "Click me",
|
|
||||||
"popup.danger.message": "Are you sure you want to delete this item?"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Path —** `./plugins/my-plugin/admin/src/translations/fr.json`.
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"button.label": "Cliquez",
|
|
||||||
"popup.danger.message": "Êtes-vous certain de vouloir supprimer ce message?"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Path —** `./plugins/my-plugin/admin/src/containers/Foo/index.js`.
|
|
||||||
```js
|
|
||||||
// ...
|
|
||||||
|
|
||||||
import Button from 'components/Button';
|
|
||||||
import PopUpWarning from 'components/PopUpWarning';
|
|
||||||
|
|
||||||
// ...
|
|
||||||
|
|
||||||
class Foo extends React.Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
isOpen: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
handlePopUpConfirm = () => {
|
|
||||||
// Some logic Here
|
|
||||||
this.setState({ isOpen: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return(
|
|
||||||
<div>
|
|
||||||
<Button primary handleClick={() => this.setState({ isOpen: !this.state.isOpen })} label="my-plugin.button.label" />
|
|
||||||
<PopUpWarning
|
|
||||||
bodyMessage="my-plugin.popup.danger.message"
|
|
||||||
handleConfirm={this.handlePopUpConfirm}
|
|
||||||
toggleModal={() => this.setState({ isOpen: !this.state.isOpen })}
|
|
||||||
popUpWarningType="danger"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Foo;
|
|
||||||
```
|
|
@ -1,112 +0,0 @@
|
|||||||
# Consume your API
|
|
||||||
|
|
||||||
Let's say you created a Product API with name, description and float fields.
|
|
||||||
|
|
||||||
## List entries
|
|
||||||
|
|
||||||
To retrieve the list of products, use `GET /your-content-type` route.
|
|
||||||
|
|
||||||
Generated APIs provides a handy way to filter and order queries. That way, ordering products by price is as easy as `GET http://localhost:1337/product?_order=price:asc`. For more information, read the [filters documentation](todo.md).
|
|
||||||
|
|
||||||
Here is an example using jQuery.
|
|
||||||
|
|
||||||
```js
|
|
||||||
$.ajax({
|
|
||||||
type: 'GET',
|
|
||||||
url: 'http://localhost:1337/product_order=price:asc', // Order by price.
|
|
||||||
done: function(products) {
|
|
||||||
console.log('Well done, here is the list of products: ', products);
|
|
||||||
},
|
|
||||||
fail: function(error) {
|
|
||||||
console.log('An error occurred:', error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Get a specific entry
|
|
||||||
|
|
||||||
If you want to get a specific entry, add the `id` of the wanted product at the end of the url.
|
|
||||||
|
|
||||||
```js
|
|
||||||
$.ajax({
|
|
||||||
type: 'GET',
|
|
||||||
url: 'http://localhost:1337/product/123', // Where `123` is the `id` of the product.
|
|
||||||
done: function(product) {
|
|
||||||
console.log('Well done, here is the product having the `id` 123: ', product);
|
|
||||||
},
|
|
||||||
fail: function(error) {
|
|
||||||
console.log('An error occurred:', error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Create data (POST)
|
|
||||||
|
|
||||||
Use the `POST` route to create a new entry.
|
|
||||||
|
|
||||||
jQuery example:
|
|
||||||
|
|
||||||
```js
|
|
||||||
$.ajax({
|
|
||||||
type: 'POST',
|
|
||||||
url: 'http://localhost:1337/product',
|
|
||||||
data: {
|
|
||||||
name: 'Cheese cake',
|
|
||||||
description: 'Chocolate cheese cake with ice cream',
|
|
||||||
price: 5
|
|
||||||
},
|
|
||||||
done: function(product) {
|
|
||||||
console.log('Congrats, your product has been successfully created: ', product); // Remember the product `id` for the next steps.
|
|
||||||
},
|
|
||||||
fail: function(error) {
|
|
||||||
console.log('An error occurred:', error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Update data (PUT)
|
|
||||||
|
|
||||||
Use the `PUT` route to update an existing entry.
|
|
||||||
|
|
||||||
jQuery example:
|
|
||||||
|
|
||||||
```js
|
|
||||||
$.ajax({
|
|
||||||
type: 'PUT',
|
|
||||||
url: 'http://localhost:1337/product/123', // Where `123` is the `id` of the product.
|
|
||||||
data: {
|
|
||||||
description: 'This is the new description'
|
|
||||||
},
|
|
||||||
done: function(product) {
|
|
||||||
console.log('Congrats, your product has been successfully updated: ', product.description);
|
|
||||||
},
|
|
||||||
fail: function(error) {
|
|
||||||
console.log('An error occurred:', error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Delete data (DELETE)
|
|
||||||
|
|
||||||
Use the `DELETE` route to delete an existing entry.
|
|
||||||
|
|
||||||
jQuery example:
|
|
||||||
|
|
||||||
```js
|
|
||||||
$.ajax({
|
|
||||||
type: 'DELETE',
|
|
||||||
url: 'http://localhost:1337/product/123', // Where `123` is the `id` of the product.
|
|
||||||
done: function(product) {
|
|
||||||
console.log('Congrats, your product has been successfully deleted: ', product);
|
|
||||||
},
|
|
||||||
fail: function(error) {
|
|
||||||
console.log('An error occurred:', error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
Congratulations! You successfully finished the Getting Started guide! Read the [documentation](../admin.md) to understand more advanced concepts.
|
|
||||||
|
|
||||||
Also, feel free to join the community thanks to the different channels listed in the [community page](/community): team members, contributors and developers will be happy to help you.
|
|
@ -1,17 +0,0 @@
|
|||||||
# Create your first project
|
|
||||||
|
|
||||||
After having [installed Strapi](installation.md), you are now ready to create your first project.
|
|
||||||
|
|
||||||
## Command Line
|
|
||||||
|
|
||||||
The first step is to **open your terminal** in the directory you want to create your application in. Then, type
|
|
||||||
|
|
||||||
```
|
|
||||||
$ strapi new my-project
|
|
||||||
```
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Files structure
|
|
||||||
|
|
||||||
This action creates a new folder named `my-project` with the entire [files structure](../API.md) of a Strapi application.
|
|
@ -1,45 +0,0 @@
|
|||||||
# Generate an API
|
|
||||||
|
|
||||||
There is two ways to create an API:
|
|
||||||
- Using the Content Type Builder plugin.
|
|
||||||
- Using the CLI.
|
|
||||||
|
|
||||||
## Content Type Builder
|
|
||||||
|
|
||||||
The easiest way is to use the Content Type Builder plugin: a powerful UI to help you to create an API within a few clicks.
|
|
||||||
|
|
||||||
To create your API using the Content Type Builder:
|
|
||||||
- Start your project and visit the admin panel at the following address: http://localhost:1337/admin/plugins/content-type-builder.
|
|
||||||
- Click on "Create Content Type", set `product` as name and submit the form.
|
|
||||||
- Then, click on "Add fields", add the following fields:
|
|
||||||
- A `string` field named `name`.
|
|
||||||
- A `text` field named `description`.
|
|
||||||
- A `float` field named `price`.
|
|
||||||
- Save.
|
|
||||||
|
|
||||||
That's it: your API is created!
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## CLI generator
|
|
||||||
|
|
||||||
You can also use the [CLI](CLI.md) and its powerful generators.
|
|
||||||
|
|
||||||
Type in your terminal the following command:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ strapi generate:api product name:string description:text price:float
|
|
||||||
```
|
|
||||||
|
|
||||||
Here are some explanations:
|
|
||||||
- `product` is the name of your Content Type.
|
|
||||||
- `name`, `description` and `price` are the attributes.
|
|
||||||
- `string`, `text` and `float` are the types of the attributes.
|
|
||||||
|
|
||||||
For more information, read the [CLI documentation](CLI.md).
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### Files structure (I think we should remove this part)
|
|
||||||
|
|
||||||
Whatever option you used to create your API, a new directory has been created in the `api` folder of your application which contains all the needed stuff for your `product` Content Type: API, routes, controller, service and model. Take a look at the [API structure documentation](API.md) for more informations.
|
|
@ -1,21 +0,0 @@
|
|||||||
# Manage data
|
|
||||||
|
|
||||||
Let's say you created a Product API with name, description and price fields.
|
|
||||||
|
|
||||||
In this section, we will discover the tool that Strapi provides to help you to manage your data.
|
|
||||||
|
|
||||||
## Content Manager
|
|
||||||
|
|
||||||
If you visit your admin panel at http://localhost:1337/admin, you can access to a plugin named Content Manager. This powerful interface is auto-generated according to your Content Types and lets you create, update and delete your data from UI.
|
|
||||||
|
|
||||||
### Create a product
|
|
||||||
|
|
||||||
Try to create a new product by clicking on "New entry". Give it a name, a description and a price. Submit the form. You can see the new product in the products list.
|
|
||||||
|
|
||||||
### Edit a product
|
|
||||||
|
|
||||||
From the list view, you can click on a product and edit its values.
|
|
||||||
|
|
||||||
### Delete a product
|
|
||||||
|
|
||||||
From the list view and the edit view, you can delete a product.
|
|
@ -1,17 +0,0 @@
|
|||||||
# Start your server
|
|
||||||
|
|
||||||
To launch your app, go in its directory and run the following command:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ strapi start
|
|
||||||
```
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Visit http://localhost:1337 to see it live!
|
|
||||||
|
|
||||||
You can also discover the admin panel at the following address: http://localhost:1337/admin.
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
Your server is up and running.
|
|
Loading…
x
Reference in New Issue
Block a user