Skip to content

Commit

Permalink
Merge pull request #8492 from marmelab/count-components
Browse files Browse the repository at this point in the history
Add Count and ReferenceManyCount components
  • Loading branch information
djhi authored Dec 13, 2022
2 parents e54d433 + 743e250 commit d7c2db3
Show file tree
Hide file tree
Showing 19 changed files with 955 additions and 14 deletions.
2 changes: 1 addition & 1 deletion cypress/support/ListPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default url => ({
previousPage: "button[aria-label='Go to previous page']",
pageNumber: n => `button[aria-label='Go to page ${n}']`,
recordRows: '.datagrid-body tr',
viewsColumn: '.datagrid-body tr td:nth-child(7)',
viewsColumn: '.datagrid-body tr td:nth-child(8)',
datagridHeaders: 'th',
sortBy: name => `th span[data-field="${name}"]`,
svg: (name, criteria = '') =>
Expand Down
170 changes: 170 additions & 0 deletions docs/Count.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
---
layout: default
title: "The Count Component"
---

# `<Count>`

When you need to render the number of records of a given resource, use the `<Count>` component. It calls `dataProvider.getList()` with the `pagination` parameter set to retrieve no data - only the total number of records.

![Count](./img/count.webp)

## Usage

The most basic usage is to count the number of records in the current resource. For example, to count the number of posts:

```jsx
import { Count } from 'react-admin';

const PostCount = () => <Count />;
```

`<Count>` is usually used inside custom Menus, to display the number of records in each category. This takes advantage of [the `filter` prop](#filter) to count the number of records matching a given filter. For example, to build a menu for the various statuses of tickets in a Helpdesk:

{% raw %}
```jsx
import { Count, useStore } from 'react-admin';
import { MenuList, MenuItem, ListItemText } from '@mui/material';
import { isEqual } from 'lodash';

const TicketListAside = () => {
const [statusFilter, setStatusFilter] = useStore("statusMenu", { status: 'open' });
return (
<MenuList>
<MenuItem
onClick={() => setStatusFilter({ status: 'open' })}
selected={isEqual(statusFilter, { status: 'open' })}
>
<ListItemText>Open</ListItemText>
<Count filter={{ status: 'open' }} />
</MenuItem>
<MenuItem
onClick={() => setStatusFilter({ status: 'pending' })}
selected={isEqual(statusFilter, { status: 'pending' })}
>
<ListItemText>Pending</ListItemText>
<Count filter={{ status: 'pending' }} />
</MenuItem>
<MenuItem
onClick={() => setStatusFilter({ status: 'closed' })}
selected={isEqual(statusFilter, { status: 'closed' })}
>
<ListItemText>Closed</ListItemText>
<Count filter={{ status: 'closed' }} />
</MenuItem>
<MenuItem
onClick={() => setStatusFilter({})}
selected={isEqual(statusFilter, {})}
>
<ListItemText>All</ListItemText>
<Count filter={{}} />
</MenuItem>
</MenuList>
);
};
```
{% endraw %}


## Props

| Prop | Required | Type | Default | Description |
| ---------- | -------- | ------ | ------- | ----------------------------------------------------------------------- |
| `filter` | Optional | Object | - | Filter to apply to the query. |
| `link` | Optional | bool | `false` | If true, the count is wrapped in a `<Link>` to the list view. |
| `resource` | Optional | string | - | Resource to count. Default to the current `ResourceContext` |
| `timeout` | Optional | number | 1000 | Number of milliseconds to wait before displaying the loading indicator. |

Additional props are passed to [the underlying MUI `<Typography>` element](https://mui.com/material-ui/api/typography/).

## `filter`

If you want to count the number of records matching a given filter, pass it as the `filter` prop. For example, to count the number of posts already published:

{% raw %}
```jsx
<Count resource="posts" filter={{ is_published: true }} />;
```
{% endraw %}

## `link`

If you want to wrap the count in a `<Link>` to the list view, pass `true` to the `link` prop.
```jsx
<Count link />
```

When used in conjunction to the `filter` prop, the link will point to the list view with the filter applied.

{% raw %}
```jsx
<Count link filter={{ is_published: true }} link />
```
{% endraw %}

## `resource`

By default, the `<Count>` component uses the current `ResourceContext`, so you don't need to pass the `resource` prop to count the number of records in the current Resource.

```jsx
<Count />
```

If you want to count a different resource, pass it as the `resource` prop.

```jsx
<Count resource="comments" />
```

## `timeout`

The `<Count>` component displays a loading indicator after 1 second. This is useful to avoid displaying a loading indicator when the count is retrieved in a few milliseconds. You can change this delay by passing a `timeout` prop.

```jsx
<Count timeout={500} />
```

## Counting Related Records

If you need to count the number of records related to the current one via a one-to-many relationship, use [the `<ReferenceManyCount>` component](./ReferenceManyCount.md) instead.

![ReferenceManyCount](./img/ReferenceManyCount.webp)

```jsx
import {
ChipField
Datagrid,
DateField,
List,
NumberField,
ReferenceArrayField,
ReferenceManyCount,
SingleFieldList,
TextField,
} from 'react-admin';

export const PostList = () => (
<List>
<Datagrid>
<TextField source="id" />
<TextField source="title" />
<DateField source="published_at" sortByOrder="DESC" />
<ReferenceManyCount
label="Comments"
reference="comments"
target="post_id"
/>
<NumberField source="views" sortByOrder="DESC" />
<ReferenceArrayField
label="Tags"
reference="tags"
source="tags"
>
<SingleFieldList>
<ChipField source="name.en" size="small" />
</SingleFieldList>
</ReferenceArrayField>
</Datagrid>
</List>
)
```
2 changes: 2 additions & 0 deletions docs/Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ title: "Reference"
* [`<CompleteCalendar>`](https://marmelab.com/ra-enterprise/modules/ra-calendar#completecalendar)<img class="icon" src="./img/premium.svg" />
* [`<Confirm>`](./Confirm.md)
* [`<ContainerLayout>`](./ContainerLayout.md)<img class="icon" src="./img/premium.svg" />
* [`<Count>`](./Count.md)
* [`<Create>`](./Create.md)
* `<CreateActions>`
* [`<CreateButton>`](./Buttons.md#createbutton)
Expand Down Expand Up @@ -106,6 +107,7 @@ title: "Reference"
* [`<ReferenceArrayInput>`](./ReferenceArrayInput.md)
* [`<ReferenceField>`](./ReferenceField.md)
* [`<ReferenceInput>`](./ReferenceInput.md)
* [`<ReferenceManyCount>`](./ReferenceManyCount.md)
* [`<ReferenceManyField>`](./ReferenceManyField.md)
* [`<ReferenceManyInput>`](./ReferenceManyInput.md)<img class="icon" src="./img/premium.svg" />
* [`<ReferenceManyToManyField>`](https://marmelab.com/ra-enterprise/modules/ra-relationships#referencemanytomanyfield)<img class="icon" src="./img/premium.svg" />
Expand Down
166 changes: 166 additions & 0 deletions docs/ReferenceManyCount.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
---
layout: default
title: "The ReferenceManyCount Component"
---

# `<ReferenceManyCount>`

When you need to render the number of records related to another record via a one-to-many relationship (e.g. the number of comments related to a post), use the `<ReferenceManyCount>` component. It calls `dataProvider.getManyReference()` with the `pagination` parameter set to retrieve no data - only the total number of records.

![ReferenceManyCount](./img/reference_many_count.webp)

## Usage

Use `<ReferenceManyCount>` as a regular Field in a `<Datagrid>` or `<SimpleShowLayout>` - or anywhere inside a [`RecordContext`](./useRecordContext.md). You must set the `reference` and `target` props to match the relationship:

- `reference` is the name of the related resource to fetch (e.g. `comments`)
- `target` is the name of the field in the related resource that points to the current resource (e.g. `post_id`)

```jsx
import {
ChipField
Datagrid,
DateField,
List,
NumberField,
ReferenceArrayField,
ReferenceManyCount,
SingleFieldList,
TextField,
} from 'react-admin';

export const PostList = () => (
<List>
<Datagrid>
<TextField source="id" />
<TextField source="title" />
<DateField source="published_at" sortByOrder="DESC" />
<ReferenceManyCount
label="Nb comments"
reference="comments"
target="post_id"
link
/>
<NumberField source="views" sortByOrder="DESC" />
<ReferenceArrayField
label="Tags"
reference="tags"
source="tags"
>
<SingleFieldList>
<ChipField source="name.en" size="small" />
</SingleFieldList>
</ReferenceArrayField>
</Datagrid>
</List>
)
```

**Tip**: If you need to count all the records of a given resource, use [the `<Count>` component](./Count.md) instead.

## Props

| Prop | Required | Type | Default | Description |
| ----------- | -------- | ------ | ------- | ------------------------------------------------------------------------- |
| `reference` | Required | string | - | Name of the related resource to fetch (e.g. `comments`) |
| `target` | Required | string | - | Name of the field in the related resource that points to the current one. |
| `filter` | Optional | Object | - | Filter to apply to the query. |
| `link` | Optional | bool | `false` | If true, the count is wrapped in a `<Link>` to the filtered list view. |
| `resource` | Optional | string | - | Resource to count. Default to the current `ResourceContext` |
| `timeout` | Optional | number | 1000 | Number of milliseconds to wait before displaying the loading indicator. |

`<ReferenceManyCount>` also accepts the [common field props](./Fields.md#common-field-props).

Additional props are passed to [the underlying MUI `<Typography>` element](https://mui.com/material-ui/api/typography/).

## `filter`

If you want to count the number of records matching a given filter, pass it as the `filter` prop. For example, to count the number of comments already published:

{% raw %}
```jsx
<ReferenceManyCount
label="Comments"
reference="comments"
target="post_id"
filter={{ is_published: true }}
/>
```
{% endraw %}

## `link`

If you want to wrap the count in a `<Link>` to the list view filtered for the current record, pass `true` to the `link` prop.

```jsx
<ReferenceManyCount
label="Comments"
reference="comments"
target="post_id"
link
/>
```

When used in conjunction to the `filter` prop, the link will point to the list view with the filter applied.

{% raw %}
```jsx
<ReferenceManyCount
label="Comments"
reference="comments"
target="post_id"
link
filter={{ is_published: true }}
/>
```
{% endraw %}

## `reference`

The `reference` prop is required and must be the name of the related resource to fetch. For instance, to fetch the number of comments related to the current post:

```jsx
<ReferenceManyCount
label="Comments"
reference="comments"
target="post_id"
/>
```

## `resource`

By default, the `<ReferenceManyCount>` component uses the current `ResourceContext`, so you don't need to pass the `resource` prop to count the number of records in the current Resource. If you want to count a different resource, pass it as the `resource` prop.

```jsx
<ReferenceManyCount
label="Comments"
reference="comments"
target="post_id"
resource="posts"
/>
```

## `target`

The `target` prop is required and must be the name of the field in the related resource that points to the current one. For instance, when fetching the number of comments related to the current post, if a comment relates to a post via a `post_id` foreign key, you must set the `target` prop to `post_id`:

```jsx
<ReferenceManyCount
label="Comments"
reference="comments"
target="post_id"
/>
```

## `timeout`

The `<ReferenceManyCount>` component displays a loading indicator after 1 second. This is useful to avoid displaying a loading indicator when the count is retrieved in a few milliseconds. You can change this delay by passing a `timeout` prop.

```jsx
<ReferenceManyCount
label="Comments"
reference="comments"
target="post_id"
timeout={500}
/>
```
Binary file added docs/img/count.webp
Binary file not shown.
Binary file added docs/img/reference_many_count.webp
Binary file not shown.
2 changes: 2 additions & 0 deletions docs/navigation.html
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
<li {% if page.path == 'Pagination.md' %} class="active" {% endif %}><a class="nav-link" href="./Pagination.html"><code>&lt;Pagination&gt;</code></a></li>
<li {% if page.path == 'SortButton.md' %} class="active" {% endif %}><a class="nav-link" href="./SortButton.html"><code>&lt;SortButton&gt;</code></a></li>
<li {% if page.path == 'SelectColumnsButton.md' %} class="active" {% endif %}><a class="nav-link" href="./SelectColumnsButton.html"><code>&lt;SelectColumnsButton&gt;</code></a></li>
<li {% if page.path == 'Count.md' %} class="active" {% endif %}><a class="nav-link" href="./Count.html"><code>&lt;Count&gt;</code></a></li>
<li {% if page.path == 'useList.md' %} class="active" {% endif %}><a class="nav-link" href="./useList.html"><code>useList</code></a></li>
<li {% if page.path == 'useListContext.md' %} class="active" {% endif %}><a class="nav-link" href="./useListContext.html"><code>useListContext</code></a></li>
<li {% if page.path == 'useListController.md' %} class="active" {% endif %}><a class="nav-link" href="./useListController.html"><code>useListController</code></a></li>
Expand Down Expand Up @@ -149,6 +150,7 @@
<li {% if page.path == 'ReferenceField.md' %} class="active" {% endif %}><a class="nav-link" href="./ReferenceField.html"><code>&lt;ReferenceField&gt;</code></a></li>
<li {% if page.path == 'ReferenceArrayField.md' %} class="active" {% endif %}><a class="nav-link" href="./ReferenceArrayField.html"><code>&lt;ReferenceArrayField&gt;</code></a></li>
<li {% if page.path == 'ReferenceManyField.md' %} class="active" {% endif %}><a class="nav-link" href="./ReferenceManyField.html"><code>&lt;ReferenceManyField&gt;</code></a></li>
<li {% if page.path == 'ReferenceManyCount.md' %} class="active" {% endif %}><a class="nav-link" href="./ReferenceManyCount.html"><code>&lt;ReferenceManyCount&gt;</code></a></li>
<li {% if page.path == 'ReferenceManyToManyField.md' %} class="active" {% endif %}><a class="nav-link" href="./ReferenceManyToManyField.html"><code>&lt;ReferenceManyToManyField&gt;</code><img class="premium" src="./img/premium.svg" /></a></li>
<li {% if page.path == 'ReferenceOneField.md' %} class="active" {% endif %}><a class="nav-link" href="./ReferenceOneField.html"><code>&lt;ReferenceOneField&gt;</code></a></li>
<li {% if page.path == 'RichTextField.md' %} class="active" {% endif %}><a class="nav-link" href="./RichTextField.html"><code>&lt;RichTextField&gt;</code></a></li>
Expand Down
1 change: 1 addition & 0 deletions examples/simple/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const messages = {
commentable: 'Commentable',
notifications: 'Notifications recipients',
nb_view: 'Nb views',
nb_comments: 'Nb comments',
password: 'Password (if protected post)',
pictures: 'Related Pictures',
},
Expand Down
1 change: 1 addition & 0 deletions examples/simple/src/i18n/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default {
created_at: 'Créé le',
notifications: 'Destinataires de notifications',
nb_view: 'Nb de vues',
nb_comments: 'Nb de commentaires',
password: 'Mot de passe (si protégé)',
pictures: 'Photos associées',
'pictures.url': 'URL',
Expand Down
Loading

0 comments on commit d7c2db3

Please sign in to comment.