diff --git a/docs/row-selection.md b/docs/row-selection.md
index 9726ce2..832ef1d 100644
--- a/docs/row-selection.md
+++ b/docs/row-selection.md
@@ -13,6 +13,8 @@ The following are available properties in `selectRow`:
* [classes)](#classes)
* [bgColor](#bgColor)
* [nonSelectable)](#nonSelectable)
+* [clickToSelect)](#clickToSelect)
+* [clickToEdit](#clickToEdit)
#### Optional
@@ -118,4 +120,28 @@ const selectRow = {
mode: 'checkbox',
nonSelectable: [1, 3 ,5]
};
+```
+
+## selectRow.clickToSelect - [Bool]
+Able to select row when clicking on row.
+
+```js
+const selectRow = {
+ mode: 'checkbox',
+ clickToSelect: true
+};
+```
+
+> Note: if you also enable [cellEdit](./cell-edit.md), the `selectRow.clickToSelect` will deactivate the functionality of cell editing
+> If you want to click on row to select row and edit cell simultaneously, you are suppose to enable [`selectRow.clickToEdit`](#clickToEdit)
+
+## selectRow.clickToEdit - [Bool]
+Able to click to edit cell and select row
+
+```js
+const selectRow = {
+ mode: 'checkbox',
+ clickToSelect: true
+ clickToEdit: true
+};
```
\ No newline at end of file
diff --git a/packages/react-bootstrap-table2-example/examples/row-selection/click-to-select-with-cell-edit.js b/packages/react-bootstrap-table2-example/examples/row-selection/click-to-select-with-cell-edit.js
new file mode 100644
index 0000000..cc0c1fe
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/row-selection/click-to-select-with-cell-edit.js
@@ -0,0 +1,65 @@
+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,
+ clickToEdit: true
+};
+
+const cellEdit = {
+ mode: 'click'
+};
+
+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,
+ clickToEdit: true // Click to edit cell also
+};
+
+const cellEdit = {
+ mode: 'click'
+};
+
+
+`;
+
+export default () => (
+
{
columns={ columns }
cellEdit={ cellEdit }
editable={ editable }
+ selectable={ selectable }
selected={ selected }
selectRow={ selectRow }
style={ style }
diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js
index c90823a..11530c8 100644
--- a/packages/react-bootstrap-table2/src/bootstrap-table.js
+++ b/packages/react-bootstrap-table2/src/bootstrap-table.js
@@ -128,6 +128,8 @@ BootstrapTable.propTypes = {
}),
selectRow: PropTypes.shape({
mode: PropTypes.oneOf([Const.ROW_SELECT_SINGLE, Const.ROW_SELECT_MULTIPLE]).isRequired,
+ clickToSelect: PropTypes.bool,
+ clickToEdit: PropTypes.bool,
style: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
classes: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
nonSelectable: PropTypes.array,
diff --git a/packages/react-bootstrap-table2/src/cell-edit/editing-cell.js b/packages/react-bootstrap-table2/src/cell-edit/editing-cell.js
index ee432dd..48a7299 100644
--- a/packages/react-bootstrap-table2/src/cell-edit/editing-cell.js
+++ b/packages/react-bootstrap-table2/src/cell-edit/editing-cell.js
@@ -1,6 +1,8 @@
/* eslint arrow-body-style: 0 */
/* eslint react/prop-types: 0 */
/* eslint no-return-assign: 0 */
+/* eslint class-methods-use-this: 0 */
+/* eslint jsx-a11y/no-noninteractive-element-interactions: 0 */
import React, { Component } from 'react';
import cs from 'classnames';
import PropTypes from 'prop-types';
@@ -16,6 +18,7 @@ class EditingCell extends Component {
this.indicatorTimer = null;
this.clearTimer = this.clearTimer.bind(this);
this.handleBlur = this.handleBlur.bind(this);
+ this.handleClick = this.handleClick.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
this.beforeComplete = this.beforeComplete.bind(this);
this.state = {
@@ -94,6 +97,15 @@ class EditingCell extends Component {
}
}
+ handleClick(e) {
+ if (e.target.tagName !== 'TD') {
+ // To avoid the row selection event be triggered,
+ // When user define selectRow.clickToSelect and selectRow.clickToEdit
+ // We shouldn't trigger selection event even if user click on the cell editor(input)
+ e.stopPropagation();
+ }
+ }
+
render() {
const { invalidMessage } = this.state;
const { row, column, className, style } = this.props;
@@ -111,6 +123,7 @@ class EditingCell extends Component {
this.editor = node }
diff --git a/packages/react-bootstrap-table2/src/cell-edit/wrapper.js b/packages/react-bootstrap-table2/src/cell-edit/wrapper.js
index da6963f..60e3134 100644
--- a/packages/react-bootstrap-table2/src/cell-edit/wrapper.js
+++ b/packages/react-bootstrap-table2/src/cell-edit/wrapper.js
@@ -61,13 +61,18 @@ class CellEditWrapper extends Component {
}
startEditing(ridx, cidx) {
- this.setState(() => {
- return {
- ridx,
- cidx,
- editing: true
- };
- });
+ const editing = () => {
+ this.setState(() => {
+ return {
+ ridx,
+ cidx,
+ editing: true
+ };
+ });
+ };
+
+ const { selectRow } = this.props;
+ if (!selectRow || (selectRow.clickToEdit || !selectRow.clickToSelect)) editing();
}
escapeEditing() {
diff --git a/packages/react-bootstrap-table2/src/const.js b/packages/react-bootstrap-table2/src/const.js
index 4e10028..e9ebaa6 100644
--- a/packages/react-bootstrap-table2/src/const.js
+++ b/packages/react-bootstrap-table2/src/const.js
@@ -10,5 +10,6 @@ export default {
ROW_SELECT_DISABLED: 'ROW_SELECT_DISABLED',
CHECKBOX_STATUS_CHECKED: 'checked',
CHECKBOX_STATUS_INDETERMINATE: 'indeterminate',
- CHECKBOX_STATUS_UNCHECKED: 'unchecked'
+ CHECKBOX_STATUS_UNCHECKED: 'unchecked',
+ DELAY_FOR_DBCLICK: 200
};
diff --git a/packages/react-bootstrap-table2/src/row.js b/packages/react-bootstrap-table2/src/row.js
index d93f384..23a8cf2 100644
--- a/packages/react-bootstrap-table2/src/row.js
+++ b/packages/react-bootstrap-table2/src/row.js
@@ -1,5 +1,5 @@
/* eslint react/prop-types: 0 */
-import React from 'react';
+import React, { Component } from 'react';
import PropTypes from 'prop-types';
import _ from './utils';
@@ -8,91 +8,133 @@ import SelectionCell from './row-selection/selection-cell';
import EditingCell from './cell-edit/editing-cell';
import Const from './const';
-const Row = (props) => {
- const {
- row,
- columns,
- keyField,
- rowIndex,
- className,
- style,
- cellEdit,
- selected,
- selectRow,
- editable: editableRow
- } = props;
+class Row extends Component {
+ constructor(props) {
+ super(props);
+ this.clickNum = 0;
+ this.handleRowClick = this.handleRowClick.bind(this);
+ }
- const {
- mode,
- onStart,
- ridx: editingRowIdx,
- cidx: editingColIdx,
- ...rest
- } = cellEdit;
-
- const key = _.get(row, keyField);
- const { nonSelectable } = selectRow;
-
- return (
-
- {
- selectRow.mode === Const.ROW_SELECT_DISABLED
- ? null
- : (
-
- )
+ handleRowClick() {
+ const {
+ row,
+ selected,
+ keyField,
+ selectable,
+ selectRow: {
+ onRowSelect,
+ clickToEdit
}
- {
- columns.map((column, index) => {
- const { dataField } = column;
- const content = _.get(row, dataField);
- let editable = _.isDefined(column.editable) ? column.editable : true;
- if (dataField === keyField || !editableRow) editable = false;
- if (_.isFunction(column.editable)) {
- editable = column.editable(content, row, rowIndex, index);
+ } = this.props;
+ const key = _.get(row, keyField);
+ if (selectable) {
+ const { cellEdit: { mode } } = this.props;
+ if (mode === Const.DBCLICK_TO_CELL_EDIT && clickToEdit) {
+ this.clickNum += 1;
+ _.debounce(() => {
+ if (this.clickNum === 1) {
+ onRowSelect(key, !selected);
}
- if (rowIndex === editingRowIdx && index === editingColIdx) {
- let editCellstyle = column.editCellStyle || {};
- let editCellclasses = column.editCellClasses;
- if (_.isFunction(column.editCellStyle)) {
- editCellstyle = column.editCellStyle(content, row, rowIndex, index);
+ this.clickNum = 0;
+ }, Const.DELAY_FOR_DBCLICK)();
+ } else {
+ onRowSelect(key, !selected);
+ }
+ }
+ }
+
+ render() {
+ const {
+ row,
+ columns,
+ keyField,
+ rowIndex,
+ className,
+ style,
+ cellEdit,
+ selected,
+ selectRow,
+ selectable,
+ editable: editableRow
+ } = this.props;
+
+ const {
+ mode,
+ onStart,
+ ridx: editingRowIdx,
+ cidx: editingColIdx,
+ ...rest
+ } = cellEdit;
+
+ const key = _.get(row, keyField);
+ const { clickToSelect } = selectRow;
+
+ const trAttrs = {};
+ if (clickToSelect) {
+ trAttrs.onClick = this.handleRowClick;
+ }
+
+ return (
+
+ {
+ selectRow.mode === Const.ROW_SELECT_DISABLED
+ ? null
+ : (
+
+ )
+ }
+ {
+ columns.map((column, index) => {
+ const { dataField } = column;
+ const content = _.get(row, dataField);
+ let editable = _.isDefined(column.editable) ? column.editable : true;
+ if (dataField === keyField || !editableRow) editable = false;
+ if (_.isFunction(column.editable)) {
+ editable = column.editable(content, row, rowIndex, index);
}
- if (_.isFunction(column.editCellClasses)) {
- editCellclasses = column.editCellClasses(content, row, rowIndex, index);
+ if (rowIndex === editingRowIdx && index === editingColIdx) {
+ let editCellstyle = column.editCellStyle || {};
+ let editCellclasses = column.editCellClasses;
+ if (_.isFunction(column.editCellStyle)) {
+ editCellstyle = column.editCellStyle(content, row, rowIndex, index);
+ }
+ if (_.isFunction(column.editCellClasses)) {
+ editCellclasses = column.editCellClasses(content, row, rowIndex, index);
+ }
+ return (
+
+ );
}
return (
-
);
- }
- return (
- |
- );
- })
- }
-
- );
-};
+ })
+ }
+
+ );
+ }
+}
Row.propTypes = {
row: PropTypes.object.isRequired,
diff --git a/packages/react-bootstrap-table2/src/utils.js b/packages/react-bootstrap-table2/src/utils.js
index 4360d0d..16572b2 100644
--- a/packages/react-bootstrap-table2/src/utils.js
+++ b/packages/react-bootstrap-table2/src/utils.js
@@ -1,5 +1,6 @@
/* eslint no-empty: 0 */
/* eslint no-param-reassign: 0 */
+/* eslint prefer-rest-params: 0 */
function splitNested(str) {
return [str]
@@ -72,6 +73,29 @@ function sleep(fn, ms) {
return setTimeout(() => fn(), ms);
}
+function debounce(func, wait, immediate) {
+ let timeout;
+
+ return () => {
+ const later = () => {
+ timeout = null;
+
+ if (!immediate) {
+ func.apply(this, arguments);
+ }
+ };
+
+ const callNow = immediate && !timeout;
+
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait || 0);
+
+ if (callNow) {
+ func.appy(this, arguments);
+ }
+ };
+}
+
export default {
get,
set,
@@ -79,5 +103,6 @@ export default {
isObject,
isEmptyObject,
isDefined,
- sleep
+ sleep,
+ debounce
};
diff --git a/packages/react-bootstrap-table2/test/body.test.js b/packages/react-bootstrap-table2/test/body.test.js
index fc30b0b..717e34e 100644
--- a/packages/react-bootstrap-table2/test/body.test.js
+++ b/packages/react-bootstrap-table2/test/body.test.js
@@ -349,6 +349,30 @@ describe('Body', () => {
expect(wrapper.find(Row).get(0).props.style.backgroundColor).toBe(bgColor);
});
});
+
+ describe('if selectRow.nonSelectable is defined', () => {
+ const nonSelectableRowIndex = 1;
+ const nonSelectable = [data[nonSelectableRowIndex][keyField]];
+
+ beforeEach(() => {
+ selectRow.nonSelectable = nonSelectable;
+ wrapper = shallow(
+
+ );
+ });
+
+ it('should render Row component with correct selectable prop', () => {
+ expect(wrapper.find(Row).get(0).props.selectable).toBeTruthy();
+ expect(wrapper.find(Row).get(nonSelectableRowIndex).props.selectable).toBeFalsy();
+ });
+ });
});
describe('when selectRow.mode is ROW_SELECT_DISABLED (row was un-selectable)', () => {
diff --git a/packages/react-bootstrap-table2/test/cell-edit/wrapper.test.js b/packages/react-bootstrap-table2/test/cell-edit/wrapper.test.js
index 49ebd22..c41d336 100644
--- a/packages/react-bootstrap-table2/test/cell-edit/wrapper.test.js
+++ b/packages/react-bootstrap-table2/test/cell-edit/wrapper.test.js
@@ -146,14 +146,61 @@ describe('CellEditWrapper', () => {
});
describe('call startEditing function', () => {
+ const ridx = 1;
+ const cidx = 3;
it('should set state correctly', () => {
- const ridx = 1;
- const cidx = 3;
wrapper.instance().startEditing(ridx, cidx);
expect(wrapper.state().ridx).toEqual(ridx);
expect(wrapper.state().cidx).toEqual(cidx);
expect(wrapper.state().editing).toBeTruthy();
});
+
+ describe('if selectRow.clickToSelect is defined', () => {
+ beforeEach(() => {
+ const selectRow = { mode: 'checkbox', clickToSelect: true };
+ wrapper = shallow(
+
+ );
+ });
+
+ it('should not set state', () => {
+ wrapper.instance().startEditing(ridx, cidx);
+ expect(wrapper.state().ridx).toBeNull();
+ expect(wrapper.state().cidx).toBeDefined();
+ });
+ });
+
+ describe('if selectRow.clickToSelect and selectRow.clickToEdit is defined', () => {
+ beforeEach(() => {
+ const selectRow = { mode: 'checkbox', clickToSelect: true, clickToEdit: true };
+ wrapper = shallow(
+
+ );
+ });
+
+ it('should set state correctly', () => {
+ wrapper.instance().startEditing(ridx, cidx);
+ expect(wrapper.state().ridx).toEqual(ridx);
+ expect(wrapper.state().cidx).toEqual(cidx);
+ expect(wrapper.state().editing).toBeTruthy();
+ });
+ });
});
describe('call completeEditing function', () => {
diff --git a/packages/react-bootstrap-table2/test/row.test.js b/packages/react-bootstrap-table2/test/row.test.js
index 73d01ea..f720ee7 100644
--- a/packages/react-bootstrap-table2/test/row.test.js
+++ b/packages/react-bootstrap-table2/test/row.test.js
@@ -37,6 +37,7 @@ describe('Row', () => {
wrapper = shallow(
{
row={ row }
selectRow={ selectRow }
selected
+ selectable
/>);
});
@@ -483,9 +485,8 @@ describe('Row', () => {
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', () => {
+ describe('if selectable prop is false', () => {
beforeEach(() => {
- selectRow = { mode: 'checkbox', nonSelectable: [row.id] };
wrapper = shallow(
{
row={ row }
keyField={ keyField }
selectRow={ selectRow }
+ selectable={ false }
/>);
});
@@ -503,9 +505,8 @@ describe('Row', () => {
});
});
- describe('if selectRow.nonSelectable is defined and not contain any rowkey which is match to current row', () => {
+ describe('if selectable prop is true', () => {
beforeEach(() => {
- selectRow = { mode: 'checkbox', nonSelectable: [3, 4, 6] };
wrapper = shallow(
{
row={ row }
keyField={ keyField }
selectRow={ selectRow }
+ selectable
/>);
});
@@ -522,5 +524,157 @@ describe('Row', () => {
expect(wrapper.find(SelectionCell).prop('disabled')).toBeFalsy();
});
});
+
+ describe('if selectRow.clickToSelect is true', () => {
+ beforeEach(() => {
+ selectRow.clickToSelect = true;
+ wrapper = shallow(
+ );
+ });
+
+ it('should render Row component successfully with onClick event', () => {
+ expect(wrapper.length).toBe(1);
+ expect(wrapper.find('tr').prop('onClick')).toBeDefined();
+ });
+ });
+ });
+
+ describe('handleRowClick', () => {
+ let selectRow;
+ let onRowSelectCallBack;
+
+ describe('selectable prop is false', () => {
+ beforeEach(() => {
+ onRowSelectCallBack = sinon.stub();
+ selectRow = {
+ mode: 'checkbox',
+ clickToSelect: true,
+ onRowSelect: onRowSelectCallBack
+ };
+ wrapper = shallow(
+ );
+ wrapper.find('tr').simulate('click');
+ });
+
+ it('should not calling selectRow.onRowSelect callback', () => {
+ expect(onRowSelectCallBack.callCount).toEqual(0);
+ });
+ });
+
+ describe('selectable prop is true', () => {
+ describe('and selected prop is true', () => {
+ beforeEach(() => {
+ onRowSelectCallBack = sinon.stub();
+ selectRow = {
+ mode: 'checkbox',
+ clickToSelect: true,
+ onRowSelect: onRowSelectCallBack
+ };
+ wrapper = shallow(
+ );
+ wrapper.find('tr').simulate('click');
+ });
+
+ it('should calling selectRow.onRowSelect callback', () => {
+ expect(onRowSelectCallBack.callCount).toEqual(1);
+ });
+
+ it('should calling selectRow.onRowSelect with correct argument', () => {
+ expect(onRowSelectCallBack.calledWith(row[keyField], false)).toBeTruthy();
+ });
+ });
+
+ describe('and selected prop is false', () => {
+ beforeEach(() => {
+ onRowSelectCallBack = sinon.stub();
+ selectRow = {
+ mode: 'checkbox',
+ clickToSelect: true,
+ onRowSelect: onRowSelectCallBack
+ };
+ wrapper = shallow(
+ );
+ wrapper.find('tr').simulate('click');
+ });
+
+ it('should calling selectRow.onRowSelect callback', () => {
+ expect(onRowSelectCallBack.callCount).toEqual(1);
+ });
+
+ it('should calling selectRow.onRowSelect with correct argument', () => {
+ expect(onRowSelectCallBack.calledWith(row[keyField], true)).toBeTruthy();
+ });
+ });
+ });
+
+ describe('if cellEdit.mode is dbclick and selectRow.clickToEdit is true', () => {
+ beforeEach(() => {
+ onRowSelectCallBack = sinon.stub();
+ const cellEdit = {
+ mode: Const.DBCLICK_TO_CELL_EDIT,
+ ridx: undefined,
+ cidx: undefined,
+ onStart: sinon.stub()
+ };
+ selectRow = {
+ mode: 'checkbox',
+ clickToSelect: true,
+ clickToEdit: true,
+ onRowSelect: onRowSelectCallBack
+ };
+ wrapper = shallow(
+ );
+ wrapper.instance().handleRowClick();
+ wrapper.instance().handleRowClick();
+ });
+
+ it('should increase clickNum as 2', () => {
+ expect(wrapper.instance().clickNum).toEqual(2);
+ });
+ });
});
});
|