mirror of
https://github.com/gosticks/react-bootstrap-table2.git
synced 2025-10-16 11:55:39 +00:00
fix #106
* implement selectRow.nonSelectable * add story for selectRow.nonSelectable * add testing for selectRow.nonSelectable * refine tests about row selection * patch docs for selectRow.nonSelectable
This commit is contained in:
parent
10f06dca10
commit
afc41879ee
@ -11,6 +11,7 @@ The following are available properties in `selectRow`:
|
||||
* [mode (**required**)](#mode)
|
||||
* [style](#style)
|
||||
* [classes)](#classes)
|
||||
* [nonSelectable)](#nonSelectable)
|
||||
|
||||
#### Optional
|
||||
|
||||
@ -86,3 +87,13 @@ const selectRow = {
|
||||
classes: (row, rowIndex) => { return ...; }
|
||||
};
|
||||
```
|
||||
|
||||
## <a name='nonSelectable'>selectRow.nonSelectable - [Array]</a>
|
||||
This prop allow you to restrict some rows which can not be selected by user. `selectRow.nonSelectable` accept an rowkeys array.
|
||||
|
||||
```js
|
||||
const selectRow = {
|
||||
mode: 'checkbox',
|
||||
nonSelectable: [1, 3 ,5]
|
||||
};
|
||||
```
|
||||
55
packages/react-bootstrap-table2-example/examples/row-selection/non-selectable-rows.js
vendored
Normal file
55
packages/react-bootstrap-table2-example/examples/row-selection/non-selectable-rows.js
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
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',
|
||||
nonSelectable: [0, 2, 4]
|
||||
};
|
||||
|
||||
const sourceCode = `\
|
||||
const columns = [{
|
||||
dataField: 'id',
|
||||
text: 'Product ID'
|
||||
}, {
|
||||
dataField: 'name',
|
||||
text: 'Product Name'
|
||||
}, {
|
||||
dataField: 'price',
|
||||
text: 'Product Price'
|
||||
}];
|
||||
|
||||
const selectRow = {
|
||||
mode: 'checkbox',
|
||||
nonSelectable: [0, 2, 4]
|
||||
};
|
||||
|
||||
<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>
|
||||
);
|
||||
@ -55,6 +55,7 @@ import SingleSelectionTable from 'examples/row-selection/single-selection';
|
||||
import MultipleSelectionTable from 'examples/row-selection/multiple-selection';
|
||||
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';
|
||||
|
||||
// css style
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
@ -118,5 +119,5 @@ storiesOf('Row Selection', module)
|
||||
.add('Single Selection', () => <SingleSelectionTable />)
|
||||
.add('Multiple Selection', () => <MultipleSelectionTable />)
|
||||
.add('Selection Style', () => <SelectionStyleTable />)
|
||||
.add('Selection Class', () => <SelectionClassTable />);
|
||||
|
||||
.add('Selection Class', () => <SelectionClassTable />)
|
||||
.add('Not Selectabled Rows', () => <NonSelectableRowsTable />);
|
||||
|
||||
@ -129,7 +129,8 @@ BootstrapTable.propTypes = {
|
||||
selectRow: PropTypes.shape({
|
||||
mode: PropTypes.oneOf([Const.ROW_SELECT_SINGLE, Const.ROW_SELECT_MULTIPLE]).isRequired,
|
||||
style: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
|
||||
classes: PropTypes.oneOfType([PropTypes.string, PropTypes.func])
|
||||
classes: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
||||
nonSelectable: PropTypes.array
|
||||
}),
|
||||
onRowSelect: PropTypes.func,
|
||||
onAllRowsSelect: PropTypes.func
|
||||
|
||||
@ -11,7 +11,8 @@ export default class SelectionCell extends Component {
|
||||
mode: PropTypes.string.isRequired,
|
||||
rowKey: PropTypes.any,
|
||||
selected: PropTypes.bool,
|
||||
onRowSelect: PropTypes.func
|
||||
onRowSelect: PropTypes.func,
|
||||
disabled: PropTypes.bool
|
||||
}
|
||||
|
||||
constructor() {
|
||||
@ -26,15 +27,17 @@ export default class SelectionCell extends Component {
|
||||
}
|
||||
|
||||
handleRowClick() {
|
||||
const { ROW_SELECT_SINGLE } = Const;
|
||||
const {
|
||||
mode: inputType,
|
||||
rowKey,
|
||||
selected,
|
||||
onRowSelect
|
||||
onRowSelect,
|
||||
disabled
|
||||
} = this.props;
|
||||
|
||||
const checked = inputType === ROW_SELECT_SINGLE
|
||||
if (disabled) return;
|
||||
|
||||
const checked = inputType === Const.ROW_SELECT_SINGLE
|
||||
? true
|
||||
: !selected;
|
||||
|
||||
@ -44,7 +47,8 @@ export default class SelectionCell extends Component {
|
||||
render() {
|
||||
const {
|
||||
mode: inputType,
|
||||
selected
|
||||
selected,
|
||||
disabled
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@ -52,6 +56,7 @@ export default class SelectionCell extends Component {
|
||||
<input
|
||||
type={ inputType }
|
||||
checked={ selected }
|
||||
disabled={ disabled }
|
||||
/>
|
||||
</td>
|
||||
);
|
||||
|
||||
@ -46,13 +46,16 @@ class RowSelectionWrapper extends Component {
|
||||
* @param {Boolean} option - customized result for all rows selection
|
||||
*/
|
||||
handleAllRowsSelect(option) {
|
||||
const { store } = this.props;
|
||||
const selected = store.isAnySelectedRow();
|
||||
const { store, selectRow } = this.props;
|
||||
const selected = store.isAnySelectedRow(selectRow.nonSelectable);
|
||||
|
||||
// set next status of all row selected by store.selected or customizing by user.
|
||||
const result = option || !selected;
|
||||
|
||||
const currSelected = result ? store.selectAllRowKeys() : [];
|
||||
const currSelected = result ?
|
||||
store.selectAllRows(selectRow.nonSelectable) :
|
||||
store.cleanSelectedRows(selectRow.nonSelectable);
|
||||
|
||||
|
||||
store.setSelectedRowKeys(currSelected);
|
||||
|
||||
|
||||
4
packages/react-bootstrap-table2/src/row.js
vendored
4
packages/react-bootstrap-table2/src/row.js
vendored
@ -30,6 +30,9 @@ const Row = (props) => {
|
||||
...rest
|
||||
} = cellEdit;
|
||||
|
||||
const key = _.get(row, keyField);
|
||||
const { nonSelectable } = selectRow;
|
||||
|
||||
return (
|
||||
<tr style={ style } className={ className }>
|
||||
{
|
||||
@ -40,6 +43,7 @@ const Row = (props) => {
|
||||
{ ...selectRow }
|
||||
rowKey={ _.get(row, keyField) }
|
||||
selected={ selected }
|
||||
disabled={ nonSelectable && nonSelectable.includes(key) }
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@ -54,15 +54,30 @@ export default class Store {
|
||||
return this.selected;
|
||||
}
|
||||
|
||||
selectAllRowKeys() {
|
||||
return this.data.map(row => _.get(row, this.keyField));
|
||||
selectAllRows(nonSelectableRows = []) {
|
||||
if (nonSelectableRows.length === 0) {
|
||||
return this.data.map(row => _.get(row, this.keyField));
|
||||
}
|
||||
return this.data
|
||||
.filter(row => !nonSelectableRows.includes(_.get(row, this.keyField)))
|
||||
.map(row => _.get(row, this.keyField));
|
||||
}
|
||||
|
||||
cleanSelectedRows(nonSelectableRows = []) {
|
||||
if (nonSelectableRows.length === 0) {
|
||||
return [];
|
||||
}
|
||||
return this.selected.filter(x => nonSelectableRows.includes(x));
|
||||
}
|
||||
|
||||
isAllRowsSelected() {
|
||||
return this.data.length === this.selected.length;
|
||||
}
|
||||
|
||||
isAnySelectedRow() {
|
||||
return this.selected.length > 0;
|
||||
isAnySelectedRow(nonSelectableRows = []) {
|
||||
if (nonSelectableRows.length === 0) {
|
||||
return this.selected.length > 0;
|
||||
}
|
||||
return this.selected.filter(x => !nonSelectableRows.includes(x)).length;
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,28 +36,65 @@ describe('<SelectionCell />', () => {
|
||||
describe('handleRowClick', () => {
|
||||
describe('when <input /> was been clicked', () => {
|
||||
const rowKey = 1;
|
||||
const mockOnRowSelect = sinon.stub();
|
||||
const selected = true;
|
||||
let mockOnRowSelect;
|
||||
const spy = sinon.spy(SelectionCell.prototype, 'handleRowClick');
|
||||
|
||||
beforeEach(() => {
|
||||
mockOnRowSelect = sinon.stub();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
spy.reset();
|
||||
mockOnRowSelect.reset();
|
||||
});
|
||||
|
||||
it('should call handleRowClicked', () => {
|
||||
wrapper = shallow(
|
||||
<SelectionCell
|
||||
selected
|
||||
rowKey={ rowKey }
|
||||
mode={ mode }
|
||||
onRowSelect={ mockOnRowSelect }
|
||||
/>
|
||||
);
|
||||
describe('when disabled prop is false', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(
|
||||
<SelectionCell
|
||||
selected
|
||||
rowKey={ rowKey }
|
||||
mode={ mode }
|
||||
onRowSelect={ mockOnRowSelect }
|
||||
/>
|
||||
);
|
||||
wrapper.find('td').simulate('click');
|
||||
});
|
||||
|
||||
wrapper.find('td').simulate('click');
|
||||
it('should calling handleRowClicked', () => {
|
||||
expect(spy.calledOnce).toBe(true);
|
||||
});
|
||||
|
||||
expect(spy.calledOnce).toBe(true);
|
||||
expect(mockOnRowSelect.calledOnce).toBe(true);
|
||||
it('should calling onRowSelect callback correctly', () => {
|
||||
expect(mockOnRowSelect.calledOnce).toBe(true);
|
||||
expect(
|
||||
mockOnRowSelect.calledWith(rowKey, !selected)
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when disabled prop is true', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(
|
||||
<SelectionCell
|
||||
selected
|
||||
rowKey={ rowKey }
|
||||
mode={ mode }
|
||||
onRowSelect={ mockOnRowSelect }
|
||||
disabled
|
||||
/>
|
||||
);
|
||||
wrapper.find('td').simulate('click');
|
||||
});
|
||||
|
||||
it('should calling handleRowClicked', () => {
|
||||
expect(spy.calledOnce).toBe(true);
|
||||
});
|
||||
|
||||
it('should not calling onRowSelect callback', () => {
|
||||
expect(mockOnRowSelect.calledOnce).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('if selectRow.mode is radio', () => {
|
||||
@ -132,5 +169,22 @@ describe('<SelectionCell />', () => {
|
||||
expect(wrapper.find('input').get(0).props.type).toBe(mode);
|
||||
expect(wrapper.find('input').get(0).props.checked).toBe(selected);
|
||||
});
|
||||
|
||||
describe('when disabled prop give as true', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(
|
||||
<SelectionCell
|
||||
rowKey={ 1 }
|
||||
mode={ mode }
|
||||
selected={ selected }
|
||||
disabled
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
it('should render component with disabled attribute', () => {
|
||||
expect(wrapper.find('input').get(0).props.disabled).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -20,6 +20,9 @@ const defaultColumns = [{
|
||||
text: 'Price'
|
||||
}];
|
||||
|
||||
const keyField = 'id';
|
||||
const rowIndex = 1;
|
||||
|
||||
describe('Row', () => {
|
||||
let wrapper;
|
||||
|
||||
@ -32,7 +35,13 @@ describe('Row', () => {
|
||||
describe('simplest row', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(
|
||||
<Row { ...mockBodyResolvedProps } rowIndex={ 1 } columns={ defaultColumns } row={ row } />);
|
||||
<Row
|
||||
{ ...mockBodyResolvedProps }
|
||||
rowIndex={ rowIndex }
|
||||
columns={ defaultColumns }
|
||||
row={ row }
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
it('should render successfully', () => {
|
||||
@ -48,7 +57,7 @@ describe('Row', () => {
|
||||
wrapper = shallow(
|
||||
<Row
|
||||
{ ...mockBodyResolvedProps }
|
||||
rowIndex={ 1 }
|
||||
rowIndex={ rowIndex }
|
||||
columns={ defaultColumns }
|
||||
row={ row }
|
||||
style={ customStyle }
|
||||
@ -67,7 +76,7 @@ describe('Row', () => {
|
||||
wrapper = shallow(
|
||||
<Row
|
||||
{ ...mockBodyResolvedProps }
|
||||
rowIndex={ 1 }
|
||||
rowIndex={ rowIndex }
|
||||
columns={ defaultColumns }
|
||||
row={ row }
|
||||
className={ className }
|
||||
@ -83,8 +92,6 @@ describe('Row', () => {
|
||||
describe('when cellEdit prop is defined', () => {
|
||||
let columns;
|
||||
let cellEdit;
|
||||
const rowIndex = 1;
|
||||
const keyField = 'id';
|
||||
|
||||
beforeEach(() => {
|
||||
columns = defaultColumns;
|
||||
@ -270,7 +277,7 @@ describe('Row', () => {
|
||||
<Row
|
||||
{ ...mockBodyResolvedProps }
|
||||
row={ row }
|
||||
rowIndex={ 1 }
|
||||
rowIndex={ rowIndex }
|
||||
columns={ columns }
|
||||
keyField={ keyField }
|
||||
cellEdit={ cellEdit }
|
||||
@ -437,29 +444,83 @@ describe('Row', () => {
|
||||
describe('when selectRow.mode is ROW_SELECT_DISABLED (row was un-selectable)', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(
|
||||
<Row { ...mockBodyResolvedProps } rowIndex={ 1 } columns={ defaultColumns } row={ row } />);
|
||||
<Row
|
||||
{ ...mockBodyResolvedProps }
|
||||
rowIndex={ rowIndex }
|
||||
columns={ defaultColumns }
|
||||
row={ row }
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
it('should not render <SelectionCell />', () => {
|
||||
it('should not render SelectionCell component', () => {
|
||||
expect(wrapper.find(SelectionCell).length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when selectRow.mode is checkbox or radio (row was selectable)', () => {
|
||||
let selectRow;
|
||||
beforeEach(() => {
|
||||
const selectRow = { mode: 'checkbox' };
|
||||
selectRow = { mode: 'checkbox' };
|
||||
wrapper = shallow(
|
||||
<Row
|
||||
{ ...mockBodyResolvedProps }
|
||||
rowIndex={ 1 }
|
||||
rowIndex={ rowIndex }
|
||||
columns={ defaultColumns }
|
||||
row={ row }
|
||||
selectRow={ selectRow }
|
||||
selected
|
||||
/>);
|
||||
});
|
||||
|
||||
it('should render <SelectionCell />', () => {
|
||||
it('should rendering SelectionCell component correctly', () => {
|
||||
expect(wrapper.find(SelectionCell).length).toBe(1);
|
||||
});
|
||||
|
||||
it('should render SelectionCell component with correct props', () => {
|
||||
expect(wrapper.find(SelectionCell).props().selected).toBeTruthy();
|
||||
expect(wrapper.find(SelectionCell).props().disabled).toBeFalsy();
|
||||
expect(wrapper.find(SelectionCell).props().mode).toEqual(selectRow.mode);
|
||||
});
|
||||
|
||||
describe('if selectRow.nonSelectable is defined and contain a rowkey which is match to current row', () => {
|
||||
beforeEach(() => {
|
||||
selectRow = { mode: 'checkbox', nonSelectable: [row.id] };
|
||||
wrapper = shallow(
|
||||
<Row
|
||||
{ ...mockBodyResolvedProps }
|
||||
rowIndex={ rowIndex }
|
||||
columns={ defaultColumns }
|
||||
row={ row }
|
||||
keyField={ keyField }
|
||||
selectRow={ selectRow }
|
||||
/>);
|
||||
});
|
||||
|
||||
it('should render SelectionCell component with correct disable prop correctly', () => {
|
||||
expect(wrapper.find(SelectionCell).length).toBe(1);
|
||||
expect(wrapper.find(SelectionCell).prop('disabled')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('if selectRow.nonSelectable is defined and not contain any rowkey which is match to current row', () => {
|
||||
beforeEach(() => {
|
||||
selectRow = { mode: 'checkbox', nonSelectable: [3, 4, 6] };
|
||||
wrapper = shallow(
|
||||
<Row
|
||||
{ ...mockBodyResolvedProps }
|
||||
rowIndex={ rowIndex }
|
||||
columns={ defaultColumns }
|
||||
row={ row }
|
||||
keyField={ keyField }
|
||||
selectRow={ selectRow }
|
||||
/>);
|
||||
});
|
||||
|
||||
it('should render SelectionCell component with correct disable prop correctly', () => {
|
||||
expect(wrapper.find(SelectionCell).length).toBe(1);
|
||||
expect(wrapper.find(SelectionCell).prop('disabled')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -111,37 +111,72 @@ describe('Store Base', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('selectAllRowKeys', () => {
|
||||
describe('selectAllRows', () => {
|
||||
it('should return all row keys', () => {
|
||||
const rowKeys = store.selectAllRowKeys();
|
||||
const rowKeys = store.selectAllRows();
|
||||
|
||||
expect(Array.isArray(rowKeys)).toBeTruthy();
|
||||
expect(rowKeys).toEqual([3, 2, 4, 1]);
|
||||
expect(rowKeys).toEqual(data.map(x => x[store.keyField]));
|
||||
});
|
||||
|
||||
it('should return correct row keys when nonSelectableRows args is not empty', () => {
|
||||
const nonSelectableRows = [1, 3];
|
||||
const rowKeys = store.selectAllRows(nonSelectableRows);
|
||||
|
||||
expect(Array.isArray(rowKeys)).toBeTruthy();
|
||||
expect(rowKeys).toEqual(
|
||||
data
|
||||
.filter(x => !nonSelectableRows.includes(_.get(x, store.keyField)))
|
||||
.map(x => x[store.keyField])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isAllRowsSelected', () => {
|
||||
it('should return true when all rows was selected', () => {
|
||||
it('should return true when all rows is selected', () => {
|
||||
store.selected = data.map(row => _.get(row, store.keyField));
|
||||
|
||||
expect(store.isAllRowsSelected()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return false when all rows was not selected', () => {
|
||||
it('should return false when not all of rows is selected', () => {
|
||||
store.selected = [1];
|
||||
|
||||
expect(store.isAllRowsSelected()).not.toBeTruthy();
|
||||
expect(store.isAllRowsSelected()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('isAnySelectedRow', () => {
|
||||
it('should return true when one or more than one rows were selected', () => {
|
||||
store.selected = data.map(row => _.get(row, store.keyField));
|
||||
describe('if store.selected not empty', () => {
|
||||
it('should return true', () => {
|
||||
store.selected = data.map(row => _.get(row, store.keyField));
|
||||
expect(store.isAnySelectedRow()).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(store.isAnySelectedRow()).toBeTruthy();
|
||||
describe('when nonSelectableRows given and not all of nonselectable rows are match current store.selected', () => {
|
||||
const nonSelectableRows = [1, 3];
|
||||
beforeEach(() => {
|
||||
store.selected = [1, 4];
|
||||
});
|
||||
|
||||
it('should return true', () => {
|
||||
expect(store.isAnySelectedRow(nonSelectableRows)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when nonSelectableRows given and all of nonselectable rows are match current store.selected', () => {
|
||||
const nonSelectableRows = [1, 3];
|
||||
beforeEach(() => {
|
||||
store.selected = nonSelectableRows;
|
||||
});
|
||||
|
||||
it('should return false', () => {
|
||||
expect(store.isAnySelectedRow(nonSelectableRows)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should return false when none was selected', () => {
|
||||
it('should return false if store.selected is empty', () => {
|
||||
store.selected = [];
|
||||
|
||||
expect(store.isAnySelectedRow()).not.toBeTruthy();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user