Skip to content

Commit

Permalink
Add a "Select all" button + make it customizable + document jsDoc
Browse files Browse the repository at this point in the history
  • Loading branch information
erwanMarmelab committed Nov 20, 2024
1 parent 2de6341 commit 972e9dd
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 7 deletions.
2 changes: 2 additions & 0 deletions packages/ra-core/src/controller/list/ListContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ import { ListControllerResult } from './useListController';
* @prop {Function} showFilter a callback to show one of the filters, e.g. showFilter('title', defaultValue)
* @prop {Function} hideFilter a callback to hide one of the filters, e.g. hideFilter('title')
* @prop {Array} selectedIds an array listing the ids of the selected rows, e.g. [123, 456]
* @prop {boolean} areAllSelected boolean to indicate if the list is already fully selected
* @prop {Function} onSelect callback to change the list of selected rows, e.g. onSelect([456, 789])
* @prop {Function} onSelectAll callback to select all the records, e.g. onSelectAll()
* @prop {Function} onToggleItem callback to toggle the selection of a given record based on its id, e.g. onToggleItem(456)
* @prop {Function} onUnselectItems callback to clear the selection, e.g. onUnselectItems();
* @prop {string} defaultTitle the translated title based on the resource, e.g. 'Posts'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ import { RaRecord } from '../../types';
* @prop {Function} showFilter a callback to show one of the filters, e.g. showFilter('title', defaultValue)
* @prop {Function} hideFilter a callback to hide one of the filters, e.g. hideFilter('title')
* @prop {Array} selectedIds an array listing the ids of the selected rows, e.g. [123, 456]
* @prop {boolean} areAllSelected boolean to indicate if the list is already fully selected
* @prop {Function} onSelect callback to change the list of selected rows, e.g. onSelect([456, 789])
* @prop {Function} onSelectAll callback to select all the records, e.g. onSelectAll()
* @prop {Function} onToggleItem callback to toggle the selection of a given record based on its id, e.g. onToggleItem(456)
* @prop {Function} onUnselectItems callback to clear the selection, e.g. onUnselectItems();
* @prop {string} defaultTitle the translated title based on the resource, e.g. 'Posts'
Expand Down Expand Up @@ -81,13 +83,15 @@ const extractListContextProps = <RecordType extends RaRecord = any>({
isLoading,
isPending,
onSelect,
onSelectAll,
onToggleItem,
onUnselectItems,
page,
perPage,
refetch,
resource,
selectedIds,
areAllSelected,
setFilters,
setPage,
setPerPage,
Expand All @@ -107,13 +111,15 @@ const extractListContextProps = <RecordType extends RaRecord = any>({
isLoading,
isPending,
onSelect,
onSelectAll,
onToggleItem,
onUnselectItems,
page,
perPage,
refetch,
resource,
selectedIds,
areAllSelected,
setFilters,
setPage,
setPerPage,
Expand Down
50 changes: 50 additions & 0 deletions packages/ra-core/src/controller/list/useListController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export const useListController = <RecordType extends RaRecord = any>(
queryOptions = {},
sort = defaultSort,
storeKey,
selectAllLimit = 250,
} = props;
const resource = useResourceContext(props);
const { meta, ...otherQueryOptions } = queryOptions;
Expand Down Expand Up @@ -168,6 +169,38 @@ export const useListController = <RecordType extends RaRecord = any>(
name: getResourceLabel(resource, 2),
});

const { data: allData } = useGetList<RecordType>(
resource,
{
pagination: {
page: 1,
perPage: selectAllLimit,
},
sort: { field: query.sort, order: query.order },
filter: { ...query.filter, ...filter },
meta,
},
{
enabled:
(!isPendingAuthenticated && !isPendingCanAccess) ||
disableAuthentication,
retry: false,
...otherQueryOptions,
}
);

const onSelectAll = () => {
console.log('onSelectAll', allData);
const allIds = allData?.map(({ id }) => id) || [];
selectionModifiers.select(allIds);
if (allIds.length === selectAllLimit) {
notify('ra.message.too_many_elements', {
messageArgs: { max: selectAllLimit },
type: 'warning',
});
}
};

return {
sort: currentSort,
data,
Expand All @@ -183,13 +216,15 @@ export const useListController = <RecordType extends RaRecord = any>(
isLoading,
isPending,
onSelect: selectionModifiers.select,
onSelectAll,
onToggleItem: selectionModifiers.toggle,
onUnselectItems: selectionModifiers.clearSelection,
page: query.page,
perPage: query.perPage,
refetch,
resource,
selectedIds,
areAllSelected: allData?.length !== selectedIds.length,
setFilters: queryModifiers.setFilters,
setPage: queryModifiers.setPage,
setPerPage: queryModifiers.setPerPage,
Expand Down Expand Up @@ -409,6 +444,19 @@ export interface ListControllerProps<RecordType extends RaRecord = any> {
* );
*/
storeKey?: string | false;

/**
* The number of items selected by the "SELECT ALL" button of the bulk actions toolbar.
*
* @see https://marmelab.com/react-admin/List.html#selectalllimit
* @example
* export const PostList = () => (
* <List selectAllLimit={500}>
* ...
* </List>
* );
*/
selectAllLimit?: number;
}

const defaultSort = {
Expand Down Expand Up @@ -475,13 +523,15 @@ export interface ListControllerBaseResult<RecordType extends RaRecord = any> {
filterValues: any;
hideFilter: (filterName: string) => void;
onSelect: (ids: RecordType['id'][]) => void;
onSelectAll: () => void;
onToggleItem: (id: RecordType['id']) => void;
onUnselectItems: () => void;
page: number;
perPage: number;
refetch: (() => void) | UseGetListHookValue<RecordType>['refetch'];
resource: string;
selectedIds: RecordType['id'][];
areAllSelected: boolean;
setFilters: (
filters: any,
displayedFilters?: any,
Expand Down
13 changes: 7 additions & 6 deletions packages/ra-language-english/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,11 @@ const englishMessages: TranslationMessages = {
},
message: {
about: 'About',
access_denied:
"You don't have the right permissions to access this page",
are_you_sure: 'Are you sure?',
authentication_error:
'The authentication server returned an error and your credentials could not be checked.',
auth_error:
'An error occurred while validating the authentication token.',
bulk_delete_content:
Expand All @@ -103,19 +107,16 @@ const englishMessages: TranslationMessages = {
delete_title: 'Delete %{name} #%{id}',
details: 'Details',
error: "A client error occurred and your request couldn't be completed.",

invalid_form: 'The form is not valid. Please check for errors',
loading: 'Please wait',
no: 'No',
not_found:
'Either you typed a wrong URL, or you followed a bad link.',
yes: 'Yes',
too_many_elements:
'Warning: There are too many elements to select them all. Only the first %{max} elements were selected.',
unsaved_changes:
"Some of your changes weren't saved. Are you sure you want to ignore them?",
access_denied:
"You don't have the right permissions to access this page",
authentication_error:
'The authentication server returned an error and your credentials could not be checked.',
yes: 'Yes',
},
navigation: {
clear_filters: 'Clear filters',
Expand Down
20 changes: 19 additions & 1 deletion packages/ra-ui-materialui/src/list/BulkActionsToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import CloseIcon from '@mui/icons-material/Close';
import { useTranslate, sanitizeListRestProps, useListContext } from 'ra-core';

import TopToolbar from '../layout/TopToolbar';
import { Button } from '../button';

export const BulkActionsToolbar = (props: BulkActionsToolbarProps) => {
const {
Expand All @@ -18,14 +19,23 @@ export const BulkActionsToolbar = (props: BulkActionsToolbarProps) => {
className,
...rest
} = props;
const { selectedIds = [], onUnselectItems } = useListContext();
const {
selectedIds = [],
onUnselectItems,
onSelectAll,
areAllSelected,
} = useListContext();

const translate = useTranslate();

const handleUnselectAllClick = useCallback(() => {
onUnselectItems();
}, [onUnselectItems]);

const handleSelectAll = useCallback(() => {
onSelectAll();
}, [onSelectAll]);

return (
<Root className={className}>
<Toolbar
Expand All @@ -52,6 +62,13 @@ export const BulkActionsToolbar = (props: BulkActionsToolbarProps) => {
smart_count: selectedIds.length,
})}
</Typography>
{areAllSelected && (
<Button
label={translate('ra.action.select_all')}
onClick={handleSelectAll}
sx={{ ml: 1 }}
/>
)}
</div>
<TopToolbar className={BulkActionsToolbarClasses.topToolbar}>
{children}
Expand All @@ -65,6 +82,7 @@ export interface BulkActionsToolbarProps {
children?: ReactNode;
label?: string;
className?: string;
selectAllLimit?: number;
}

const PREFIX = 'RaBulkActionsToolbar';
Expand Down
3 changes: 3 additions & 0 deletions packages/ra-ui-materialui/src/list/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { Loading } from '../layout';
* - perPage: Pagination Size
* - queryOptions
* - sort: Default Sort Field & Order
* - selectAllLimit: The number of items selected by the "SELECT ALL" button of the bulk actions toolbar
* - title
* - sx: CSS API
*
Expand Down Expand Up @@ -68,6 +69,7 @@ export const List = <RecordType extends RaRecord = any>({
resource,
sort,
storeKey,
selectAllLimit = 250,
...rest
}: ListProps<RecordType>): ReactElement => (
<ListBase<RecordType>
Expand All @@ -83,6 +85,7 @@ export const List = <RecordType extends RaRecord = any>({
resource={resource}
sort={sort}
storeKey={storeKey}
selectAllLimit={selectAllLimit}
>
<ListView<RecordType> {...rest} />
</ListBase>
Expand Down

0 comments on commit 972e9dd

Please sign in to comment.