Update summary and redo customize-admin file s structure

This commit is contained in:
cyril lopez 2017-10-11 13:11:15 +02:00
parent 52a582f3f5
commit ac100d1a27
18 changed files with 26 additions and 1292 deletions

View File

@ -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)

View File

@ -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

View File

@ -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.

View File

@ -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 |

View File

@ -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.

View File

@ -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. |

View File

@ -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;
```

View File

@ -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);
```

View File

@ -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);
}
```

View File

@ -1,60 +0,0 @@
# Component Button
Button library based on bootstrap classes
![Buttons img](../assets/buttons.png)
## 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;
```

View File

@ -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>
);
}
}
```

View File

@ -1,74 +0,0 @@
# PopUp Warning
PopUp warning library based on [reactstrap](https://reactstrap.github.io/components/modals/).
![PopUp warning img](../assets/popup-warning.png)
## 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;
```

View File

@ -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.

View File

@ -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
```
![Generate a Strapi project](../assets/new-project.png)
## Files structure
This action creates a new folder named `my-project` with the entire [files structure](../API.md) of a Strapi application.

View File

@ -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!
![Strapi Content Type Builder](../assets/content-type-builder.png)
## 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).
![Generate an API](../assets/generate-api.png)
### 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.

View File

@ -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.

View File

@ -1,17 +0,0 @@
# Start your server
To launch your app, go in its directory and run the following command:
```
$ strapi start
```
![Start Strapi](../assets/strapi-start.png)
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.