Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFR] Add Reference many input #597

Merged
merged 19 commits into from May 22, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
188 changes: 187 additions & 1 deletion docs/Inputs.md
Expand Up @@ -555,6 +555,111 @@ The enclosed component may further filter results (that's the case, for instance
</ReferenceInput>
```

## `<ReferenceArrayInput>`

Use `<ReferenceArrayInput>` to edit an array of reference values, i.e. to let users choose a list of values (usually foreign keys) from another REST endpoint.

`<ReferenceArrayInput>` fetches the related resources (using the `CRUD_GET_MANY` REST method) as well as possible resources (using the
`CRUD_GET_MATCHING` REST method) in the reference endpoint.

For instance, if the post object has many tags, a post resource may look like:

```js
{
id: 1234,
tag_ids: [1, 23, 4]
}
```

Then `<ReferenceArrayInput>` would fetch a list of tag resources from these two calls:

```
http://myapi.com/tags?id=[1,23,4]
http://myapi.com/tags?page=1&perPage=25
```

Once it receives the deduplicated reference resources, this component delegates rendering to a subcomponent, to which it passes the possible choices as the `choices` attribute.

This means you can use `<ReferenceArrayInput>` with [`<SelectArrayInput>`](#selectarrayinput), or with the component of your choice, provided it supports the `choices` attribute.

The component expects a `source` and a `reference` attributes. For instance, to make the `tag_ids` for a `post` editable:

```js
import { ReferenceArrayInput, SelectArrayInput } from 'admin-on-rest'

<ReferenceArrayInput source="tag_ids" reference="tags">
<SelectArrayInput optionText="name" />
</ReferenceArrayInput>
```

![SelectArrayInput](./img/select-array-input.gif)

**Note**: You **must** add a `<Resource>` for the reference resource - admin-on-rest needs it to fetch the reference data. You can omit the list prop in this reference if you want to hide it in the sidebar menu.

```js
<Admin restClient={myRestClient}>
<Resource name="posts" list={PostList} edit={PostEdit} />
<Resource name="tags" />
</Admin>
```

Set the `allowEmpty` prop when the empty value is allowed.

```js
import { ReferenceArrayInput, SelectArrayInput } from 'admin-on-rest'

<ReferenceArrayInput source="tag_ids" reference="tags" allowEmpty>
<SelectArrayInput optionText="name" />
</ReferenceArrayInput>
```

**Tip**: `allowEmpty` is set by default for all Input components children of the `<Filter>` component

You can tweak how this component fetches the possible values using the `perPage`, `sort`, and `filter` props.

{% raw %}
```js
// by default, fetches only the first 25 values. You can extend this limit
// by setting the `perPage` prop.
<ReferenceArrayInput
source="tag_ids"
reference="tags"
perPage={100}>
<SelectArrayInput optionText="name" />
</ReferenceArrayInput>

// by default, orders the possible values by id desc. You can change this order
// by setting the `sort` prop (an object with `field` and `order` properties).
<ReferenceArrayInput
source="tag_ids"
reference="tags"
sort={{ field: 'title', order: 'ASC' }}>
<SelectArrayInput optionText="name" />
</ReferenceArrayInput>

// you can filter the query used to populate the possible values. Use the
// `filter` prop for that.
<ReferenceArrayInput
source="tag_ids"
reference="tags"
filter={{ is_published: true }}>
<SelectArrayInput optionText="name" />
</ReferenceArrayInput>
```
{% endraw %}

The enclosed component may further filter results (that's the case, for instance, for `<SelectArrayInput>`). `ReferenceArrayInput` passes a `setFilter` function as prop to its child component. It uses the value to create a filter for the query - by default `{ q: [searchText] }`. You can customize the mapping
`searchText => searchQuery` by setting a custom `filterToQuery` function prop:

```js
<ReferenceArrayInput
source="tag_ids"
reference="tags"
filterToQuery={searchText => ({ name: searchText })}>
<SelectArrayInput optionText="name" />
</ReferenceArrayInput>
```

## `<RichTextInput>`

`<RichTextInput>` is the ideal component if you want to allow your users to edit some HTML contents. It
Expand Down Expand Up @@ -649,12 +754,14 @@ const choices = [
];
```

However, in some cases (e.g. inside a `<ReferenceInput>`), you may not want the choice to be translated. In that case, set the `translateChoice` prop to false.
However, in some cases, you may not want the choice to be translated. In that case, set the `translateChoice` prop to false.

```jsx
<SelectInput source="gender" choices={choices} translateChoice={false}/>
```

Note that `translateChoice` is set to false when `<SelectInput>` is a child of `<ReferenceInput>`.

Lastly, use the `options` attribute if you want to override any of Material UI's `<SelectField>` attributes:

{% raw %}
Expand All @@ -679,6 +786,85 @@ import { SelectInput, ReferenceInput } from 'admin-on-rest'

If, instead of showing choices as a dropdown list, you prefer to display them as a list of radio buttons, try the [`<RadioButtonGroupInput>`](#radiobuttongroupinput). And if the list is too big, prefer the [`<AutocompleteInput>`](#autocompleteinput).

## `<SelectArrayInput>`

To let users choose several values in a list using a dropdown, use `<SelectArrayInput>`. It renders using [material-ui-chip-input](https://github.com/TeamWertarbyte/material-ui-chip-input). Set the `choices` attribute to determine the options (with `id`, `name` tuples):

```js
import { SelectArrayInput } from 'admin-on-rest';

<SelectArrayInput label="Tags" source="categories" choices={[
{ id: 'music', name: 'Music' },
{ id: 'photography', name: 'Photo' },
{ id: 'programming', name: 'Code' },
{ id: 'tech', name: 'Technology' },
{ id: 'sport', name: 'Sport' },
]} />
```

![SelectArrayInput](./img/select-array-input.gif)

You can also customize the properties to use for the option name and value,
thanks to the `optionText` and `optionValue` attributes.

```js
const choices = [
{ _id: '1', name: 'Book', plural_name: 'Books' },
{ _id: '2', name: 'Video', plural_name: 'Videos' },
{ _id: '3', name: 'Audio', plural_name: 'Audios' },
];
<SelectArrayInput source="categories" choices={choices} optionText="plural_name" optionValue="_id" />
```

`optionText` also accepts a function, so you can shape the option text at will:

```js
const choices = [
{ id: '1', name: 'Book', quantity: 23 },
{ id: '2', name: 'Video', quantity: 56 },
{ id: '3', name: 'Audio', quantity: 12 },
];
const optionRenderer = choice => `${choice.name} (${choice.quantity})`;
<SelectArrayInput source="categories" choices={choices} optionText={optionRenderer} />
```

The choices are translated by default, so you can use translation identifiers as choices:

```js
const choices = [
{ id: 'books', name: 'myroot.category.books' },
{ id: 'sport', name: 'myroot.category.sport' },
];
```

However, in some cases, you may not want the choice to be translated. In that case, set the `translateChoice` prop to false.

```js
<SelectArrayInput source="gender" choices={choices} translateChoice={false}/>
```

Note that `translateChoice` is set to false when `<SelectArrayInput>` is a child of `<ReferenceArrayInput>`.

Lastly, use the `options` attribute if you want to override any of the `<ChipInput>` attributes:

{% raw %}
```js
<SelectArrayInput source="category" options={{ fullWidth: true }} />
```
{% endraw %}

Refer to [the ChipInput documentation](https://github.com/TeamWertarbyte/material-ui-chip-input) for more details.

**Tip**: If you want to populate the `choices` attribute with a list of related records, you should decorate `<SelectArrayInput>` with [`<ReferenceArrayInput>`](#referencearrayinput), and leave the `choices` empty:

```js
import { SelectArrayInput, ReferenceArrayInput } from 'admin-on-rest'

<ReferenceArrayInput source="tag_ids" reference="tags">
<SelectArrayInput optionText="name" />
</ReferenceArrayInput>
```

## `<TextInput>`

`<TextInput>` is the most common input. It is used for texts, emails, URL or passwords. In translates to an HTML `<input>` tag.
Expand Down
2 changes: 2 additions & 0 deletions docs/Reference.md
Expand Up @@ -46,6 +46,7 @@ title: "Reference"
* [`<NumberInput>`](./Inputs.html#numberinput)
* [`<Pagination>`](./List.html#pagination)
* [`<RadioButtonGroupInput>`](./Inputs.html#radiobuttongroupinput)
* [`<ReferenceArrayInput>`](./Inputs.html#referencearrayinput)
* [`<ReferenceField>`](./Fields.html#referencefield)
* [`<ReferenceInput>`](./Inputs.html#referenceinput)
* [`<ReferenceManyField>`](./Fields.html#referencemanyfield)
Expand All @@ -55,6 +56,7 @@ title: "Reference"
* [`<RichTextField>`](./Fields.html#richtextfield)
* [`<RichTextInput>`](./Inputs.html#richtextinput)
* `<SaveButton>`
* [`<SelectArrayInput>`](./Inputs.html#selectarrayinput)
* [`<SelectInput>`](./Inputs.html#selectinput)
* `<Show>`
* `<ShowButton>`
Expand Down
6 changes: 6 additions & 0 deletions docs/_layouts/default.html
Expand Up @@ -254,12 +254,18 @@
<li class="chapter">
<a href="#referenceinput"><code>&lt;ReferenceInput&gt;</code></a>
</li>
<li class="chapter">
<a href="#referencearrayinput"><code>&lt;ReferenceArrayInput&gt;</code></a>
</li>
<li class="chapter">
<a href="#richtextinput"><code>&lt;RichTextInput&gt;</code></a>
</li>
<li class="chapter">
<a href="#selectinput"><code>&lt;SelectInput&gt;</code></a>
</li>
<li class="chapter">
<a href="#selectarrayinput"><code>&lt;SelectArrayInput&gt;</code></a>
</li>
<li class="chapter">
<a href="#textinput"><code>&lt;TextInput&gt;</code></a>
</li>
Expand Down
Binary file added docs/img/select-array-input.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions example/i18n/en.js
Expand Up @@ -16,6 +16,7 @@ export const messages = {
pictures: 'Related Pictures',
published_at: 'Published at',
teaser: 'Teaser',
tags: 'Tags',
title: 'Title',
views: 'Views',
},
Expand Down
1 change: 1 addition & 0 deletions example/i18n/fr.js
Expand Up @@ -15,6 +15,7 @@ export const messages = {
pictures: 'Photos associées',
published_at: 'Publié le',
teaser: 'Description',
tags: 'Catégories',
title: 'Titre',
views: 'Vues',
},
Expand Down
6 changes: 5 additions & 1 deletion example/posts.js
Expand Up @@ -21,8 +21,10 @@ import {
NumberInput,
ReferenceArrayField,
ReferenceManyField,
ReferenceArrayInput,
Responsive,
RichTextField,
SelectArrayInput,
SelectField,
SelectInput,
Show,
Expand Down Expand Up @@ -139,7 +141,9 @@ export const PostEdit = ({ ...props }) => (
<RichTextInput source="body" label="" validate={required} addLabel={false} />
</FormTab>
<FormTab label="post.form.miscellaneous">
<TextInput source="password" type="password" />
<ReferenceArrayInput source="tags" reference="tags" allowEmpty>
<SelectArrayInput optionText="name" options={{ fullWidth: true }} />
</ReferenceArrayInput>
<DateInput source="published_at" />
<SelectInput source="category" choices={[
{ name: 'Tech', id: 'tech' },
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -61,6 +61,7 @@
"lodash.get": "~4.4.2",
"lodash.set": "~4.3.2",
"material-ui": "~0.17.4",
"material-ui-chip-input": "~0.13.5",
"node-polyglot": "2.2.2",
"prop-types": "~15.5.7",
"query-string": "~4.3.2",
Expand Down