diff --git a/docs/README.md b/docs/README.md
index 66e6923..14c3cf3 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -38,33 +38,4 @@ Same as `.table-hover` class for adding a hover effect (grey background color) o
Same as `.table-condensed` class for makeing a table more compact by cutting cell padding in half
### cellEdit - [Object]
-Assign a valid `cellEdit` object can enable the cell editing on the cell. The default usage is click/dbclick to trigger cell editing and press `ENTER` to save cell or press `ESC` to cancel editing.
-
-> Note: The `keyField` column can't be edited
-
-Following is a `cellEdit` object:
-```js
-{
- mode: 'click',
- blurToSave: true,
- timeToCloseMessage: 2500,
- onEditing: (rowId, dataField, newValue) => { ... },
- beforeSaveCell: (oldValue, newValue, row, column) => { ... },
- afterSaveCell: (oldValue, newValue, row, column) => { ... },
- nonEditableRows: () => { ... }
-}
-```
-#### cellEdit.mode - [String]
-`cellEdit.mode` possible value is `click` and `dbclick`. **It's required value** that tell `react-bootstrap-table2` how to trigger the cell editing.
-
-#### cellEdit.blurToSave - [Bool]
-Default is `false`, enable it will be able to save the cell automatically when blur from the cell editor.
-
-#### cellEdit.nonEditableRows - [Function]
-`cellEdit.nonEditableRows` accept a callback function and expect return an array which used to restrict all the columns of some rows as non-editable. So the each item in return array should be rowkey(`keyField`)
-
-#### cellEdit.timeToCloseMessage - [Function]
-If a [`column.validator`](./columns.md#validator) defined and the new value is invalid, `react-bootstrap-table2` will popup a alert at the bottom of editor. `cellEdit.timeToCloseMessage` is a chance to let you decide how long the alert should be stay. Default is 3000 millisecond.
-
-### selectRow - [Object]
-Pass prop `selectRow` to enable row selection. For more detail, please navigate to [row selection document](./row-selection.md).
+`cellEdit` props accept an object, please see [cellEdit definition](./cell-edit-prop.md) for more detail.
diff --git a/docs/cell-edit-prop.md b/docs/cell-edit-prop.md
new file mode 100644
index 0000000..328a94e
--- /dev/null
+++ b/docs/cell-edit-prop.md
@@ -0,0 +1,136 @@
+# Properties on cellEdit prop
+* [mode (**required**)](#mode)
+* [blurToSave](#blurToSave)
+* [nonEditableRows](#nonEditableRows)
+* [timeToCloseMessage](#timeToCloseMessage)
+* [beforeSaveCell](#beforeSaveCell)
+* [afterSaveCell](#afterSaveCell)
+* [onUpdate](#onUpdate)
+* [editing](#editing)
+* [errorMessage](#errorMessage)
+* [onErrorMessageDisappear](#onErrorMessageDisappear)
+
+## cellEdit - [Object]
+Assign a valid `cellEdit` object can enable the cell editing on the cell. The default usage is click/dbclick to trigger cell editing and press `ENTER` to save cell or press `ESC` to cancel editing.
+
+> Note: The `keyField` column can't be edited
+
+Following is the shape of `cellEdit` object:
+```js
+{
+ mode: 'click',
+ blurToSave: true,
+ timeToCloseMessage: 2500,
+ editing: false|true,
+ errorMessage: '',
+ onUpdate: (rowId, dataField, newValue) => { ... },
+ beforeSaveCell: (oldValue, newValue, row, column) => { ... },
+ afterSaveCell: (oldValue, newValue, row, column) => { ... },
+ onErrorMessageDisappear: () => { ... },
+ nonEditableRows: () => { ... }
+}
+```
+
+### cellEdit.mode - [String]
+`cellEdit.mode` possible value is `click` and `dbclick`. **It's required value** that tell `react-bootstrap-table2` how to trigger the cell editing.
+
+### cellEdit.blurToSave - [Bool]
+Default is `false`, enable it will be able to save the cell automatically when blur from the cell editor.
+
+### cellEdit.nonEditableRows - [Function]
+`cellEdit.nonEditableRows` accept a callback function and expect return an array which used to restrict all the columns of some rows as non-editable. So the each item in return array should be rowkey(`keyField`)
+
+### cellEdit.timeToCloseMessage - [Function]
+If a [`column.validator`](./columns.md#validator) defined and the new value is invalid, `react-bootstrap-table2` will popup a alert at the bottom of editor. `cellEdit.timeToCloseMessage` is a chance to let you decide how long the alert should be stay. Default is 3000 millisecond.
+
+### cellEdit.beforeSaveCell - [Function]
+This callback function will be called before triggering cell update.
+
+```js
+const cellEdit = {
+ // omit...
+ beforeSaveCell: (oldValue, newValue, row, column) => { ... }
+}
+```
+
+### cellEdit.afterSaveCell - [Function]
+This callback function will be called after updating cell.
+
+```js
+const cellEdit = {
+ // omit...
+ afterSaveCell: (oldValue, newValue, row, column) => { ... }
+};
+```
+
+### cellEdit.onUpdate - [Function]
+If you want to control the cell updating process by yourself, for example, connect with `Redux` or saving data to backend database, `cellEdit.onUpdate` is a great chance you can work on it.
+
+Firsylt, `react-bootstrap-table2` allow `cellEdit.onUpdate` to return a promise:
+
+```js
+const cellEdit = {
+ // omit...
+ onUpdate: (rowId, dataField, newValue) => {
+ return apiCall().then(response => {
+ console.log('update cell to backend successfully');
+ // Actually, you dont do any thing here, we will update the new value when resolve your promise
+ })
+ .catch(err => throw new Error(err.message));
+ }
+};
+```
+
+If your promise is resolved successfully, `react-bootstrap-table2` will default help you to update the new cell value.
+If your promise is resolved failure, you can throw an `Error` instance, `react-bootstrap-table2` will show up the error message (Same behavior like [`column.validator`](./columns.md#validator)).
+
+In some case, backend will return a new value to client side and you want to apply this new value instead of the value that user input. In this situation, you can return an object which contain a `value` property:
+
+```js
+const cellEdit = {
+ // omit...
+ onUpdate: (rowId, dataField, newValue) => {
+ return apiCall().then(response => {
+ return { value: response.value }; // response.value is from your backend api
+ })
+ .catch(err => throw new Error(err.message));
+ }
+};
+```
+
+If your application integgrate with `Redux`, you may need to dispatch an action in `cellEdit.onUpdate` callback. In this circumstances, you need to return `false` explicity which `react-bootstrap-table2` will stop any operation internally and wait rerender by your application.
+
+In a simple redux application, you probably need to handle those props by your application:
+
+* [`cellEdit.editing`](#editing): Is cell still on editing or not? This value should always be `true` when saving cell failure.
+* [`cellEdit.errorMessage`](#errorMessage): Error message when save the cell failure.
+* [`cellEdit.onErrorMessageDisappear`](#onErrorMessageDisappear): This callback will be called when error message alert closed automatically.
+* `cellEdit.onUpdate`
+
+```js
+const cellEdit = {
+ editing: this.props.editing,
+ errorMessage: this.props.errorMessage,
+ onErrorMessageDisappear: () => {
+ // cleanErrorMessage is an action creator
+ this.props.dispatch(cleanErrorMessage());
+ },
+ onUpdate: (rowId, dataField, newValue) => {
+ // updateCell is an action creator
+ this.props.dispatch(updateCell(rowId, dataField, newValue)));
+ return false; // Have to return false here
+ }
+};
+```
+
+Please check [this](https://github.com/react-bootstrap-table/react-bootstrap-table2/blob/develop/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-with-redux-table.js) exmaple to learn how use `cellEdit` with a redux application
+
+### cellEdit.editing - [Bool]
+This only used when you want to control cell saving externally, `cellEdit.editing` will be a flag to tell `react-bootstrap-table2` whether currecnt editing cell is still editing or not. Because, it's possible that some error happen when you saving cell, in this situation, you need to configre this value as `false` to keep the cell as edtiable and show up an error message.
+
+### cellEdit.errorMessage - [String]
+Same as [`cellEdit.editing`](#editing). This prop is not often used. Only used when you keep the error message in your application state.
+
+### cellEdit.onErrorMessageDisappear - [Function]
+This callback function will be called when error message discard.
+
diff --git a/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-with-promise-table.js b/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-with-promise-table.js
new file mode 100644
index 0000000..c9e6874
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-with-promise-table.js
@@ -0,0 +1,75 @@
+/* eslint no-unused-vars: 0 */
+/* eslint arrow-body-style: 0 */
+import React, { Component } from 'react';
+
+import { BootstrapTableful } from 'react-bootstrap-table2';
+import Code from 'components/common/code-block';
+import { productsGenerator, sleep } from 'utils/common';
+
+const products = productsGenerator();
+
+const columns = [{
+ dataField: 'id',
+ text: 'Product ID'
+}, {
+ dataField: 'name',
+ text: 'Product Name'
+}, {
+ dataField: 'price',
+ text: 'Product Price'
+}];
+
+const sourceCode = `\
+class CellEditWithPromise extends Component {
+ handleCellEditing = (rowId, dataField, newValue) => {
+ return sleep(1000).then(() => {
+ if (dataField === 'price' && (newValue < 2000 || isNaN(newValue))) {
+ throw new Error('Product Price should bigger than $2000');
+ }
+ });
+ }
+
+ render() {
+ const cellEdit = {
+ mode: 'click',
+ blurToSave: true,
+ onUpdate: this.handleCellEditing
+ };
+
+ return (
+
+
+ { sourceCode }
+
+ );
+ }
+}
+`;
+
+class CellEditWithPromise extends Component {
+ handleCellEditing = (rowId, dataField, newValue) => {
+ return sleep(1000).then(() => {
+ if (dataField === 'price' && (newValue < 2000 || isNaN(newValue))) {
+ throw new Error('Product Price should bigger than $2000');
+ }
+ });
+ }
+
+ render() {
+ const cellEdit = {
+ mode: 'click',
+ blurToSave: true,
+ onUpdate: this.handleCellEditing
+ };
+
+ return (
+
+
+ { sourceCode }
+
+ );
+ }
+}
+
+export default CellEditWithPromise;
+
diff --git a/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-with-redux-table.js b/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-with-redux-table.js
new file mode 100644
index 0000000..d961ad1
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-with-redux-table.js
@@ -0,0 +1,212 @@
+/* eslint no-unused-vars: 0 */
+/* eslint react/prop-types: 0 */
+/* eslint arrow-body-style: 0 */
+/* eslint consistent-return: 0 */
+/* eslint no-class-assign: 0 */
+import React, { Component } from 'react';
+import thunk from 'redux-thunk';
+import { Provider, connect } from 'react-redux';
+import { createStore, applyMiddleware } from 'redux';
+import { BootstrapTableful } from 'react-bootstrap-table2';
+import Code from 'components/common/code-block';
+import { productsGenerator } from 'utils/common';
+
+const columns = [{
+ dataField: 'id',
+ text: 'Product ID'
+}, {
+ dataField: 'name',
+ text: 'Product Name'
+}, {
+ dataField: 'price',
+ text: 'Product Price'
+}];
+
+const sourceCode = `\
+/////////////////////// Action Creator ///////////////////////
+const setErrorMessage = (errorMessage = null) => {
+ return { type: 'SET_ERR_MESSAGE', errorMessage };
+};
+
+// Async Action, using redux-thunk
+const cellEditingAsync = (rowId, dataField, newValue) => {
+ return (dispatch) => {
+ setTimeout(() => {
+ if (dataField === 'price' && (newValue < 2000 || isNaN(newValue))) {
+ dispatch(setErrorMessage('Product Price should bigger than $2000'));
+ } else {
+ dispatch({ type: 'ADD_SUCCESS', rowId, dataField, newValue });
+ }
+ }, 1200);
+ };
+};
+
+/////////////////////// Component ///////////////////////
+class CellEditWithRedux extends Component {
+ // dispatch a async action
+ handleCellEditing = (rowId, dataField, newValue) => {
+ this.props.dispatch(cellEditingAsync(rowId, dataField, newValue));
+ return false;
+ }
+
+ handleErrorMsgDisappear = () => {
+ this.props.dispatch(setErrorMessage());
+ }
+
+ render() {
+ const cellEdit = {
+ mode: 'click',
+ editing: this.props.cellEditing,
+ errorMessage: this.props.errorMessage,
+ onUpdate: this.handleCellEditing,
+ onErrorMessageDisappear: this.handleErrorMsgDisappear
+ };
+
+ return (
+
+
+ { sourceCode }
+
+ );
+ }
+}
+// connect
+CellEditWithRedux = connect(state => state)(CellEditWithRedux);
+
+/////////////////////// Reducer ///////////////////////
+// initial state object and simple reducers
+const initialState = {
+ data: productsGenerator(),
+ cellEditing: false,
+ errorMessage: null
+};
+
+const reducers = (state, action) => {
+ switch (action.type) {
+ case 'ADD_SUCCESS': {
+ const { rowId, dataField, newValue } = action;
+ const data = [...state.data];
+ const rowIndex = data.findIndex(r => r.id === rowId);
+ data[rowIndex][dataField] = newValue;
+ return {
+ data,
+ cellEditing: false,
+ errorMessage: null
+ };
+ }
+ case 'SET_ERR_MESSAGE': {
+ const { errorMessage } = action;
+ return {
+ ...state,
+ cellEditing: true,
+ errorMessage
+ };
+ }
+ default: {
+ return { ...state };
+ }
+ }
+};
+
+/////////////////////// Index ///////////////////////
+const store = createStore(reducers, initialState, applyMiddleware(thunk));
+
+const Index = () => (
+
+
+
+);
+`;
+
+const setErrorMessage = (errorMessage = null) => {
+ return { type: 'SET_ERR_MESSAGE', errorMessage };
+};
+
+// Async Action, using redux-thunk
+const cellEditingAsync = (rowId, dataField, newValue) => {
+ return (dispatch) => {
+ setTimeout(() => {
+ if (dataField === 'price' && (newValue < 2000 || isNaN(newValue))) {
+ dispatch(setErrorMessage('Product Price should bigger than $2000'));
+ } else {
+ dispatch({ type: 'ADD_SUCCESS', rowId, dataField, newValue });
+ }
+ }, 1200);
+ };
+};
+
+class CellEditWithRedux extends Component {
+ // dispatch a async action
+ handleCellEditing = (rowId, dataField, newValue) => {
+ this.props.dispatch(cellEditingAsync(rowId, dataField, newValue));
+ return false;
+ }
+
+ handleErrorMsgDisappear = () => {
+ this.props.dispatch(setErrorMessage());
+ }
+
+ render() {
+ const cellEdit = {
+ mode: 'click',
+ editing: this.props.cellEditing,
+ errorMessage: this.props.errorMessage,
+ onUpdate: this.handleCellEditing,
+ onErrorMessageDisappear: this.handleErrorMsgDisappear
+ };
+
+ return (
+
+
+ { sourceCode }
+
+ );
+ }
+}
+// connect
+CellEditWithRedux = connect(state => state)(CellEditWithRedux);
+
+// initial state object and simple reducers
+const initialState = {
+ data: productsGenerator(),
+ cellEditing: false,
+ errorMessage: null
+};
+
+const reducers = (state, action) => {
+ switch (action.type) {
+ case 'ADD_SUCCESS': {
+ const { rowId, dataField, newValue } = action;
+ const data = JSON.parse(JSON.stringify(state.data));
+ const rowIndex = data.findIndex(r => r.id === rowId);
+ data[rowIndex][dataField] = newValue;
+ return {
+ data,
+ cellEditing: false,
+ errorMessage: null
+ };
+ }
+ case 'SET_ERR_MESSAGE': {
+ const { errorMessage } = action;
+ return {
+ ...state,
+ cellEditing: true,
+ errorMessage
+ };
+ }
+ default: {
+ return { ...state };
+ }
+ }
+};
+
+const store = createStore(reducers, initialState, applyMiddleware(thunk));
+
+const Index = () => (
+
+
+
+);
+
+export default Index;
+
diff --git a/packages/react-bootstrap-table2-example/package.json b/packages/react-bootstrap-table2-example/package.json
index 4e8bb00..76efab3 100644
--- a/packages/react-bootstrap-table2-example/package.json
+++ b/packages/react-bootstrap-table2-example/package.json
@@ -21,6 +21,9 @@
},
"devDependencies": {
"@storybook/react": "^3.2.8",
+ "react-redux": "^5.0.6",
+ "redux": "^3.7.2",
+ "redux-thunk": "^2.2.0",
"typed.js": "^2.0.5"
}
}
diff --git a/packages/react-bootstrap-table2-example/src/index.js b/packages/react-bootstrap-table2-example/src/index.js
index 6b2f5f9..a1af3b3 100644
--- a/packages/react-bootstrap-table2-example/src/index.js
+++ b/packages/react-bootstrap-table2-example/src/index.js
@@ -1,7 +1,12 @@
+/* eslint no-unused-vars: 0 */
+/* eslint no-debugger: 0 */
+/* eslint arrow-body-style: 0 */
import React from 'react';
import ReactDom from 'react-dom';
-import { BootstrapTableful } from 'react-bootstrap-table2';
+import { BootstrapTableful, createTable } from 'react-bootstrap-table2';
+
+require('react-bootstrap-table2/style/react-bootstrap-table.scss');
const products = [];
@@ -29,38 +34,85 @@ const columns = [{
style: {
backgroundColor: 'red'
},
- headerTitle: (column, colIndex) => {
- console.log(column);
- console.log(colIndex);
- return 'yes~~~ oh';
- },
classes: 'my-xxx'
}, {
dataField: 'name',
text: 'Product Name',
headerTitle: true,
formatter: (cell, row) =>
- ({ cell }::: ${ row.price }
)
+ ({ cell }::: ${ row.price }
),
+ validator: (newValue, row, column) => {
+ const validationForm = {
+ valid: true,
+ message: null
+ };
+ validationForm.valid = false;
+ validationForm.message = 'Invalid message';
+ return validationForm;
+ }
}, {
dataField: 'price',
text: 'Product Price',
- style: (cell, row, colIndex) => {
- console.log(cell);
- console.log(row);
- console.log(colIndex);
- return {
- backgroundColor: 'blue'
- };
+ validator: (newValue, row, column) => {
+ if (newValue < 2000) {
+ return {
+ valid: false,
+ message: 'Price should bigger than 2000'
+ };
+ }
+ return true;
}
}, {
dataField: 'nest.address',
text: 'Address'
}, {
dataField: 'nest.postcal',
- text: 'Postal'
+ text: 'Postal',
+ editable: true,
+ validator: (newValue, row, column) => true
}];
+const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
+
+const cellEdit = {
+ mode: 'click',
+ blurToSave: true,
+ // beforeSaveCell: (oldValue, newValue, row, column) => {
+ // console.log('beforeSavecell');
+ // // console.log(oldValue);
+ // // console.log(newValue);
+ // // console.log(row);
+ // // console.log(column);
+ // },
+ // afterSaveCell: (oldValue, newValue, row, column) => {
+ // console.log('aftersavecell');
+ // // console.log(oldValue);
+ // // console.log(newValue);
+ // // console.log(row);
+ // // console.log(column);
+ // }
+ onUpdate: (rowId, dataField, newValue) => {
+ return sleep(1000).then(() => {
+ // return { forceUpdate: true };
+ throw new Error('test is not successfully');
+ });
+ }
+ // nonEditableRows: () => [1, 3, 7]
+};
+
+
+// let Table = withCellEdit({
+// mode: 'click',
+// blurToSave: true,
+// onEditing: (rowId, dataField, newValue) => {
+// return sleep(1000).then(() => {
+// // return { forceUpdate: true };
+// throw new Error('test is not successfully');
+// });
+// }
+// })(BootstrapTable);
+// Table = createTable(Table);
ReactDom.render(
- ,
+ ,
document.getElementById('example'));
diff --git a/packages/react-bootstrap-table2-example/src/utils/common.js b/packages/react-bootstrap-table2-example/src/utils/common.js
index 8d95013..724e042 100644
--- a/packages/react-bootstrap-table2-example/src/utils/common.js
+++ b/packages/react-bootstrap-table2-example/src/utils/common.js
@@ -19,3 +19,5 @@ export const productsGenerator = (quantity = 5, callback) => {
}))
);
};
+
+export const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js
index 4f0d85f..d3c1bec 100644
--- a/packages/react-bootstrap-table2-example/stories/index.js
+++ b/packages/react-bootstrap-table2-example/stories/index.js
@@ -45,6 +45,8 @@ import ColumnLevelEditableTable from 'examples/cell-edit/column-level-editable-t
import CellLevelEditable from 'examples/cell-edit/cell-level-editable-table';
import CellEditHooks from 'examples/cell-edit/cell-edit-hooks-table';
import CellEditValidator from 'examples/cell-edit/cell-edit-validator-table';
+import CellEditWithPromise from 'examples/cell-edit/cell-edit-with-promise-table';
+import CellEditWithRedux from 'examples/cell-edit/cell-edit-with-redux-table';
// work on row selection
import SingleSelectionTable from 'examples/row-selection/single-selection';
@@ -102,8 +104,11 @@ storiesOf('Cell Editing', module)
.add('Column Level Editable', () => )
.add('Cell Level Editable', () => )
.add('Rich Hook Functions', () => )
- .add('Validation', () => );
+ .add('Validation', () => )
+ .add('Async Cell Editing(Promise)', () => )
+ .add('Async Cell Editing(Redux)', () => );
storiesOf('Row Selection', module)
.add('Single selection', () => )
.add('Multiple selection', () => );
+
diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js
index 2aaf510..d0332c0 100644
--- a/packages/react-bootstrap-table2/src/bootstrap-table.js
+++ b/packages/react-bootstrap-table2/src/bootstrap-table.js
@@ -6,36 +6,32 @@ import cs from 'classnames';
import Header from './header';
import Caption from './caption';
import Body from './body';
-import Store from './store/base';
import PropsBaseResolver from './props-resolver';
import Const from './const';
-import _ from './utils';
class BootstrapTable extends PropsBaseResolver(Component) {
constructor(props) {
super(props);
this.validateProps();
- const { store } = this.props;
- this.store = !store ? new Store(props) : store;
this.handleSort = this.handleSort.bind(this);
- this.startEditing = this.startEditing.bind(this);
- this.escapeEditing = this.escapeEditing.bind(this);
- this.completeEditing = this.completeEditing.bind(this);
this.handleRowSelect = this.handleRowSelect.bind(this);
this.handleAllRowsSelect = this.handleAllRowsSelect.bind(this);
this.state = {
- data: this.store.get(),
- selectedRowKeys: this.store.getSelectedRowKeys(),
- currEditCell: {
- ridx: null,
- cidx: null
- }
+ data: props.store.get(),
+ selectedRowKeys: props.store.getSelectedRowKeys()
};
}
+ componentWillReceiveProps(nextProps) {
+ this.setState({
+ data: nextProps.store.get()
+ });
+ }
+
render() {
const {
+ store,
columns,
keyField,
striped,
@@ -54,9 +50,10 @@ class BootstrapTable extends PropsBaseResolver(Component) {
});
const cellEditInfo = this.resolveCellEditProps({
- onStart: this.startEditing,
- onEscape: this.escapeEditing,
- onComplete: this.completeEditing
+ onStart: this.props.onStartEditing,
+ onEscape: this.props.onEscapeEditing,
+ onUpdate: this.props.onCellUpdate,
+ currEditCell: this.props.currEditCell
});
const cellSelectionInfo = this.resolveCellSelectionProps({
@@ -64,7 +61,9 @@ class BootstrapTable extends PropsBaseResolver(Component) {
});
const headerCellSelectionInfo = this.resolveHeaderCellSelectionProps({
- onAllRowsSelect: this.handleAllRowsSelect
+ onAllRowsSelect: this.handleAllRowsSelect,
+ selected: store.selected,
+ allRowsSelected: store.isAllRowsSelected()
});
return (
@@ -73,8 +72,8 @@ class BootstrapTable extends PropsBaseResolver(Component) {
{ caption }
@@ -100,10 +99,10 @@ class BootstrapTable extends PropsBaseResolver(Component) {
* @param {Boolean} checked - next checked status of input button.
*/
handleRowSelect(rowKey, checked) {
- const { mode } = this.props.selectRow;
+ const { selectRow: { mode }, store } = this.props;
const { ROW_SELECT_SINGLE } = Const;
- let currSelected = [...this.store.getSelectedRowKeys()];
+ let currSelected = [...store.getSelectedRowKeys()];
if (mode === ROW_SELECT_SINGLE) { // when select mode is radio
currSelected = [rowKey];
@@ -113,7 +112,7 @@ class BootstrapTable extends PropsBaseResolver(Component) {
currSelected = currSelected.filter(value => value !== rowKey);
}
- this.store.setSelectedRowKeys(currSelected);
+ store.setSelectedRowKeys(currSelected);
this.setState(() => ({
selectedRowKeys: currSelected
@@ -125,14 +124,15 @@ class BootstrapTable extends PropsBaseResolver(Component) {
* @param {Boolean} option - customized result for all rows selection
*/
handleAllRowsSelect(option) {
- const selected = this.store.isAnySelectedRow();
+ const { store } = this.props;
+ const selected = store.isAnySelectedRow();
// set next status of all row selected by store.selected or customizing by user.
const result = option || !selected;
- const currSelected = result ? this.store.selectAllRowKeys() : [];
+ const currSelected = result ? store.selectAllRowKeys() : [];
- this.store.setSelectedRowKeys(currSelected);
+ store.setSelectedRowKeys(currSelected);
this.setState(() => ({
selectedRowKeys: currSelected
@@ -140,44 +140,12 @@ class BootstrapTable extends PropsBaseResolver(Component) {
}
handleSort(column) {
- this.store.sortBy(column);
+ const { store } = this.props;
+ store.sortBy(column);
this.setState(() => {
return {
- data: this.store.get()
- };
- });
- }
-
- completeEditing(row, column, newValue) {
- const { cellEdit, keyField } = this.props;
- const { beforeSaveCell, onEditing, afterSaveCell } = cellEdit;
- const oldValue = _.get(row, column.dataField);
- const rowId = _.get(row, keyField);
- if (_.isFunction(beforeSaveCell)) beforeSaveCell(oldValue, newValue, row, column);
- onEditing(rowId, column.dataField, newValue);
- if (_.isFunction(afterSaveCell)) afterSaveCell(oldValue, newValue, row, column);
-
- this.setState(() => {
- return {
- data: this.store.get(),
- currEditCell: { ridx: null, cidx: null }
- };
- });
- }
-
- startEditing(ridx, cidx) {
- this.setState(() => {
- return {
- currEditCell: { ridx, cidx }
- };
- });
- }
-
- escapeEditing() {
- this.setState(() => {
- return {
- currEditCell: { ridx: null, cidx: null }
+ data: store.get()
};
});
}
@@ -199,15 +167,27 @@ BootstrapTable.propTypes = {
]),
cellEdit: PropTypes.shape({
mode: PropTypes.oneOf([Const.CLICK_TO_CELL_EDIT, Const.DBCLICK_TO_CELL_EDIT]).isRequired,
- onEditing: PropTypes.func.isRequired,
+ onUpdate: PropTypes.func,
+ onErrorMessageDisappear: PropTypes.func,
blurToSave: PropTypes.bool,
beforeSaveCell: PropTypes.func,
afterSaveCell: PropTypes.func,
nonEditableRows: PropTypes.func,
- timeToCloseMessage: PropTypes.number
+ editing: PropTypes.bool,
+ timeToCloseMessage: PropTypes.number,
+ errorMessage: PropTypes.string
}),
selectRow: PropTypes.shape({
mode: PropTypes.oneOf([Const.ROW_SELECT_SINGLE, Const.ROW_SELECT_MULTIPLE]).isRequired
+ }),
+ onCellUpdate: PropTypes.func,
+ onStartEditing: PropTypes.func,
+ onEscapeEditing: PropTypes.func,
+ currEditCell: PropTypes.shape({
+ ridx: PropTypes.number,
+ cidx: PropTypes.number,
+ message: PropTypes.string,
+ editing: PropTypes.bool
})
};
diff --git a/packages/react-bootstrap-table2/src/cell-edit-wrapper.js b/packages/react-bootstrap-table2/src/cell-edit-wrapper.js
new file mode 100644
index 0000000..404d796
--- /dev/null
+++ b/packages/react-bootstrap-table2/src/cell-edit-wrapper.js
@@ -0,0 +1,105 @@
+/* eslint arrow-body-style: 0 */
+/* eslint react/prop-types: 0 */
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import _ from './utils';
+
+class CellEditWrapper extends Component {
+ constructor(props) {
+ super(props);
+ this.startEditing = this.startEditing.bind(this);
+ this.escapeEditing = this.escapeEditing.bind(this);
+ this.completeEditing = this.completeEditing.bind(this);
+ this.handleCellUpdate = this.handleCellUpdate.bind(this);
+ this.updateEditingWithErr = this.updateEditingWithErr.bind(this);
+ this.state = {
+ ridx: null,
+ cidx: null,
+ message: null,
+ editing: false
+ };
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.cellEdit) {
+ if (nextProps.cellEdit.editing) {
+ this.setState(() => {
+ return {
+ ...this.state,
+ message: nextProps.cellEdit.errorMessage
+ };
+ });
+ } else {
+ this.escapeEditing();
+ }
+ }
+ }
+
+ handleCellUpdate(row, column, newValue) {
+ const { keyField, cellEdit, onUpdateCell } = this.props;
+ const { beforeSaveCell, afterSaveCell } = cellEdit;
+ const oldValue = _.get(row, column.dataField);
+ const rowId = _.get(row, keyField);
+ if (_.isFunction(beforeSaveCell)) beforeSaveCell(oldValue, newValue, row, column);
+ if (onUpdateCell(rowId, column.dataField, newValue)) {
+ if (_.isFunction(afterSaveCell)) afterSaveCell(oldValue, newValue, row, column);
+ this.completeEditing();
+ }
+ }
+
+ completeEditing() {
+ this.setState(() => {
+ return {
+ ridx: null,
+ cidx: null,
+ message: null,
+ editing: false
+ };
+ });
+ }
+
+ startEditing(ridx, cidx) {
+ this.setState(() => {
+ return {
+ ridx,
+ cidx,
+ editing: true
+ };
+ });
+ }
+
+ escapeEditing() {
+ this.setState(() => {
+ return {
+ ridx: null,
+ cidx: null,
+ editing: false
+ };
+ });
+ }
+
+ updateEditingWithErr(message) {
+ this.setState(() => {
+ return {
+ ...this.state,
+ message
+ };
+ });
+ }
+
+ render() {
+ return React.cloneElement(this.props.elem, {
+ onCellUpdate: this.handleCellUpdate,
+ onStartEditing: this.startEditing,
+ onEscapeEditing: this.escapeEditing,
+ currEditCell: { ...this.state }
+ });
+ }
+}
+
+CellEditWrapper.propTypes = {
+ elem: PropTypes.element.isRequired,
+ onUpdateCell: PropTypes.func.isRequired
+};
+
+export default CellEditWrapper;
diff --git a/packages/react-bootstrap-table2/src/editing-cell.js b/packages/react-bootstrap-table2/src/editing-cell.js
index 97320c9..339426c 100644
--- a/packages/react-bootstrap-table2/src/editing-cell.js
+++ b/packages/react-bootstrap-table2/src/editing-cell.js
@@ -23,6 +23,15 @@ class EditingCell extends Component {
};
}
+ componentWillReceiveProps({ message }) {
+ if (_.isDefined(message)) {
+ this.createTimer();
+ this.setState(() => {
+ return { invalidMessage: message };
+ });
+ }
+ }
+
componentWillUnmount() {
this.clearTimer();
}
@@ -33,24 +42,30 @@ class EditingCell extends Component {
}
}
- beforeComplete(row, column, newValue) {
+ createTimer() {
this.clearTimer();
- const { onComplete, timeToCloseMessage } = this.props;
+ const { timeToCloseMessage, onErrorMessageDisappear } = this.props;
+ this.indicatorTimer = _.sleep(() => {
+ this.setState(() => {
+ return { invalidMessage: null };
+ });
+ if (_.isFunction(onErrorMessageDisappear)) onErrorMessageDisappear();
+ }, timeToCloseMessage);
+ }
+
+ beforeComplete(row, column, newValue) {
+ const { onUpdate } = this.props;
if (_.isFunction(column.validator)) {
const validateForm = column.validator(newValue, row, column);
if (_.isObject(validateForm) && !validateForm.valid) {
this.setState(() => {
return { invalidMessage: validateForm.message };
});
- this.indicatorTimer = setTimeout(() => {
- this.setState(() => {
- return { invalidMessage: null };
- });
- }, timeToCloseMessage);
+ this.createTimer();
return;
}
}
- onComplete(row, column, newValue);
+ onUpdate(row, column, newValue);
}
handleBlur() {
@@ -90,7 +105,8 @@ class EditingCell extends Component {
onBlur: this.handleBlur
};
- const editorClass = invalidMessage ? cs('animated', 'shake') : null;
+ const hasError = _.isDefined(invalidMessage);
+ const editorClass = hasError ? cs('animated', 'shake') : null;
return (
- { invalidMessage ? : null }
+ { hasError ? : null }
|
);
}
@@ -108,7 +124,7 @@ class EditingCell extends Component {
EditingCell.propTypes = {
row: PropTypes.object.isRequired,
column: PropTypes.object.isRequired,
- onComplete: PropTypes.func.isRequired,
+ onUpdate: PropTypes.func.isRequired,
onEscape: PropTypes.func.isRequired,
timeToCloseMessage: PropTypes.number
};
diff --git a/packages/react-bootstrap-table2/src/props-resolver/index.js b/packages/react-bootstrap-table2/src/props-resolver/index.js
index f01790c..5e227f8 100644
--- a/packages/react-bootstrap-table2/src/props-resolver/index.js
+++ b/packages/react-bootstrap-table2/src/props-resolver/index.js
@@ -18,13 +18,12 @@ export default ExtendBase =>
return this.props.data.length === 0;
}
- resolveCellEditProps(options) {
+ resolveCellEditProps(options = { currEditCell: null }) {
const { cellEdit } = this.props;
- const { currEditCell } = this.state;
const nonEditableRows =
(cellEdit && _.isFunction(cellEdit.nonEditableRows)) ? cellEdit.nonEditableRows() : [];
const cellEditInfo = {
- ...currEditCell,
+ ...options.currEditCell,
nonEditableRows
};
@@ -72,9 +71,9 @@ export default ExtendBase =>
* @returns {String} result.mode - input type of row selection or disabled.
* @returns {String} result.checkedStatus - checkbox status depending on selected rows counts
*/
- resolveHeaderCellSelectionProps(options) {
- const { selected } = this.store;
+ resolveHeaderCellSelectionProps(options = {}) {
const { selectRow } = this.props;
+ const { allRowsSelected, selected = [], ...rest } = options;
const {
ROW_SELECT_DISABLED, CHECKBOX_STATUS_CHECKED,
CHECKBOX_STATUS_INDETERMINATE, CHECKBOX_STATUS_UNCHECKED
@@ -83,8 +82,6 @@ export default ExtendBase =>
if (_.isDefined(selectRow)) {
let checkedStatus;
- const allRowsSelected = this.store.isAllRowsSelected();
-
// checkbox status depending on selected rows counts
if (allRowsSelected) checkedStatus = CHECKBOX_STATUS_CHECKED;
else if (selected.length === 0) checkedStatus = CHECKBOX_STATUS_UNCHECKED;
@@ -92,7 +89,7 @@ export default ExtendBase =>
return {
...selectRow,
- ...options,
+ ...rest,
checkedStatus
};
}
diff --git a/packages/react-bootstrap-table2/src/stateful-layer.js b/packages/react-bootstrap-table2/src/stateful-layer.js
index c6a0133..8ddfa77 100644
--- a/packages/react-bootstrap-table2/src/stateful-layer.js
+++ b/packages/react-bootstrap-table2/src/stateful-layer.js
@@ -1,26 +1,72 @@
/* eslint arrow-body-style: 0 */
+/* eslint react/jsx-no-bind: 0 */
+/* eslint no-return-assign: 0 */
+/* eslint react/prop-types: 0 */
import React, { Component } from 'react';
import Store from './store/base';
+import CellEditWrapper from './cell-edit-wrapper';
+import _ from './utils';
const withStateful = (Base) => {
class StatefulComponent extends Component {
constructor(props) {
super(props);
this.store = new Store(props);
- this.edit = this.edit.bind(this);
+ this.handleUpdateCell = this.handleUpdateCell.bind(this);
}
- edit(rowId, dataField, newValue) {
- this.store.edit(rowId, dataField, newValue);
+ componentWillReceiveProps(nextProps) {
+ this.store.set(nextProps.data);
+ }
+
+ handleUpdateCell(rowId, dataField, newValue) {
+ const { cellEdit } = this.props;
+ // handle cell editing internal
+ if (!cellEdit.onUpdate) {
+ this.store.edit(rowId, dataField, newValue);
+ return true;
+ }
+
+ // handle cell editing external
+ const aPromise = cellEdit.onUpdate(rowId, dataField, newValue);
+ if (_.isDefined(aPromise) && aPromise !== false) { // TODO: should be a promise here
+ aPromise.then((result = true) => {
+ const response = result === true ? {} : result;
+ if (_.isObject(response)) {
+ const { value } = response;
+ this.store.edit(rowId, dataField, value || newValue);
+ this.table.completeEditing();
+ }
+ }).catch((e) => {
+ this.table.updateEditingWithErr(e.message);
+ });
+ }
+ return false;
+ }
+
+ renderCellEdit(elem) {
+ return (
+ this.table = node }
+ elem={ elem }
+ onUpdateCell={ this.handleUpdateCell }
+ />
+ );
}
render() {
- const { props } = this;
- const newProps = { ...props };
- if (newProps.cellEdit && !newProps.cellEdit.onEditing) {
- newProps.cellEdit.onEditing = this.edit;
+ const baseProps = {
+ ...this.props,
+ store: this.store
+ };
+
+ let element = React.createElement(Base, baseProps);
+ if (this.props.cellEdit) {
+ element = this.renderCellEdit(element);
}
- return ;
+ return element;
}
}
return StatefulComponent;
diff --git a/packages/react-bootstrap-table2/src/store/base.js b/packages/react-bootstrap-table2/src/store/base.js
index 31a96cd..6006861 100644
--- a/packages/react-bootstrap-table2/src/store/base.js
+++ b/packages/react-bootstrap-table2/src/store/base.js
@@ -1,3 +1,4 @@
+/* eslint class-methods-use-this: 0 */
import { sort } from './sort';
import Const from '../const';
import _ from '../utils';
@@ -6,7 +7,7 @@ export default class Store {
constructor(props) {
const { data, keyField } = props;
this.keyField = keyField;
- this.data = data ? data.slice() : [];
+ this.set(data);
this.sortOrder = undefined;
this.sortField = undefined;
@@ -37,6 +38,10 @@ export default class Store {
return this.data;
}
+ set(data) {
+ this.data = data ? JSON.parse(JSON.stringify(data)) : [];
+ }
+
getRowByRowId(rowId) {
return this.get().find(row => _.get(row, this.keyField) === rowId);
}
diff --git a/packages/react-bootstrap-table2/src/utils.js b/packages/react-bootstrap-table2/src/utils.js
index bc4febf..4360d0d 100644
--- a/packages/react-bootstrap-table2/src/utils.js
+++ b/packages/react-bootstrap-table2/src/utils.js
@@ -68,11 +68,16 @@ function isDefined(value) {
return typeof value !== 'undefined' && value !== null;
}
+function sleep(fn, ms) {
+ return setTimeout(() => fn(), ms);
+}
+
export default {
get,
set,
isFunction,
isObject,
isEmptyObject,
- isDefined
+ isDefined,
+ sleep
};
diff --git a/packages/react-bootstrap-table2/test/bootstrap-table.test.js b/packages/react-bootstrap-table2/test/bootstrap-table.test.js
index 2ee2b96..95cc99e 100644
--- a/packages/react-bootstrap-table2/test/bootstrap-table.test.js
+++ b/packages/react-bootstrap-table2/test/bootstrap-table.test.js
@@ -3,6 +3,7 @@ import sinon from 'sinon';
import { shallow } from 'enzyme';
import Caption from '../src/caption';
+import Store from '../src/store/base';
import Header from '../src/header';
import Body from '../src/body';
import BootstrapTable from '../src/bootstrap-table';
@@ -26,9 +27,12 @@ describe('BootstrapTable', () => {
name: 'B'
}];
+ const store = new Store({ data });
+
describe('simplest table', () => {
beforeEach(() => {
- wrapper = shallow();
+ wrapper = shallow(
+ );
});
it('should render successfully', () => {
@@ -40,9 +44,8 @@ describe('BootstrapTable', () => {
});
it('should have correct default state', () => {
- expect(wrapper.state().currEditCell).toBeDefined();
- expect(wrapper.state().currEditCell.ridx).toBeNull();
- expect(wrapper.state().currEditCell.cidx).toBeNull();
+ expect(wrapper.state().data).toBeDefined();
+ expect(wrapper.state().data).toEqual(store.get());
});
it('should have table-bordered class as default', () => {
@@ -52,7 +55,8 @@ describe('BootstrapTable', () => {
describe('when hover props is true', () => {
beforeEach(() => {
- wrapper = shallow();
+ wrapper = shallow(
+ );
});
it('should have table-hover class on table', () => {
@@ -62,7 +66,8 @@ describe('BootstrapTable', () => {
describe('when striped props is true', () => {
beforeEach(() => {
- wrapper = shallow();
+ wrapper = shallow(
+ );
});
it('should have table-striped class on table', () => {
@@ -72,7 +77,8 @@ describe('BootstrapTable', () => {
describe('when condensed props is true', () => {
beforeEach(() => {
- wrapper = shallow();
+ wrapper = shallow(
+ );
});
it('should have table-condensed class on table', () => {
@@ -82,7 +88,8 @@ describe('BootstrapTable', () => {
describe('when bordered props is false', () => {
beforeEach(() => {
- wrapper = shallow();
+ wrapper = shallow(
+ );
});
it('should not have table-condensed class on table', () => {
@@ -94,6 +101,7 @@ describe('BootstrapTable', () => {
beforeEach(() => {
wrapper = shallow(
test }
keyField="id"
columns={ columns }
@@ -111,6 +119,12 @@ describe('BootstrapTable', () => {
describe('when cellEdit props is defined', () => {
const nonEditableRows = [data[1].id];
+ const currEditCell = {
+ ridx: 1,
+ cidx: 2,
+ message: null,
+ editing: false
+ };
const cellEdit = {
mode: Const.CLICK_TO_CELL_EDIT,
onEditing: sinon.stub(),
@@ -124,7 +138,12 @@ describe('BootstrapTable', () => {
columns={ columns }
data={ data }
bordered={ false }
+ store={ store }
cellEdit={ cellEdit }
+ onCellUpdate={ sinon.stub() }
+ onStartEditing={ sinon.stub() }
+ onEscapeEditing={ sinon.stub() }
+ currEditCell={ currEditCell }
/>
);
});
@@ -133,11 +152,13 @@ describe('BootstrapTable', () => {
const body = wrapper.find(Body);
expect(body.length).toBe(1);
expect(body.props().cellEdit.nonEditableRows).toEqual(nonEditableRows);
- expect(body.props().cellEdit.ridx).toEqual(wrapper.state().currEditCell.ridx);
- expect(body.props().cellEdit.cidx).toEqual(wrapper.state().currEditCell.cidx);
+ expect(body.props().cellEdit.ridx).toEqual(currEditCell.ridx);
+ expect(body.props().cellEdit.cidx).toEqual(currEditCell.cidx);
+ expect(body.props().cellEdit.message).toEqual(currEditCell.message);
+ expect(body.props().cellEdit.editing).toEqual(currEditCell.editing);
expect(body.props().cellEdit.onStart).toBeDefined();
expect(body.props().cellEdit.onEscape).toBeDefined();
- expect(body.props().cellEdit.onComplete).toBeDefined();
+ expect(body.props().cellEdit.onUpdate).toBeDefined();
});
});
@@ -149,6 +170,7 @@ describe('BootstrapTable', () => {
wrapper = shallow(
{
wrapper = shallow(
{
wrapper = shallow(
@@ -208,7 +232,7 @@ describe('BootstrapTable', () => {
describe('when customized option was not given', () => {
describe('when nothing was selected', () => {
it('should select all rows', () => {
- wrapper.instance().store.setSelectedRowKeys([]);
+ store.setSelectedRowKeys([]);
wrapper.instance().handleAllRowsSelect();
@@ -218,7 +242,7 @@ describe('BootstrapTable', () => {
describe('when one or more than one row was selected', () => {
it('should unselect all rows', () => {
- wrapper.instance().store.setSelectedRowKeys([1]);
+ store.setSelectedRowKeys([1]);
wrapper.instance().handleAllRowsSelect();
@@ -238,7 +262,7 @@ describe('BootstrapTable', () => {
describe('when option is falsy', () => {
it('should unselect all rows', () => {
- wrapper.instance().store.setSelectedRowKeys([1]);
+ store.setSelectedRowKeys([1]);
wrapper.instance().handleAllRowsSelect(false);
diff --git a/packages/react-bootstrap-table2/test/cell-edit-wrapper.test.js b/packages/react-bootstrap-table2/test/cell-edit-wrapper.test.js
new file mode 100644
index 0000000..cfea0cf
--- /dev/null
+++ b/packages/react-bootstrap-table2/test/cell-edit-wrapper.test.js
@@ -0,0 +1,268 @@
+import React from 'react';
+import sinon from 'sinon';
+import { shallow } from 'enzyme';
+
+import Store from '../src/store/base';
+import BootstrapTable from '../src/bootstrap-table';
+import CellEditWrapper from '../src/cell-edit-wrapper';
+
+describe('CellEditWrapper', () => {
+ let wrapper;
+ let elem;
+
+ const columns = [{
+ dataField: 'id',
+ text: 'ID'
+ }, {
+ dataField: 'name',
+ text: 'Name'
+ }];
+
+ const data = [{
+ id: 1,
+ name: 'A'
+ }, {
+ id: 2,
+ name: 'B'
+ }];
+
+ const cellEdit = {
+ mode: 'click'
+ };
+
+ const keyField = 'id';
+
+ const store = new Store({ data, keyField });
+
+ beforeEach(() => {
+ elem = React.createElement(BootstrapTable, { data, cellEdit, columns, keyField, store });
+ wrapper = shallow(
+
+ );
+ });
+
+ it('should render CellEditWrapper correctly', () => {
+ expect(wrapper.length).toBe(1);
+ expect(wrapper.find(BootstrapTable)).toBeDefined();
+ });
+
+ it('should have correct state', () => {
+ expect(wrapper.state().ridx).toBeNull();
+ expect(wrapper.state().cidx).toBeNull();
+ expect(wrapper.state().message).toBeNull();
+ expect(wrapper.state().editing).toBeFalsy();
+ });
+
+ it('should inject correct props to elem', () => {
+ expect(wrapper.props().onCellUpdate).toBeDefined();
+ expect(wrapper.props().onStartEditing).toBeDefined();
+ expect(wrapper.props().onEscapeEditing).toBeDefined();
+ expect(wrapper.props().currEditCell).toBeDefined();
+ expect(wrapper.props().currEditCell.ridx).toBeNull();
+ expect(wrapper.props().currEditCell.cidx).toBeNull();
+ expect(wrapper.props().currEditCell.message).toBeNull();
+ expect(wrapper.props().currEditCell.editing).toBeFalsy();
+ });
+
+ describe('when receive new cellEdit prop', () => {
+ const spy = jest.spyOn(CellEditWrapper.prototype, 'escapeEditing');
+
+ describe('and cellEdit.editing is false', () => {
+ beforeEach(() => {
+ elem = React.createElement(BootstrapTable, { data, cellEdit, columns, keyField, store });
+ wrapper = shallow(
+
+ );
+ wrapper.setProps({ cellEdit: { ...cellEdit, editing: false } });
+ });
+
+ it('should call escapeEditing', () => {
+ expect(spy).toHaveBeenCalled();
+ });
+
+ it('should have correct state', () => {
+ expect(wrapper.state().ridx).toBeNull();
+ expect(wrapper.state().cidx).toBeNull();
+ expect(wrapper.state().message).toBeNull();
+ expect(wrapper.state().editing).toBeFalsy();
+ });
+ });
+
+ describe('and cellEdit.editing is true', () => {
+ const errorMessage = 'test';
+ const ridx = 1;
+ const cidx = 2;
+
+ beforeEach(() => {
+ elem = React.createElement(BootstrapTable, { data, cellEdit, columns, keyField, store });
+ wrapper = shallow(
+
+ );
+ wrapper.setState({ ridx, cidx, editing: true });
+ wrapper.setProps({ cellEdit: { ...cellEdit, editing: true, errorMessage } });
+ });
+
+ it('should have correct state', () => {
+ expect(wrapper.state().ridx).toEqual(ridx);
+ expect(wrapper.state().cidx).toEqual(cidx);
+ expect(wrapper.state().editing).toBeTruthy();
+ expect(wrapper.state().message).toEqual(errorMessage);
+ });
+ });
+ });
+
+ describe('call updateEditingWithErr function', () => {
+ it('should set state.message correctly', () => {
+ const message = 'test';
+ wrapper.instance().updateEditingWithErr(message);
+ expect(wrapper.state().message).toEqual(message);
+ });
+ });
+
+ describe('call escapeEditing function', () => {
+ it('should set state correctly', () => {
+ wrapper.instance().escapeEditing();
+ expect(wrapper.state().ridx).toBeNull();
+ expect(wrapper.state().cidx).toBeNull();
+ expect(wrapper.state().editing).toBeFalsy();
+ });
+ });
+
+ describe('call startEditing function', () => {
+ 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('call completeEditing function', () => {
+ it('should set state correctly', () => {
+ wrapper.instance().completeEditing();
+ expect(wrapper.state().ridx).toBeNull();
+ expect(wrapper.state().cidx).toBeNull();
+ expect(wrapper.state().message).toBeNull();
+ expect(wrapper.state().editing).toBeFalsy();
+ });
+ });
+
+ describe('call handleCellUpdate function', () => {
+ let onUpdateCellCallBack;
+ const row = data[0];
+ const column = columns[1];
+ const newValue = 'new name';
+
+ beforeEach(() => {
+ onUpdateCellCallBack = sinon.stub().returns(true);
+ wrapper = shallow(
+
+ );
+ wrapper.instance().handleCellUpdate(row, column, newValue);
+ });
+
+ afterEach(() => { onUpdateCellCallBack.reset(); });
+
+ it('should calling onUpdateCell callback correctly', () => {
+ expect(onUpdateCellCallBack.callCount).toBe(1);
+ expect(onUpdateCellCallBack.calledWith(row.id, column.dataField, newValue)).toBe(true);
+ });
+
+ describe('when onUpdateCell function return true', () => {
+ const spy = jest.spyOn(CellEditWrapper.prototype, 'completeEditing');
+
+ it('should calling completeEditing function', () => {
+ expect(spy).toHaveBeenCalled();
+ });
+
+ describe('if cellEdit.afterSaveCell prop defined', () => {
+ const aftereSaveCellCallBack = sinon.stub();
+ beforeEach(() => {
+ cellEdit.beforeSaveCell = aftereSaveCellCallBack;
+ wrapper = shallow(
+
+ );
+ wrapper.instance().handleCellUpdate(row, column, newValue);
+ });
+
+ it('should calling cellEdit.afterSaveCell correctly', () => {
+ expect(aftereSaveCellCallBack.callCount).toBe(1);
+ expect(aftereSaveCellCallBack.calledWith(
+ row[column.dataField], newValue, row, column)
+ ).toBe(true);
+ });
+ });
+ });
+
+ describe('when onUpdateCell function return false', () => {
+ const spy = jest.spyOn(CellEditWrapper.prototype, 'completeEditing');
+
+ beforeEach(() => {
+ onUpdateCellCallBack = sinon.stub().returns(false);
+ wrapper = shallow(
+
+ );
+ wrapper.instance().handleCellUpdate(row, column, newValue);
+ });
+
+ it('shouldn\'t calling completeEditing function', () => {
+ expect(spy).toHaveBeenCalled();
+ });
+ });
+
+ describe('if cellEdit.beforeSaveCell prop defined', () => {
+ const beforeSaveCellCallBack = sinon.stub();
+ beforeEach(() => {
+ cellEdit.beforeSaveCell = beforeSaveCellCallBack;
+ wrapper = shallow(
+
+ );
+ wrapper.instance().handleCellUpdate(row, column, newValue);
+ });
+
+ it('should calling cellEdit.beforeSaveCell correctly', () => {
+ expect(beforeSaveCellCallBack.callCount).toBe(1);
+ expect(beforeSaveCellCallBack.calledWith(
+ row[column.dataField], newValue, row, column)
+ ).toBe(true);
+ });
+ });
+ });
+});
diff --git a/packages/react-bootstrap-table2/test/editing-cell.test.js b/packages/react-bootstrap-table2/test/editing-cell.test.js
index 864b7c4..9523f60 100644
--- a/packages/react-bootstrap-table2/test/editing-cell.test.js
+++ b/packages/react-bootstrap-table2/test/editing-cell.test.js
@@ -10,8 +10,8 @@ import EditorIndicator from '../src/editor-indicator';
describe('EditingCell', () => {
let wrapper;
+ let onUpdate;
let onEscape;
- let onComplete;
const row = {
id: 1,
name: 'A'
@@ -23,14 +23,14 @@ describe('EditingCell', () => {
};
beforeEach(() => {
- onComplete = sinon.stub();
onEscape = sinon.stub();
+ onUpdate = sinon.stub();
wrapper = shallow(
);
});
@@ -55,12 +55,12 @@ describe('EditingCell', () => {
expect(indicator.length).toEqual(0);
});
- it('when press ENTER on TextEditor should call onComplete correctly', () => {
+ it('when press ENTER on TextEditor should call onUpdate correctly', () => {
const newValue = 'test';
const textEditor = wrapper.find(TextEditor);
textEditor.simulate('keyDown', { keyCode: 13, currentTarget: { value: newValue } });
- expect(onComplete.callCount).toBe(1);
- expect(onComplete.calledWith(row, column, newValue)).toBe(true);
+ expect(onUpdate.callCount).toBe(1);
+ expect(onUpdate.calledWith(row, column, newValue)).toBe(true);
});
it('when press ESC on TextEditor should call onEscape correctly', () => {
@@ -82,19 +82,19 @@ describe('EditingCell', () => {
);
});
- it('when blur from TextEditor should call onComplete correctly', () => {
+ it('when blur from TextEditor should call onUpdate correctly', () => {
const textEditor = wrapper.find(TextEditor);
textEditor.simulate('blur');
- expect(onComplete.callCount).toBe(1);
- expect(onComplete.calledWith(row, column, `${row[column.dataField]}`)).toBe(true);
+ expect(onUpdate.callCount).toBe(1);
+ expect(onUpdate.calledWith(row, column, `${row[column.dataField]}`)).toBe(true);
});
});
@@ -121,8 +121,8 @@ describe('EditingCell', () => {
expect(validatorCallBack.calledWith(newValue, row, column)).toBe(true);
});
- it('should not call onComplete', () => {
- expect(onComplete.callCount).toBe(0);
+ it('should not call onUpdate', () => {
+ expect(onUpdate.callCount).toBe(0);
});
it('should set indicatorTimer successfully', () => {
@@ -164,8 +164,8 @@ describe('EditingCell', () => {
expect(validatorCallBack.calledWith(newValue, row, column)).toBe(true);
});
- it('should call onComplete', () => {
- expect(onComplete.callCount).toBe(1);
+ it('should call onUpdate', () => {
+ expect(onUpdate.callCount).toBe(1);
});
});
});
diff --git a/packages/react-bootstrap-table2/test/props-resolver/index.test.js b/packages/react-bootstrap-table2/test/props-resolver/index.test.js
index e865126..840db69 100644
--- a/packages/react-bootstrap-table2/test/props-resolver/index.test.js
+++ b/packages/react-bootstrap-table2/test/props-resolver/index.test.js
@@ -86,18 +86,6 @@ describe('TableResolver', () => {
expect(cellEdit).toBeDefined();
expect(cellEdit.mode).toEqual(Const.UNABLE_TO_CELL_EDIT);
expect(cellEdit.nonEditableRows.length).toEqual(0);
- expect(cellEdit.ridx).toBeNull();
- expect(cellEdit.cidx).toBeNull();
- });
-
- it('should resolve a default cellEdit instance even if state.currEditCell changed', () => {
- const ridx = 1;
- const cidx = 1;
- wrapper.setState({ currEditCell: { ridx, cidx } });
- const cellEdit = wrapper.instance().resolveCellEditProps();
- expect(cellEdit).toBeDefined();
- expect(cellEdit.ridx).toEqual(ridx);
- expect(cellEdit.cidx).toEqual(cidx);
});
});
@@ -105,7 +93,7 @@ describe('TableResolver', () => {
const expectNonEditableRows = [1, 2];
const cellEdit = {
mode: Const.DBCLICK_TO_CELL_EDIT,
- onEditing: sinon.stub(),
+ onUpdate: sinon.stub(),
blurToSave: true,
beforeSaveCell: sinon.stub(),
afterSaveCell: sinon.stub(),
@@ -122,10 +110,8 @@ describe('TableResolver', () => {
it('should resolve a cellEdit correctly', () => {
const cellEditInfo = wrapper.instance().resolveCellEditProps();
expect(cellEditInfo).toBeDefined();
- expect(cellEditInfo.ridx).toBeNull();
- expect(cellEditInfo.cidx).toBeNull();
expect(cellEditInfo.mode).toEqual(cellEdit.mode);
- expect(cellEditInfo.onEditing).toEqual(cellEdit.onEditing);
+ expect(cellEditInfo.onUpdate).toEqual(cellEdit.onUpdate);
expect(cellEditInfo.blurToSave).toEqual(cellEdit.blurToSave);
expect(cellEditInfo.beforeSaveCell).toEqual(cellEdit.beforeSaveCell);
expect(cellEditInfo.afterSaveCell).toEqual(cellEdit.afterSaveCell);
@@ -280,7 +266,9 @@ describe('TableResolver', () => {
selectRow = {};
const mockOptions = {
foo: 'test',
- bar: sinon.stub()
+ bar: sinon.stub(),
+ allRowsSelected: false,
+ selected: []
};
const selectedRowKeys = [];
const mockElement = React.createElement(BootstrapTableMock, {
@@ -290,12 +278,20 @@ describe('TableResolver', () => {
headerCellSelectionInfo = wrapper.instance().resolveHeaderCellSelectionProps(mockOptions);
});
- it('should return object which contain options', () => {
+ it('should return object which contain specified options', () => {
expect(headerCellSelectionInfo).toEqual(expect.objectContaining({
foo: 'test',
bar: expect.any(Function)
}));
});
+
+ it('should return object which can not contain allRowsSelected option', () => {
+ expect(headerCellSelectionInfo.allRowsSelected).not.toBeDefined();
+ });
+
+ it('should return object which can not contain allRowsSelected option', () => {
+ expect(headerCellSelectionInfo.selected).not.toBeDefined();
+ });
});
describe('if all rows were selected', () => {
@@ -307,9 +303,11 @@ describe('TableResolver', () => {
}, null);
wrapper = shallow(mockElement);
- wrapper.instance().store.setSelectedRowKeys(selectedRowKeys);
- headerCellSelectionInfo = wrapper.instance().resolveHeaderCellSelectionProps();
+ headerCellSelectionInfo = wrapper.instance().resolveHeaderCellSelectionProps({
+ allRowsSelected: true,
+ selected: selectedRowKeys
+ });
});
it('should return checkedStatus which eqauls to checked', () => {
@@ -318,6 +316,7 @@ describe('TableResolver', () => {
}));
});
});
+
describe('if part of rows were selected', () => {
beforeEach(() => {
selectRow = {};
@@ -327,8 +326,10 @@ describe('TableResolver', () => {
}, null);
wrapper = shallow(mockElement);
- wrapper.instance().store.setSelectedRowKeys(selectedRowKeys);
- headerCellSelectionInfo = wrapper.instance().resolveHeaderCellSelectionProps();
+ headerCellSelectionInfo = wrapper.instance().resolveHeaderCellSelectionProps({
+ allRowsSelected: false,
+ selected: selectedRowKeys
+ });
});
it('should return checkedStatus which eqauls to indeterminate', () => {
@@ -347,9 +348,11 @@ describe('TableResolver', () => {
}, null);
wrapper = shallow(mockElement);
- wrapper.instance().store.setSelectedRowKeys(selectedRowKeys);
- headerCellSelectionInfo = wrapper.instance().resolveHeaderCellSelectionProps();
+ headerCellSelectionInfo = wrapper.instance().resolveHeaderCellSelectionProps({
+ allRowsSelected: false,
+ selected: selectedRowKeys
+ });
});
it('should return checkedStatus which eqauls to unchecked', () => {
diff --git a/packages/react-bootstrap-table2/test/row.test.js b/packages/react-bootstrap-table2/test/row.test.js
index cdbf885..c03848a 100644
--- a/packages/react-bootstrap-table2/test/row.test.js
+++ b/packages/react-bootstrap-table2/test/row.test.js
@@ -225,8 +225,9 @@ describe('Row', () => {
beforeEach(() => {
cellEdit.ridx = rowIndex;
cellEdit.cidx = editingColIndex;
- cellEdit.onComplete = sinon.stub();
+ cellEdit.onUpdate = sinon.stub();
cellEdit.onEscape = sinon.stub();
+ cellEdit.onUpdate = sinon.stub();
wrapper = shallow(
{
beforeEach(() => {
cellEdit.ridx = 3;
cellEdit.cidx = editingColIndex;
- cellEdit.onComplete = sinon.stub();
+ cellEdit.onUpdate = sinon.stub();
cellEdit.onEscape = sinon.stub();
wrapper = shallow(
{
+ let wrapper;
+
+ const keyField = 'id';
+
+ const columns = [{
+ dataField: keyField,
+ text: 'ID'
+ }, {
+ dataField: 'name',
+ text: 'Name'
+ }];
+
+ const data = [{
+ id: 1,
+ name: 'A'
+ }, {
+ id: 2,
+ name: 'B'
+ }];
+
+ describe('initialization', () => {
+ beforeEach(() => {
+ wrapper = shallow(
+
+ );
+ });
+
+ it('should render BootstrapTable successfully', () => {
+ expect(wrapper.length).toBe(1);
+ expect(wrapper.find(BootstrapTable).length).toBe(1);
+ });
+
+ it('should creating store successfully', () => {
+ const store = wrapper.instance().store;
+ expect(store).toBeDefined();
+ expect(store.get()).toEqual(data);
+ expect(store.keyField).toEqual(keyField);
+ });
+ });
+
+ describe('when cellEdit is defined', () => {
+ const spy = jest.spyOn(BootstrapTableful.prototype, 'renderCellEdit');
+ const cellEdit = {
+ mode: 'click'
+ };
+
+ beforeEach(() => {
+ wrapper = shallow(
+
+ );
+ });
+
+ it('should calling renderCellEdit function', () => {
+ expect(spy).toHaveBeenCalled();
+ });
+
+ it('should injecting correct props', () => {
+ expect(wrapper.props().keyField).toEqual('id');
+ expect(wrapper.props().cellEdit).toEqual(cellEdit);
+ expect(wrapper.props().elem).toBeDefined();
+ expect(wrapper.props().onUpdateCell).toBeDefined();
+ });
+
+ describe('for handleUpdateCell function', () => {
+ const rowId = data[1].id;
+ const dataField = columns[1].dataField;
+ const newValue = 'tester';
+ let result;
+
+ describe('when cellEdit.onUpdate callback is not defined', () => {
+ beforeEach(() => {
+ result = wrapper.instance().handleUpdateCell(rowId, dataField, newValue);
+ });
+
+ it('should return true', () => {
+ expect(result).toBeTruthy();
+ });
+
+ it('should update store data directly', () => {
+ const store = wrapper.instance().store;
+ const row = store.getRowByRowId(rowId);
+ expect(row[dataField]).toEqual(newValue);
+ });
+ });
+
+ describe('when cellEdit.onUpdate callback is define and which return false', () => {
+ beforeEach(() => {
+ cellEdit.onUpdate = sinon.stub().returns(false);
+ wrapper = shallow(
+
+ );
+ result = wrapper.instance().handleUpdateCell(rowId, dataField, newValue);
+ });
+
+ it('should calling cellEdit.onUpdate callback correctly', () => {
+ expect(cellEdit.onUpdate.callCount).toBe(1);
+ expect(cellEdit.onUpdate.calledWith(rowId, dataField, newValue)).toBe(true);
+ });
+
+ it('should return false', () => {
+ expect(result).toBeFalsy();
+ });
+
+ it('shouldn\'t update store data', () => {
+ const store = wrapper.instance().store;
+ const row = store.getRowByRowId(rowId);
+ expect(row[dataField]).not.toEqual(newValue);
+ });
+ });
+
+ // We need refactoring handleUpdateCell function for handling promise firstly
+ // then it will be much easier to test
+ describe.skip('when cellEdit.onUpdate callback is define and which return a Promise', () => {});
+ });
+ });
+});
diff --git a/packages/react-bootstrap-table2/test/test-helpers/mock-component.js b/packages/react-bootstrap-table2/test/test-helpers/mock-component.js
index ca7114d..f09890e 100644
--- a/packages/react-bootstrap-table2/test/test-helpers/mock-component.js
+++ b/packages/react-bootstrap-table2/test/test-helpers/mock-component.js
@@ -8,13 +8,7 @@ export const extendTo = Base =>
const { data } = props;
this.store = new Store(props);
- this.state = {
- data,
- currEditCell: {
- ridx: null,
- cidx: null
- }
- };
+ this.state = { data };
}
render() { return null; }