mirror of
https://github.com/strapi/strapi.git
synced 2025-08-31 20:33:03 +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)
|
||||
|
||||
### Guides - Advanced
|
||||
* [Admin panel](guides-advanced/customize-admin.md)
|
||||
* [Admin panel](advanced/customize-admin.md)
|
||||
* [Hooks](advanced/hooks.md)
|
||||
* [Logging](advanced/logging.md)
|
||||
* [Middlewares](advanced/middlewares.md)
|
||||
|
@ -7,26 +7,29 @@ See the [Contributing Guide](https://github.com/strapi/strapi/blob/master/.githu
|
||||
## Files 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.
|
||||
- `src/`: source code of the front-end part.
|
||||
- `app.js`: entry point of the React application.
|
||||
- `assets/`: contains necessary assets (images…).
|
||||
- `components/`: contains components used by the admin panel.
|
||||
- `containers/`: contains high level components.
|
||||
- `favicon.ico`: favicon displayed in the web browser.
|
||||
- `i18n.js`: logic for internationalization.
|
||||
- `index.html`: basic html file in which are injected necessary styles and scripts.
|
||||
- `reducers.js`: reducers logic.
|
||||
- `store.js` store logic.
|
||||
- `styles/`: contains the global styles. Specific styles are defined at the component level.
|
||||
- `translations/`: text messages for each supported languages.
|
||||
- `config/`:
|
||||
- `routes.json`: admin panel API routes.
|
||||
- `admin.json`: contains admin panel specific settings.
|
||||
- `controllers/`: admin panel API controllers.
|
||||
- `package.json`: contains admin panel dependencies list.
|
||||
- `services/`: admin panel API services.
|
||||
```
|
||||
/admin
|
||||
└─── admin
|
||||
| └─── build // Webpack generated build of the front-end
|
||||
| └─── src // Front-end directory
|
||||
| └─── app.js // Entry point of the Reacr application
|
||||
| └─── assets // Assets directory containing images,...
|
||||
| └─── components // Admin's React components directory
|
||||
| └─── containers // Admin's high level components directory
|
||||
| └─── favicon.ico // Favicon displayed in web browser
|
||||
| └─── i18n.js // Internalization logic
|
||||
| └─── index.html // Basic html file in which are injected necessary styles and scripts
|
||||
| └─── reducers.js // Redux reducers logic
|
||||
| └─── store.js // Redux store logic
|
||||
| └─── styles // Directory containing the global styles. Specific styles are defined at the component level
|
||||
| └─── translations // Directory containing text messages for each supported languages
|
||||
└─── config
|
||||
| └─── routes.json // Admin's API routes
|
||||
| └─── admin.json // Admin's specific settings
|
||||
└─── controllers // Admin's API controllers
|
||||
└─── services // Admin's API services
|
||||
└─── packages.json // Admin's npm dependencies
|
||||
```
|
||||
|
||||
## Customization
|
||||
|
@ -319,6 +319,8 @@ const shouldRenderCompo = (plugin) => new Promise((resolve, request) => {
|
||||
export default shouldRenderCompo;
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
## 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.
|
||||
|
@ -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