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

feat: Support rowSelection.dirty #24718

Merged
merged 4 commits into from Jun 4, 2020
Merged
Show file tree
Hide file tree
Changes from 3 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
69 changes: 49 additions & 20 deletions components/table/__tests__/Table.rowSelection.test.js
Expand Up @@ -787,27 +787,56 @@ describe('Table.rowSelection', () => {
expect(onChange.mock.calls[0][1]).toEqual([expect.objectContaining({ name: 'bamboo' })]);
});

it('do not cache selected keys', () => {
const onChange = jest.fn();
const wrapper = mount(
<Table
dataSource={[{ name: 'light' }, { name: 'bamboo' }]}
rowSelection={{ onChange }}
rowKey="name"
/>,
);
describe('cache with selected keys', () => {
it('default not cache', () => {
const onChange = jest.fn();
const wrapper = mount(
<Table
dataSource={[{ name: 'light' }, { name: 'bamboo' }]}
rowSelection={{ onChange }}
rowKey="name"
/>,
);

wrapper
.find('tbody input')
.first()
.simulate('change', { target: { checked: true } });
expect(onChange).toHaveBeenCalledWith(['light'], [{ name: 'light' }]);
wrapper
.find('tbody input')
.first()
.simulate('change', { target: { checked: true } });
expect(onChange).toHaveBeenCalledWith(['light'], [{ name: 'light' }]);

wrapper.setProps({ dataSource: [{ name: 'bamboo' }] });
wrapper
.find('tbody input')
.first()
.simulate('change', { target: { checked: true } });
expect(onChange).toHaveBeenCalledWith(['bamboo'], [{ name: 'bamboo' }]);
wrapper.setProps({ dataSource: [{ name: 'bamboo' }] });
wrapper
.find('tbody input')
.first()
.simulate('change', { target: { checked: true } });
expect(onChange).toHaveBeenCalledWith(['bamboo'], [{ name: 'bamboo' }]);
});

it('cache with preserveKeys', () => {
const onChange = jest.fn();
const wrapper = mount(
<Table
dataSource={[{ name: 'light' }, { name: 'bamboo' }]}
rowSelection={{ onChange, preserveKeys: true }}
rowKey="name"
/>,
);

wrapper
.find('tbody input')
.first()
.simulate('change', { target: { checked: true } });
expect(onChange).toHaveBeenCalledWith(['light'], [{ name: 'light' }]);

wrapper.setProps({ dataSource: [{ name: 'bamboo' }] });
wrapper
.find('tbody input')
.first()
.simulate('change', { target: { checked: true } });
expect(onChange).toHaveBeenCalledWith(
['light', 'bamboo'],
[{ name: 'light' }, { name: 'bamboo' }],
);
});
});
});
4 changes: 4 additions & 0 deletions components/table/demo/ajax.md
Expand Up @@ -11,12 +11,16 @@ title:

另外,本例也展示了筛选排序功能如何交给服务端实现,列不需要指定具体的 `onFilter` 和 `sorter` 函数,而是在把筛选和排序的参数发到服务端来处理。

当使用 `rowSelection` 时,请设置 `rowSelection.preserveKeys` 属性以保留 `key`。

