diff --git a/docs/row-selection.md b/docs/row-selection.md
index 3b6ddcd..6143295 100644
--- a/docs/row-selection.md
+++ b/docs/row-selection.md
@@ -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 ...; }
};
```
+
+## selectRow.nonSelectable - [Array]
+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]
+};
+```
\ No newline at end of file
diff --git a/packages/react-bootstrap-table2-example/examples/row-selection/non-selectable-rows.js b/packages/react-bootstrap-table2-example/examples/row-selection/non-selectable-rows.js
new file mode 100644
index 0000000..d74d785
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/row-selection/non-selectable-rows.js
@@ -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]
+};
+
+
+`;
+
+export default () => (
+
+
+ { sourceCode }
+
+);
diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js
index e0ed3ca..95192e0 100644
--- a/packages/react-bootstrap-table2-example/stories/index.js
+++ b/packages/react-bootstrap-table2-example/stories/index.js
@@ -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', () => )
.add('Multiple Selection', () => )
.add('Selection Style', () => )
- .add('Selection Class', () => );
-
+ .add('Selection Class', () => )
+ .add('Not Selectabled Rows', () => );
diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js
index 13a9cce..f336f04 100644
--- a/packages/react-bootstrap-table2/src/bootstrap-table.js
+++ b/packages/react-bootstrap-table2/src/bootstrap-table.js
@@ -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
diff --git a/packages/react-bootstrap-table2/src/row-selection/selection-cell.js b/packages/react-bootstrap-table2/src/row-selection/selection-cell.js
index 604188b..fa41f3a 100644
--- a/packages/react-bootstrap-table2/src/row-selection/selection-cell.js
+++ b/packages/react-bootstrap-table2/src/row-selection/selection-cell.js
@@ -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 {
);
diff --git a/packages/react-bootstrap-table2/src/row-selection/wrapper.js b/packages/react-bootstrap-table2/src/row-selection/wrapper.js
index ab2fb9f..97909f2 100644
--- a/packages/react-bootstrap-table2/src/row-selection/wrapper.js
+++ b/packages/react-bootstrap-table2/src/row-selection/wrapper.js
@@ -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);
diff --git a/packages/react-bootstrap-table2/src/row.js b/packages/react-bootstrap-table2/src/row.js
index 1949c33..d93f384 100644
--- a/packages/react-bootstrap-table2/src/row.js
+++ b/packages/react-bootstrap-table2/src/row.js
@@ -30,6 +30,9 @@ const Row = (props) => {
...rest
} = cellEdit;
+ const key = _.get(row, keyField);
+ const { nonSelectable } = selectRow;
+
return (
{
@@ -40,6 +43,7 @@ const Row = (props) => {
{ ...selectRow }
rowKey={ _.get(row, keyField) }
selected={ selected }
+ disabled={ nonSelectable && nonSelectable.includes(key) }
/>
)
}
diff --git a/packages/react-bootstrap-table2/src/store/base.js b/packages/react-bootstrap-table2/src/store/base.js
index 6006861..eb435d1 100644
--- a/packages/react-bootstrap-table2/src/store/base.js
+++ b/packages/react-bootstrap-table2/src/store/base.js
@@ -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;
}
}
diff --git a/packages/react-bootstrap-table2/test/row-selection/selection-cell.test.js b/packages/react-bootstrap-table2/test/row-selection/selection-cell.test.js
index d443301..5c92145 100644
--- a/packages/react-bootstrap-table2/test/row-selection/selection-cell.test.js
+++ b/packages/react-bootstrap-table2/test/row-selection/selection-cell.test.js
@@ -36,28 +36,65 @@ describe('', () => {
describe('handleRowClick', () => {
describe('when 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(
-
- );
+ describe('when disabled prop is false', () => {
+ beforeEach(() => {
+ wrapper = shallow(
+
+ );
+ 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(
+
+ );
+ 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('', () => {
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(
+
+ );
+ });
+
+ it('should render component with disabled attribute', () => {
+ expect(wrapper.find('input').get(0).props.disabled).toBeTruthy();
+ });
+ });
});
});
diff --git a/packages/react-bootstrap-table2/test/row.test.js b/packages/react-bootstrap-table2/test/row.test.js
index c332f27..73d01ea 100644
--- a/packages/react-bootstrap-table2/test/row.test.js
+++ b/packages/react-bootstrap-table2/test/row.test.js
@@ -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(
-
);
+
+ );
});
it('should render successfully', () => {
@@ -48,7 +57,7 @@ describe('Row', () => {
wrapper = shallow(
{
wrapper = shallow(
{
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', () => {
{
describe('when selectRow.mode is ROW_SELECT_DISABLED (row was un-selectable)', () => {
beforeEach(() => {
wrapper = shallow(
-
);
+
+ );
});
- it('should not render ', () => {
+ 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(
);
});
- it('should render ', () => {
+ 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(
+
);
+ });
+
+ 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(
+
);
+ });
+
+ it('should render SelectionCell component with correct disable prop correctly', () => {
+ expect(wrapper.find(SelectionCell).length).toBe(1);
+ expect(wrapper.find(SelectionCell).prop('disabled')).toBeFalsy();
+ });
+ });
});
});
diff --git a/packages/react-bootstrap-table2/test/store/base.test.js b/packages/react-bootstrap-table2/test/store/base.test.js
index d3b0ec3..716a350 100644
--- a/packages/react-bootstrap-table2/test/store/base.test.js
+++ b/packages/react-bootstrap-table2/test/store/base.test.js
@@ -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();