* implement selection hook

* add story for selection hooks

* add tests for selection hooks

* add missing test for store.setSelectedRowKeys(array)

* patch docs for selection hooks
This commit is contained in:
Allen 2017-10-26 03:49:35 -05:00 committed by GitHub
parent cf142d3b39
commit 974f129aad
13 changed files with 236 additions and 27 deletions

View File

@ -15,6 +15,8 @@ The following are available properties in `selectRow`:
* [nonSelectable)](#nonSelectable)
* [clickToSelect)](#clickToSelect)
* [clickToEdit](#clickToEdit)
* [onSelect](#onSelect)
* [onSelectAll](#onSelectAll)
#### Optional
@ -144,4 +146,29 @@ const selectRow = {
clickToSelect: true
clickToEdit: true
};
```
```
# <a name='onSelect'>selectRow.onSelect - [Function]</a>
This callback function will be called when a row is select/unselect and pass following three arguments:
`row`, `isSelect` and `rowIndex`.
```js
const selectRow = {
mode: 'checkbox',
onSelect: (row, isSelect, rowIndex) => {
// ...
}
};
```
# <a name='onSelectAll'>selectRow.onSelectAll - [Function]</a>
This callback function will be called when select/unselect all and it only work when you configure [`selectRow.mode`](#mode) as `checkbox`.
```js
const selectRow = {
mode: 'checkbox',
onSelectAll: (isSelect, results) => {
// ...
}
};
```

View File

@ -0,0 +1,66 @@
/* eslint no-console: 0 */
import React from 'react';
import BootstrapTable from 'react-bootstrap-table2';
import Code from 'components/common/code-block';
import { productsGenerator } from 'utils/common';
const products = productsGenerator();
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'price',
text: 'Product Price'
}];
const selectRow = {
mode: 'checkbox',
clickToSelect: true,
onSelect: (row, isSelect, rowIndex) => {
console.log(row.id);
console.log(isSelect);
console.log(rowIndex);
},
onSelectAll: (isSelect, rows) => {
console.log(isSelect);
console.log(rows);
}
};
const sourceCode = `\
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'price',
text: 'Product Price'
}];
const selectRow = {
mode: 'checkbox',
clickToSelect: true
};
<BootstrapTable
keyField='id'
data={ products }
columns={ columns }
selectRow={ selectRow }
/>
`;
export default () => (
<div>
<BootstrapTable keyField="id" data={ products } columns={ columns } selectRow={ selectRow } />
<Code>{ sourceCode }</Code>
</div>
);

View File

@ -59,6 +59,7 @@ import SelectionStyleTable from 'examples/row-selection/selection-style';
import SelectionClassTable from 'examples/row-selection/selection-class';
import NonSelectableRowsTable from 'examples/row-selection/non-selectable-rows';
import SelectionBgColorTable from 'examples/row-selection/selection-bgcolor';
import SelectionHooks from 'examples/row-selection/selection-hooks';
// css style
import 'bootstrap/dist/css/bootstrap.min.css';
@ -126,4 +127,5 @@ storiesOf('Row Selection', module)
.add('Selection Style', () => <SelectionStyleTable />)
.add('Selection Class', () => <SelectionClassTable />)
.add('Selection Background Color', () => <SelectionBgColorTable />)
.add('Not Selectabled Rows', () => <NonSelectableRowsTable />);
.add('Not Selectabled Rows', () => <NonSelectableRowsTable />)
.add('Selection Hooks', () => <SelectionHooks />);

View File

@ -130,6 +130,8 @@ BootstrapTable.propTypes = {
mode: PropTypes.oneOf([Const.ROW_SELECT_SINGLE, Const.ROW_SELECT_MULTIPLE]).isRequired,
clickToSelect: PropTypes.bool,
clickToEdit: PropTypes.bool,
onSelect: PropTypes.func,
onSelectAll: PropTypes.func,
style: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
classes: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
nonSelectable: PropTypes.array,

View File

@ -12,7 +12,8 @@ export default class SelectionCell extends Component {
rowKey: PropTypes.any,
selected: PropTypes.bool,
onRowSelect: PropTypes.func,
disabled: PropTypes.bool
disabled: PropTypes.bool,
rowIndex: PropTypes.number
}
constructor() {
@ -32,7 +33,8 @@ export default class SelectionCell extends Component {
rowKey,
selected,
onRowSelect,
disabled
disabled,
rowIndex
} = this.props;
if (disabled) return;
@ -41,7 +43,7 @@ export default class SelectionCell extends Component {
? true
: !selected;
onRowSelect(rowKey, checked);
onRowSelect(rowKey, checked, rowIndex);
}
render() {

View File

@ -21,8 +21,8 @@ class RowSelectionWrapper extends Component {
* @param {String} rowKey - row key of what was selected.
* @param {Boolean} checked - next checked status of input button.
*/
handleRowSelect(rowKey, checked) {
const { selectRow: { mode }, store } = this.props;
handleRowSelect(rowKey, checked, rowIndex) {
const { selectRow: { mode, onSelect }, store } = this.props;
const { ROW_SELECT_SINGLE } = Const;
let currSelected = [...store.getSelectedRowKeys()];
@ -37,6 +37,11 @@ class RowSelectionWrapper extends Component {
store.setSelectedRowKeys(currSelected);
if (onSelect) {
const row = store.getRowByRowId(rowKey);
onSelect(row, checked, rowIndex);
}
this.setState(() => ({
selectedRowKeys: currSelected
}));
@ -47,19 +52,26 @@ class RowSelectionWrapper extends Component {
* @param {Boolean} option - customized result for all rows selection
*/
handleAllRowsSelect(option) {
const { store, selectRow } = this.props;
const selected = store.isAnySelectedRow(selectRow.nonSelectable);
const { store, selectRow: {
onSelectAll,
nonSelectable
} } = this.props;
const selected = store.isAnySelectedRow(nonSelectable);
// set next status of all row selected by store.selected or customizing by user.
const result = option || !selected;
const currSelected = result ?
store.selectAllRows(selectRow.nonSelectable) :
store.cleanSelectedRows(selectRow.nonSelectable);
store.selectAllRows(nonSelectable) :
store.cleanSelectedRows(nonSelectable);
store.setSelectedRowKeys(currSelected);
if (onSelectAll) {
onSelectAll(result, store.getSelectedRows());
}
this.setState(() => ({
selectedRowKeys: currSelected
}));

View File

@ -21,6 +21,7 @@ class Row extends Component {
selected,
keyField,
selectable,
rowIndex,
selectRow: {
onRowSelect,
clickToEdit
@ -33,12 +34,12 @@ class Row extends Component {
this.clickNum += 1;
_.debounce(() => {
if (this.clickNum === 1) {
onRowSelect(key, !selected);
onRowSelect(key, !selected, rowIndex);
}
this.clickNum = 0;
}, Const.DELAY_FOR_DBCLICK)();
} else {
onRowSelect(key, !selected);
onRowSelect(key, !selected, rowIndex);
}
}
}
@ -83,6 +84,7 @@ class Row extends Component {
<SelectionCell
{ ...selectRow }
rowKey={ key }
rowIndex={ rowIndex }
selected={ selected }
disabled={ !selectable }
/>

View File

@ -50,6 +50,10 @@ export default class Store {
this.selected = selectedKeys;
}
getSelectedRows() {
return this.selected.map(k => this.getRowByRowId(k));
}
getSelectedRowKeys() {
return this.selected;
}

View File

@ -318,7 +318,7 @@ describe('Body', () => {
it('should calling selectRow.bgColor callback correctly', () => {
expect(bgColorCallBack.calledOnce).toBeTruthy();
expect(bgColorCallBack.calledWith(data[0]), 1);
expect(bgColorCallBack.calledWith(data[0]), 1).toBeTruthy();
});
it('should render Row component with correct style.backgroundColor prop', () => {

View File

@ -6,6 +6,7 @@ import SelectionCell from '../../src/row-selection/selection-cell';
describe('<SelectionCell />', () => {
const mode = 'checkbox';
const rowIndex = 1;
let wrapper;
@ -56,6 +57,7 @@ describe('<SelectionCell />', () => {
selected
rowKey={ rowKey }
mode={ mode }
rowIndex={ rowIndex }
onRowSelect={ mockOnRowSelect }
/>
);
@ -69,7 +71,7 @@ describe('<SelectionCell />', () => {
it('should calling onRowSelect callback correctly', () => {
expect(mockOnRowSelect.calledOnce).toBe(true);
expect(
mockOnRowSelect.calledWith(rowKey, !selected)
mockOnRowSelect.calledWith(rowKey, !selected, rowIndex)
).toBe(true);
});
});
@ -81,6 +83,7 @@ describe('<SelectionCell />', () => {
selected
rowKey={ rowKey }
mode={ mode }
rowIndex={ rowIndex }
onRowSelect={ mockOnRowSelect }
disabled
/>
@ -104,6 +107,7 @@ describe('<SelectionCell />', () => {
selected
rowKey={ rowKey }
mode="radio"
rowIndex={ rowIndex }
onRowSelect={ mockOnRowSelect }
/>
);
@ -113,12 +117,12 @@ describe('<SelectionCell />', () => {
// first click
wrapper.find('td').simulate('click');
expect(mockOnRowSelect.callCount).toBe(1);
expect(mockOnRowSelect.calledWith(rowKey, true)).toBe(true);
expect(mockOnRowSelect.calledWith(rowKey, true, rowIndex)).toBe(true);
// second click
wrapper.find('td').simulate('click');
expect(mockOnRowSelect.callCount).toBe(2);
expect(mockOnRowSelect.calledWith(rowKey, true)).toBe(true);
expect(mockOnRowSelect.calledWith(rowKey, true, rowIndex)).toBe(true);
});
});
@ -128,6 +132,7 @@ describe('<SelectionCell />', () => {
<SelectionCell
rowKey={ rowKey }
mode="checkbox"
rowIndex={ rowIndex }
onRowSelect={ mockOnRowSelect }
/>
);
@ -138,13 +143,13 @@ describe('<SelectionCell />', () => {
wrapper.setProps({ selected: true });
wrapper.find('td').simulate('click');
expect(mockOnRowSelect.callCount).toBe(1);
expect(mockOnRowSelect.calledWith(rowKey, false)).toBe(true);
expect(mockOnRowSelect.calledWith(rowKey, false, rowIndex)).toBe(true);
// second click
wrapper.setProps({ selected: false });
wrapper.find('td').simulate('click');
expect(mockOnRowSelect.callCount).toBe(2);
expect(mockOnRowSelect.calledWith(rowKey, true)).toBe(true);
expect(mockOnRowSelect.calledWith(rowKey, true, rowIndex)).toBe(true);
});
});
});
@ -158,6 +163,7 @@ describe('<SelectionCell />', () => {
<SelectionCell
rowKey={ 1 }
mode={ mode }
rowIndex={ rowIndex }
selected={ selected }
/>
);
@ -176,6 +182,7 @@ describe('<SelectionCell />', () => {
<SelectionCell
rowKey={ 1 }
mode={ mode }
rowIndex={ rowIndex }
selected={ selected }
disabled
/>

View File

@ -1,4 +1,5 @@
import React from 'react';
import sinon from 'sinon';
import { shallow } from 'enzyme';
import Store from '../../src/store/base';
@ -28,6 +29,8 @@ describe('RowSelectionWrapper', () => {
mode: 'radio'
};
const rowIndex = 1;
const keyField = 'id';
const store = new Store({ data, keyField });
@ -64,10 +67,10 @@ describe('RowSelectionWrapper', () => {
const secondSelectedRow = data[1][keyField];
it('call handleRowSelect function should seting correct state.selectedRowKeys', () => {
wrapper.instance().handleRowSelect(firstSelectedRow);
wrapper.instance().handleRowSelect(firstSelectedRow, rowIndex);
expect(wrapper.state('selectedRowKeys')).toEqual([firstSelectedRow]);
wrapper.instance().handleRowSelect(secondSelectedRow);
wrapper.instance().handleRowSelect(secondSelectedRow, rowIndex);
expect(wrapper.state('selectedRowKeys')).toEqual([secondSelectedRow]);
});
});
@ -90,16 +93,16 @@ describe('RowSelectionWrapper', () => {
});
it('call handleRowSelect function should seting correct state.selectedRowKeys', () => {
wrapper.instance().handleRowSelect(firstSelectedRow, true);
wrapper.instance().handleRowSelect(firstSelectedRow, true, rowIndex);
expect(wrapper.state('selectedRowKeys')).toEqual(expect.arrayContaining([firstSelectedRow]));
wrapper.instance().handleRowSelect(secondSelectedRow, true);
wrapper.instance().handleRowSelect(secondSelectedRow, true, rowIndex);
expect(wrapper.state('selectedRowKeys')).toEqual(expect.arrayContaining([firstSelectedRow, secondSelectedRow]));
wrapper.instance().handleRowSelect(firstSelectedRow, false);
wrapper.instance().handleRowSelect(firstSelectedRow, false, rowIndex);
expect(wrapper.state('selectedRowKeys')).toEqual(expect.arrayContaining([secondSelectedRow]));
wrapper.instance().handleRowSelect(secondSelectedRow, false);
wrapper.instance().handleRowSelect(secondSelectedRow, false, rowIndex);
expect(wrapper.state('selectedRowKeys')).toEqual([]);
});
@ -119,4 +122,61 @@ describe('RowSelectionWrapper', () => {
expect(wrapper.state('selectedRowKeys')).toEqual([]);
});
});
describe('when selectRow.onSelect is defined', () => {
const selectedRow = data[0][keyField];
const onSelectCallBack = sinon.stub();
beforeEach(() => {
selectRow.mode = 'checkbox';
selectRow.onSelect = onSelectCallBack;
wrapper = shallow(
<RowSelectionWrapper
keyField={ keyField }
data={ data }
columns={ columns }
selectRow={ selectRow }
store={ store }
/>
);
});
it('selectRow.onSelect callback should be called correctly when calling handleRowSelect function', () => {
wrapper.instance().handleRowSelect(selectedRow, true, rowIndex);
expect(onSelectCallBack.callCount).toEqual(1);
expect(onSelectCallBack.calledWith(data[0], true, rowIndex)).toBeTruthy();
wrapper.instance().handleRowSelect(selectedRow, false, rowIndex);
expect(onSelectCallBack.callCount).toEqual(2);
expect(onSelectCallBack.calledWith(data[0], false, rowIndex)).toBeTruthy();
});
});
describe('when selectRow.onSelectAll is defined', () => {
const onSelectAllCallBack = sinon.stub();
beforeEach(() => {
selectRow.mode = 'checkbox';
selectRow.onSelectAll = onSelectAllCallBack;
wrapper = shallow(
<RowSelectionWrapper
keyField={ keyField }
data={ data }
columns={ columns }
selectRow={ selectRow }
store={ store }
/>
);
});
it('selectRow.onSelect callback should be called correctly when calling handleRowSelect function', () => {
wrapper.instance().handleAllRowsSelect();
expect(onSelectAllCallBack.callCount).toEqual(1);
expect(onSelectAllCallBack.calledWith(true, data)).toBeTruthy();
wrapper.instance().handleAllRowsSelect();
expect(onSelectAllCallBack.callCount).toEqual(2);
expect(onSelectAllCallBack.calledWith(false, [])).toBeTruthy();
});
});
});

View File

@ -605,7 +605,7 @@ describe('Row', () => {
});
it('should calling selectRow.onRowSelect with correct argument', () => {
expect(onRowSelectCallBack.calledWith(row[keyField], false)).toBeTruthy();
expect(onRowSelectCallBack.calledWith(row[keyField], false, rowIndex)).toBeTruthy();
});
});
@ -636,7 +636,7 @@ describe('Row', () => {
});
it('should calling selectRow.onRowSelect with correct argument', () => {
expect(onRowSelectCallBack.calledWith(row[keyField], true)).toBeTruthy();
expect(onRowSelectCallBack.calledWith(row[keyField], true, rowIndex)).toBeTruthy();
});
});
});

View File

@ -87,6 +87,31 @@ describe('Store Base', () => {
});
});
describe('getSelectedRows', () => {
const selected = [1, 4];
beforeEach(() => {
store.setSelectedRowKeys(selected);
});
it('should return all selected rows by store.selected', () => {
const result = store.getSelectedRows();
expect(result).toBeDefined();
expect(result.length).toEqual(2);
result.forEach((r) => {
expect(r).toBeDefined();
expect(selected.includes(r[store.keyField])).toBeTruthy();
});
});
});
describe('setSelectedRowKeys', () => {
const selected = [1, 4];
it('should set store.selected correctly', () => {
store.setSelectedRowKeys(selected);
expect(store.getSelectedRowKeys()).toEqual(selected);
});
});
describe('edit', () => {
it('should update a specified field correctly', () => {
const newValue = 'newValue';