**注意,此示例使用 [模拟接口](https://randomuser.me),展示数据可能不准确,请打开网络面板查看请求。**

## en-US

This example shows how to fetch and present data from a remote server, and how to implement filtering and sorting in server side by sending related parameters to server.

Setting `rowSelection.preserveKeys` to keep the `key` when enable selection.

**Note, this example use [Mock API](https://randomuser.me) that you can look up in Network Console.**

```jsx
Expand Down
68 changes: 50 additions & 18 deletions components/table/hooks/useSelection.tsx
@@ -1,4 +1,5 @@
import * as React from 'react';
import { useState, useCallback, useMemo } from 'react';
import DownOutlined from '@ant-design/icons/DownOutlined';
import { INTERNAL_COL_DEFINE } from 'rc-table';
import { FixedType } from 'rc-table/lib/interface';
Expand Down Expand Up @@ -71,6 +72,7 @@ export default function useSelection<RecordType>(
config: UseSelectionConfig<RecordType>,
): [TransformColumns<RecordType>, Set<Key>] {
const {
preserveKeys,
selectedRowKeys,
getCheckboxProps,
onChange: onSelectionChange,
Expand Down Expand Up @@ -99,15 +101,19 @@ export default function useSelection<RecordType>(
getPopupContainer,
} = config;

const [innerSelectedKeys, setInnerSelectedKeys] = React.useState<Key[]>();
// ======================== Caches ========================
const preserveRecordsRef = React.useRef(new Map<Key, RecordType>());

// ========================= Keys =========================
const [innerSelectedKeys, setInnerSelectedKeys] = useState<Key[]>();
const mergedSelectedKeys = selectedRowKeys || innerSelectedKeys || EMPTY_LIST;
const mergedSelectedKeySet = React.useMemo(() => {
const mergedSelectedKeySet = useMemo(() => {
const keys = selectionType === 'radio' ? mergedSelectedKeys.slice(0, 1) : mergedSelectedKeys;
return new Set(keys);
}, [mergedSelectedKeys, selectionType]);

// Save last selected key to enable range selection
const [lastSelectedKey, setLastSelectedKey] = React.useState<Key | null>(null);
const [lastSelectedKey, setLastSelectedKey] = useState<Key | null>(null);

// Reset if rowSelection reset
React.useEffect(() => {
Expand All @@ -116,30 +122,55 @@ export default function useSelection<RecordType>(
}
}, [!!rowSelection]);

const setSelectedKeys = React.useCallback(
const setSelectedKeys = useCallback(
(keys: Key[]) => {
const availableKeys: Key[] = [];
const records: RecordType[] = [];

keys.forEach(key => {
const record = getRecordByKey(key);
if (record !== undefined) {
availableKeys.push(key);
records.push(record);
}
});
let availableKeys: Key[];
let records: RecordType[];

if (preserveKeys) {
// Keep key if mark as preserveKeys
const newCache = new Map<Key, RecordType>();
availableKeys = keys;
records = keys.map(key => {
let record = getRecordByKey(key);

if (!record && preserveRecordsRef.current.has(key)) {
record = preserveRecordsRef.current.get(key)!;
}

newCache.set(key, record);

return record;
});

// Refresh to new cache
preserveRecordsRef.current = newCache;
} else {
// Filter key which not exist in the `dataSource`
availableKeys = [];
records = [];

keys.forEach(key => {
const record = getRecordByKey(key);
if (record !== undefined) {
availableKeys.push(key);
records.push(record);
}
});
}

setInnerSelectedKeys(availableKeys);

if (onSelectionChange) {
onSelectionChange(availableKeys, records);
}
},
[setInnerSelectedKeys, getRecordByKey, onSelectionChange],
[setInnerSelectedKeys, getRecordByKey, onSelectionChange, preserveKeys],
);

// ====================== Selections ======================
// Trigger single `onSelect` event
const triggerSingleSelection = React.useCallback(
const triggerSingleSelection = useCallback(
(key: Key, selected: boolean, keys: Key[], event: Event) => {
if (onSelect) {
const rows = keys.map(k => getRecordByKey(k));
Expand All @@ -151,7 +182,7 @@ export default function useSelection<RecordType>(
[onSelect, getRecordByKey, setSelectedKeys],
);

const mergedSelections = React.useMemo<SelectionItem[] | null>(() => {
const mergedSelections = useMemo<SelectionItem[] | null>(() => {
if (!selections || hideSelectAll) {
return null;
}
Expand Down Expand Up @@ -202,7 +233,8 @@ export default function useSelection<RecordType>(
});
}, [selections, mergedSelectedKeySet, pageData, getRowKey]);

const transformColumns = React.useCallback(
// ======================= Columns ========================
const transformColumns = useCallback(
(columns: ColumnsType<RecordType>): ColumnsType<RecordType> => {
if (!rowSelection) {
return columns;
Expand Down
23 changes: 12 additions & 11 deletions components/table/index.en-US.md
Expand Up @@ -185,19 +185,20 @@ Properties for row selection.

| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| columnWidth | Set the width of the selection column | string\|number | `60px` | 4.0 |
| columnTitle | Set the title of the selection column | string\|React.ReactNode | - | 4.0 |
| fixed | Fixed selection column on the left | boolean | - | 4.0 |
| getCheckboxProps | Get Checkbox or Radio props | Function(record) | - | 4.0 |
| columnWidth | Set the width of the selection column | string\|number | `60px` | |
| columnTitle | Set the title of the selection column | string\|React.ReactNode | - | |
| fixed | Fixed selection column on the left | boolean | - | |
| getCheckboxProps | Get Checkbox or Radio props | Function(record) | - | |
| hideSelectAll | Hide the selectAll checkbox and custom selection | boolean | `false` | 4.3 |
| preserveKeys | Keep selection `key` even when it removed from `dataSource` | boolean | - | 4.4 |
| renderCell | Renderer of the table cell. Same as `render` in column | Function(checked, record, index, originNode) {} | - | 4.1 |
| selectedRowKeys | Controlled selected row keys | string\[]\|number[] | \[] | 4.0 |
| selections | Custom selection [config](#rowSelection), only displays default selections when set to `true` | object\[]\|boolean | - | 4.0 |
| type | `checkbox` or `radio` | `checkbox` \| `radio` | `checkbox` | 4.0 |
| onChange | Callback executed when selected rows change | Function(selectedRowKeys, selectedRows) | - | 4.0 |
| onSelect | Callback executed when select/deselect one row | Function(record, selected, selectedRows, nativeEvent) | - | 4.0 |
| onSelectAll | Callback executed when select/deselect all rows | Function(selected, selectedRows, changeRows) | - | 4.0 |
| onSelectInvert | Callback executed when row selection is inverted | Function(selectedRowKeys) | - | 4.0 |
| selectedRowKeys | Controlled selected row keys | string\[]\|number[] | \[] | |
| selections | Custom selection [config](#rowSelection), only displays default selections when set to `true` | object\[]\|boolean | - | |
| type | `checkbox` or `radio` | `checkbox` \| `radio` | `checkbox` | |
| onChange | Callback executed when selected rows change | Function(selectedRowKeys, selectedRows) | - | |
| onSelect | Callback executed when select/deselect one row | Function(record, selected, selectedRows, nativeEvent) | - | |
| onSelectAll | Callback executed when select/deselect all rows | Function(selected, selectedRows, changeRows) | - | |
| onSelectInvert | Callback executed when row selection is inverted | Function(selectedRowKeys) | - | |

### scroll

Expand Down
23 changes: 12 additions & 11 deletions components/table/index.zh-CN.md
Expand Up @@ -190,19 +190,20 @@ const columns = [

| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| columnWidth | 自定义列表选择框宽度 | string\|number | `60px` | 4.0 |
| columnTitle | 自定义列表选择框标题 | string\|React.ReactNode | - | 4.0 |
| fixed | 把选择框列固定在左边 | boolean | - | 4.0 |
| getCheckboxProps | 选择框的默认属性配置 | Function(record) | - | 4.0 |
| columnWidth | 自定义列表选择框宽度 | string\|number | `60px` | |
| columnTitle | 自定义列表选择框标题 | string\|React.ReactNode | - | |
| fixed | 把选择框列固定在左边 | boolean | - | |
| getCheckboxProps | 选择框的默认属性配置 | Function(record) | - | |
| hideSelectAll | 隐藏全选勾选框与自定义选择项 | boolean | false | 4.3 |
| preserveKeys | 当数据被删除时仍然保留选项的 `key` | boolean | - | 4.4 |
| renderCell | 渲染勾选框,用法与 Column 的 `render` 相同 | Function(checked, record, index, originNode) {} | - | 4.1 |
| selectedRowKeys | 指定选中项的 key 数组,需要和 onChange 进行配合 | string\[]\|number[] | \[] | 4.0 |
| selections | 自定义选择项 [配置项](#selection), 设为 `true` 时使用默认选择项 | object\[]\|boolean | true | 4.0 |
| type | 多选/单选,`checkbox` or `radio` | string | `checkbox` | 4.0 |
| onChange | 选中项发生变化时的回调 | Function(selectedRowKeys, selectedRows) | - | 4.0 |
| onSelect | 用户手动选择/取消选择某行的回调 | Function(record, selected, selectedRows, nativeEvent) | - | 4.0 |
| onSelectAll | 用户手动选择/取消选择所有行的回调 | Function(selected, selectedRows, changeRows) | - | 4.0 |
| onSelectInvert | 用户手动选择反选的回调 | Function(selectedRowKeys) | - | 4.0 |
| selectedRowKeys | 指定选中项的 key 数组,需要和 onChange 进行配合 | string\[]\|number[] | \[] | |
| selections | 自定义选择项 [配置项](#selection), 设为 `true` 时使用默认选择项 | object\[]\|boolean | true | |
| type | 多选/单选,`checkbox` or `radio` | string | `checkbox` | |
| onChange | 选中项发生变化时的回调 | Function(selectedRowKeys, selectedRows) | - | |
| onSelect | 用户手动选择/取消选择某行的回调 | Function(record, selected, selectedRows, nativeEvent) | - | |
| onSelectAll | 用户手动选择/取消选择所有行的回调 | Function(selected, selectedRows, changeRows) | - | |
| onSelectInvert | 用户手动选择反选的回调 | Function(selectedRowKeys) | - | |

### scroll

Expand Down
2 changes: 2 additions & 0 deletions components/table/interface.tsx
Expand Up @@ -125,6 +125,8 @@ export type SelectionSelectFn<T> = (
) => void;

export interface TableRowSelection<T> {
/** Keep the selection keys in list even the key not exist in `dataSource` anymore */
preserveKeys?: boolean;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

preserveSelectedRowKeys 吧。

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌

type?: RowSelectionType;
selectedRowKeys?: Key[];
onChange?: (selectedRowKeys: Key[], selectedRows: T[]) => void;
Expand Down