Multiple row selection (#11406)

This PR intends to solve issue #11404 (Multiple row selection in table component).

The Chinese documentation is missing from the PR. Could someone please add it ?
This commit is contained in:
Raphael Chauveau 2018-07-24 08:49:23 +02:00 committed by Wei Zhu
parent ecff4997d9
commit 08e83193f2
6 changed files with 107 additions and 21 deletions

View File

@ -35,7 +35,7 @@ export interface CheckboxChangeEvent {
target: CheckboxChangeEventTarget;
stopPropagation: () => void;
preventDefault: () => void;
nativeEvent: Event;
nativeEvent: MouseEvent;
}
export default class Checkbox extends React.Component<CheckboxProps, {}> {

View File

@ -38,5 +38,5 @@ export interface RadioChangeEvent {
target: RadioChangeEventTarget;
stopPropagation: () => void;
preventDefault: () => void;
nativeEvent: Event;
nativeEvent: MouseEvent;
}

View File

@ -124,6 +124,7 @@ export default class Table<T> extends React.Component<TableProps<T>, TableState<
// 减少状态
filters: this.getFiltersFromColumns(),
pagination: this.getDefaultPagination(props),
pivot: undefined,
};
this.CheckboxPropsCache = {};
@ -250,6 +251,11 @@ export default class Table<T> extends React.Component<TableProps<T>, TableState<
}
if (selectWay === 'onSelect' && rowSelection.onSelect) {
rowSelection.onSelect(record!, checked!, selectedRows, nativeEvent!);
} else if (selectWay === 'onSelectMulti' && rowSelection.onSelectMulti) {
const changeRows = data.filter(
(row, i) => changeRowKeys!.indexOf(this.getRecordKey(row, i)) >= 0,
);
rowSelection.onSelectMulti(checked!, selectedRows, changeRows);
} else if (selectWay === 'onSelectAll' && rowSelection.onSelectAll) {
const changeRows = data.filter(
(row, i) => changeRowKeys!.indexOf(this.getRecordKey(row, i)) >= 0,
@ -456,21 +462,67 @@ export default class Table<T> extends React.Component<TableProps<T>, TableState<
const defaultSelection = this.store.getState().selectionDirty ? [] : this.getDefaultSelection();
let selectedRowKeys = this.store.getState().selectedRowKeys.concat(defaultSelection);
let key = this.getRecordKey(record, rowIndex);
if (checked) {
selectedRowKeys.push(this.getRecordKey(record, rowIndex));
} else {
selectedRowKeys = selectedRowKeys.filter((i: string) => key !== i);
const pivot = this.state.pivot;
const rows = this.getFlatCurrentPageData();
let realIndex = rowIndex;
if (this.props.expandedRowRender) {
realIndex = rows.findIndex(row => this.getRecordKey(row, rowIndex) === key);
}
if (nativeEvent.shiftKey && pivot !== undefined && realIndex !== pivot) {
const changeRowKeys = [];
const direction = Math.sign(pivot - realIndex);
const dist = Math.abs(pivot - realIndex);
let step = 0;
while (step <= dist) {
const i = realIndex + (step * direction);
step += 1;
const row = rows[i];
const rowKey = this.getRecordKey(row, i);
const checkboxProps = this.getCheckboxPropsByItem(row, i);
if (!checkboxProps.disabled) {
if (selectedRowKeys.includes(rowKey)) {
if (!checked) {
selectedRowKeys = selectedRowKeys.filter((j: string) => rowKey !== j);
changeRowKeys.push(rowKey);
}
} else if (checked) {
selectedRowKeys.push(rowKey);
changeRowKeys.push(rowKey);
}
}
}
this.setState({ pivot: realIndex });
this.store.setState({
selectionDirty: true,
});
this.setSelectedRowKeys(selectedRowKeys, {
selectWay: 'onSelectMulti',
record,
checked,
changeRowKeys,
nativeEvent,
});
} else {
if (checked) {
selectedRowKeys.push(this.getRecordKey(record, realIndex));
} else {
selectedRowKeys = selectedRowKeys.filter((i: string) => key !== i);
}
this.setState({ pivot: realIndex });
this.store.setState({
selectionDirty: true,
});
this.setSelectedRowKeys(selectedRowKeys, {
selectWay: 'onSelect',
record,
checked,
changeRowKeys: void(0),
nativeEvent,
});
}
this.store.setState({
selectionDirty: true,
});
this.setSelectedRowKeys(selectedRowKeys, {
selectWay: 'onSelect',
record,
checked,
changeRowKeys: void(0),
nativeEvent,
});
}
handleRadioSelect = (record: T, rowIndex: number, e: RadioChangeEvent) => {
@ -599,11 +651,11 @@ export default class Table<T> extends React.Component<TableProps<T>, TableState<
renderSelectionBox = (type: RowSelectionType | undefined) => {
return (_: any, record: T, index: number) => {
let rowIndex = this.getRecordKey(record, index); // 从 1 开始
const rowKey = this.getRecordKey(record, index);
const props = this.getCheckboxPropsByItem(record, index);
const handleChange = (e: RadioChangeEvent | CheckboxChangeEvent) => {
type === 'radio' ? this.handleRadioSelect(record, rowIndex, e) :
this.handleSelect(record, rowIndex, e);
type === 'radio' ? this.handleRadioSelect(record, index, e) :
this.handleSelect(record, index, e);
};
return (
@ -611,7 +663,7 @@ export default class Table<T> extends React.Component<TableProps<T>, TableState<
<SelectionBox
type={type}
store={this.store}
rowIndex={rowIndex}
rowIndex={rowKey}
onChange={handleChange}
defaultSelection={this.getDefaultSelection()}
{...props}

View File

@ -162,6 +162,36 @@ describe('Table.rowSelection', () => {
expect(handleSelect.mock.calls[0][3].type).toBe('change');
});
it('fires selectMulti event', () => {
const handleSelectMulti = jest.fn();
const handleSelect = jest.fn();
const rowSelection = {
onSelect: handleSelect,
onSelectMulti: handleSelectMulti,
};
const wrapper = mount(createTable({ rowSelection }));
wrapper.find('input').at(1).simulate('change', {
target: { checked: true },
nativeEvent: { shiftKey: true },
});
expect(handleSelect).toBeCalled();
wrapper.find('input').at(3).simulate('change', {
target: { checked: true },
nativeEvent: { shiftKey: true },
});
expect(handleSelectMulti).toBeCalledWith(true,
[data[0], data[1], data[2]], [data[1], data[2]]);
wrapper.find('input').at(1).simulate('change', {
target: { checked: false },
nativeEvent: { shiftKey: true },
});
expect(handleSelectMulti).toBeCalledWith(false,
[], [data[0], data[1], data[2]]);
});
it('fires selectAll event', () => {
const handleSelectAll = jest.fn();
const rowSelection = {

View File

@ -162,6 +162,7 @@ Properties for row selection.
| 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) | - |
| onSelectMulti | Callback executed when multiple rows are selected/unselected | Function(selected, selectedRows, changeRows) | - |
| onSelectAll | Callback executed when select/deselect all rows | Function(selected, selectedRows, changeRows) | - |
| onSelectInvert | Callback executed when row selection is inverted | Function(selectedRows) | - |

View File

@ -62,7 +62,7 @@ export interface TableLocale {
export type RowSelectionType = 'checkbox' | 'radio';
export type SelectionSelectFn<T> = (record: T, selected: boolean, selectedRows: Object[], nativeEvent: Event) => any;
export type TableSelectWay = 'onSelect' | 'onSelectAll' | 'onSelectInvert';
export type TableSelectWay = 'onSelect' | 'onSelectMulti' | 'onSelectAll' | 'onSelectInvert';
export interface TableRowSelection<T> {
type?: RowSelectionType;
@ -70,12 +70,14 @@ export interface TableRowSelection<T> {
onChange?: (selectedRowKeys: string[] | number[], selectedRows: Object[]) => void;
getCheckboxProps?: (record: T) => Object;
onSelect?: SelectionSelectFn<T>;
onSelectMulti?: (selected: boolean, selectedRows: Object[], changeRows: Object[]) => void;
onSelectAll?: (selected: boolean, selectedRows: Object[], changeRows: Object[]) => void;
onSelectInvert?: (selectedRows: Object[]) => void;
selections?: SelectionItem[] | boolean;
hideDefaultSelections?: boolean;
fixed?: boolean;
columnWidth?: string | number;
selectWay?: TableSelectWay;
}
export type SortOrder = 'descend' | 'ascend';
export interface SorterResult<T> {
@ -138,6 +140,7 @@ export interface TableState<T> {
filters: TableStateFilters;
sortColumn: ColumnProps<T> | null;
sortOrder?: SortOrder;
pivot?: number;
}
export type SelectionItemSelectFn = (key: string[]) => any;