diff --git a/docs/README.md b/docs/README.md
index 5023f08..6ed4f43 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -40,7 +40,13 @@ This is a chance that you can connect to your remote server or database to manip
For flexibility reason, you can control what functionality should be handled on remote via a object return:
```js
-remote={ { filter: true } }
+remote={ {
+ filter: true,
+ pagination: true,
+ filter: true,
+ sort: true,
+ cellEdit: true
+} }
```
In above case, only column filter will be handled on remote.
@@ -53,7 +59,7 @@ A special case for remote pagination:
remote={ { pagination: true, filter: false, sort: false } }
```
-In pagination case, even you only specified the paignation need to handle as remote, `react-bootstrap-table2` will handle all the table changes(`filter`, `sort` etc) as remote mode, because `react-bootstrap-table` only know the data of current page, but filtering, searching or sort need to work on overall datas.
+In pagination case, even you only specified the paignation need to handle as remote, `react-bootstrap-table2` will handle all the table changes(`filter`, `sort`) as remote mode, because `react-bootstrap-table` only know the data of current page, but filtering, searching or sort need to work on overall datas.
### loading - [Bool]
Telling if table is loading or not, for example: waiting data loading, filtering etc. It's **only** valid when [`remote`](#remote) is enabled.
@@ -250,6 +256,7 @@ There's only two arguments will be passed to `onTableChange`: `type` and `newSta
* `filter`
* `pagination`
* `sort`
+* `cellEdit`
Following is a shape of `newState`
@@ -260,6 +267,11 @@ Following is a shape of `newState`
sortField, // newest sort field
sortOrder, // newest sort order
filters, // an object which have current filter status per column
- data // when you enable remote sort, you may need to base on data to sort if data is filtered/searched
+ data, // when you enable remote sort, you may need to base on data to sort if data is filtered/searched
+ cellEdit: { // You can only see this prop when type is cellEdit
+ rowId,
+ dataField,
+ newValue
+ }
}
```
\ No newline at end of file
diff --git a/docs/cell-edit.md b/docs/cell-edit.md
index 328a94e..051dde8 100644
--- a/docs/cell-edit.md
+++ b/docs/cell-edit.md
@@ -5,8 +5,6 @@
* [timeToCloseMessage](#timeToCloseMessage)
* [beforeSaveCell](#beforeSaveCell)
* [afterSaveCell](#afterSaveCell)
-* [onUpdate](#onUpdate)
-* [editing](#editing)
* [errorMessage](#errorMessage)
* [onErrorMessageDisappear](#onErrorMessageDisappear)
@@ -21,9 +19,7 @@ Following is the shape of `cellEdit` object:
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: () => { ... },
@@ -63,73 +59,8 @@ const cellEdit = {
};
```
-### 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.
+This prop is not often used. Only used when you keep the error message in your application state and also handle the cell editing on remote mode.
### 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
deleted file mode 100644
index c66e3bd..0000000
--- a/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-with-promise-table.js
+++ /dev/null
@@ -1,75 +0,0 @@
-/* eslint no-unused-vars: 0 */
-/* eslint arrow-body-style: 0 */
-import React, { Component } from 'react';
-
-import BootstrapTable 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
deleted file mode 100644
index 760c6d9..0000000
--- a/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-with-redux-table.js
+++ /dev/null
@@ -1,212 +0,0 @@
-/* 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 BootstrapTable 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/examples/remote/remote-celledit.js b/packages/react-bootstrap-table2-example/examples/remote/remote-celledit.js
new file mode 100644
index 0000000..312c81e
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/remote/remote-celledit.js
@@ -0,0 +1,165 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+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 sourceCode = `\
+const RemoteCellEdit = (props) => {
+ const cellEdit = {
+ mode: 'click',
+ errorMessage: props.errorMessage
+ };
+
+ return (
+
+
+ { sourceCode }
+
+ );
+};
+
+RemoteCellEdit.propTypes = {
+ data: PropTypes.array.isRequired,
+ onTableChange: PropTypes.func.isRequired,
+ errorMessage: PropTypes.string.isRequired
+};
+
+class Container extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ data: products,
+ errorMessage: null
+ };
+ }
+
+ handleTableChange = (type, { data, cellEdit: { rowId, dataField, newValue } }) => {
+ setTimeout(() => {
+ if (newValue === 'test' && dataField === 'name') {
+ this.setState(() => ({
+ data,
+ errorMessage: 'Oops, product name shouldn't be "test"'
+ }));
+ } else {
+ const result = data.map((row) => {
+ if (row.id === rowId) {
+ const newRow = { ...row };
+ newRow[dataField] = newValue;
+ return newRow;
+ }
+ return row;
+ });
+ this.setState(() => ({
+ data: result,
+ errorMessage: null
+ }));
+ }
+ }, 2000);
+ }
+
+ render() {
+ return (
+
+ );
+ }
+}
+`;
+
+
+const RemoteCellEdit = (props) => {
+ const cellEdit = {
+ mode: 'click',
+ errorMessage: props.errorMessage
+ };
+
+ return (
+
+
+ { sourceCode }
+
+ );
+};
+
+RemoteCellEdit.propTypes = {
+ data: PropTypes.array.isRequired,
+ onTableChange: PropTypes.func.isRequired,
+ errorMessage: PropTypes.string.isRequired
+};
+
+class Container extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ data: products,
+ errorMessage: null
+ };
+ }
+
+ handleTableChange = (type, { data, cellEdit: { rowId, dataField, newValue } }) => {
+ setTimeout(() => {
+ if (newValue === 'test' && dataField === 'name') {
+ this.setState(() => ({
+ data,
+ errorMessage: 'Oops, product name shouldn\'t be "test"'
+ }));
+ } else {
+ const result = data.map((row) => {
+ if (row.id === rowId) {
+ const newRow = { ...row };
+ newRow[dataField] = newValue;
+ return newRow;
+ }
+ return row;
+ });
+ this.setState(() => ({
+ data: result,
+ errorMessage: null
+ }));
+ }
+ }, 2000);
+ }
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+export default Container;
diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js
index 909f5b8..3fe704c 100644
--- a/packages/react-bootstrap-table2-example/stories/index.js
+++ b/packages/react-bootstrap-table2-example/stories/index.js
@@ -63,8 +63,6 @@ import CellEditHooks from 'examples/cell-edit/cell-edit-hooks-table';
import CellEditValidator from 'examples/cell-edit/cell-edit-validator-table';
import CellEditStyleTable from 'examples/cell-edit/cell-edit-style-table';
import CellEditClassTable from 'examples/cell-edit/cell-edit-class-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';
@@ -91,6 +89,7 @@ import TableOverlay from 'examples/loading-overlay/table-overlay';
import RemoteSort from 'examples/remote/remote-sort';
import RemoteFilter from 'examples/remote/remote-filter';
import RemotePaginationTable from 'examples/remote/remote-pagination';
+import RemoteCellEdit from 'examples/remote/remote-celledit';
import RemoteAll from 'examples/remote/remote-all';
// css style
@@ -165,9 +164,7 @@ storiesOf('Cell Editing', module)
.add('Rich Hook Functions', () => )
.add('Validation', () => )
.add('Custom Cell Style When Editing', () => )
- .add('Custom Cell Classes When Editing', () => )
- .add('Async Cell Editing(Promise)', () => )
- .add('Async Cell Editing(Redux)', () => );
+ .add('Custom Cell Classes When Editing', () => );
storiesOf('Row Selection', module)
.add('Single Selection', () => )
@@ -194,4 +191,5 @@ storiesOf('Remote', module)
.add('Remote Sort', () => )
.add('Remote Filter', () => )
.add('Remote Pagination', () => )
+ .add('Remote Cell Editing', () => )
.add('Remote All', () => );
diff --git a/packages/react-bootstrap-table2-filter/src/wrapper.js b/packages/react-bootstrap-table2-filter/src/wrapper.js
index 780c4d8..ad08b82 100644
--- a/packages/react-bootstrap-table2-filter/src/wrapper.js
+++ b/packages/react-bootstrap-table2-filter/src/wrapper.js
@@ -1,3 +1,5 @@
+/* eslint no-param-reassign: 0 */
+
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { filters } from './filter';
@@ -15,14 +17,20 @@ export default (Base, {
constructor(props) {
super(props);
- this.state = { currFilters: {}, isDataChanged: false };
+ this.state = { currFilters: {}, isDataChanged: props.isDataChanged || false };
this.onFilter = this.onFilter.bind(this);
}
- componentWillReceiveProps(nextProps) {
+ componentWillReceiveProps({ isDataChanged, store, columns }) {
// consider to use lodash.isEqual
- if (JSON.stringify(this.state.currFilters) !== JSON.stringify(nextProps.store.filters)) {
- this.setState(() => ({ isDataChanged: true, currFilters: nextProps.store.filters }));
+ if (JSON.stringify(this.state.currFilters) !== JSON.stringify(store.filters)) {
+ this.setState(() => ({ isDataChanged: true, currFilters: store.filters }));
+ } else if (isDataChanged) {
+ if (!(this.isRemoteFiltering() || this.isRemotePagination()) &&
+ Object.keys(this.state.currFilters).length > 0) {
+ store.filteredData = filters(store, columns, _)(this.state.currFilters);
+ }
+ this.setState(() => ({ isDataChanged }));
} else {
this.setState(() => ({ isDataChanged: false }));
}
diff --git a/packages/react-bootstrap-table2-filter/test/wrapper.test.js b/packages/react-bootstrap-table2-filter/test/wrapper.test.js
index f250c46..b80cd74 100644
--- a/packages/react-bootstrap-table2-filter/test/wrapper.test.js
+++ b/packages/react-bootstrap-table2-filter/test/wrapper.test.js
@@ -28,7 +28,7 @@ describe('Wrapper', () => {
onTableChangeCB.reset();
});
- const createTableProps = () => {
+ const createTableProps = (props) => {
const tableProps = {
keyField: 'id',
columns: [{
@@ -47,7 +47,8 @@ describe('Wrapper', () => {
filter: filter(),
_,
store: new Store('id'),
- onTableChange: onTableChangeCB
+ onTableChange: onTableChangeCB,
+ ...props
};
tableProps.store.data = data;
return tableProps;
@@ -105,6 +106,17 @@ describe('Wrapper', () => {
});
});
+ describe('when props.isDataChanged is true and remote is enable', () => {
+ beforeEach(() => {
+ nextProps = createTableProps({ isDataChanged: true });
+ instance.componentWillReceiveProps(nextProps);
+ });
+
+ it('should setting isDataChanged as true', () => {
+ expect(instance.state.isDataChanged).toBeTruthy();
+ });
+ });
+
describe('when props.store.filters is different from current state.currFilters', () => {
beforeEach(() => {
nextProps = createTableProps();
diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js
index 19e7afa..aeed455 100644
--- a/packages/react-bootstrap-table2/src/bootstrap-table.js
+++ b/packages/react-bootstrap-table2/src/bootstrap-table.js
@@ -130,13 +130,11 @@ BootstrapTable.propTypes = {
filter: PropTypes.object,
cellEdit: PropTypes.shape({
mode: PropTypes.oneOf([Const.CLICK_TO_CELL_EDIT, Const.DBCLICK_TO_CELL_EDIT]).isRequired,
- onUpdate: PropTypes.func,
onErrorMessageDisappear: PropTypes.func,
blurToSave: PropTypes.bool,
beforeSaveCell: PropTypes.func,
afterSaveCell: PropTypes.func,
nonEditableRows: PropTypes.func,
- editing: PropTypes.bool,
timeToCloseMessage: PropTypes.number,
errorMessage: PropTypes.string
}),
@@ -146,8 +144,7 @@ BootstrapTable.propTypes = {
currEditCell: PropTypes.shape({
ridx: PropTypes.number,
cidx: PropTypes.number,
- message: PropTypes.string,
- editing: PropTypes.bool
+ message: PropTypes.string
}),
selectRow: PropTypes.shape({
mode: PropTypes.oneOf([Const.ROW_SELECT_SINGLE, Const.ROW_SELECT_MULTIPLE]).isRequired,
diff --git a/packages/react-bootstrap-table2/src/cell-edit/wrapper.js b/packages/react-bootstrap-table2/src/cell-edit/wrapper.js
index ec62fd2..7f21e53 100644
--- a/packages/react-bootstrap-table2/src/cell-edit/wrapper.js
+++ b/packages/react-bootstrap-table2/src/cell-edit/wrapper.js
@@ -1,44 +1,54 @@
/* eslint react/prop-types: 0 */
import React, { Component } from 'react';
import _ from '../utils';
+import remoteResolver from '../props-resolver/remote-resolver';
-export default (Base, parentProps) =>
- class CellEditWrapper extends Component {
+export default Base =>
+ class CellEditWrapper extends remoteResolver(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
+ isDataChanged: false
};
}
componentWillReceiveProps(nextProps) {
- if (nextProps.cellEdit) {
- if (nextProps.cellEdit.editing) {
+ if (nextProps.cellEdit && this.isRemoteCellEdit()) {
+ if (nextProps.cellEdit.errorMessage) {
this.setState(() => ({
- ...this.state,
+ isDataChanged: false,
message: nextProps.cellEdit.errorMessage
}));
} else {
+ this.setState(() => ({
+ isDataChanged: true
+ }));
this.escapeEditing();
}
+ } else {
+ this.setState(() => ({
+ isDataChanged: false
+ }));
}
}
handleCellUpdate(row, column, newValue) {
- const { keyField, cellEdit } = this.props;
+ const { keyField, cellEdit, store } = 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 (parentProps.onUpdateCell(rowId, column.dataField, newValue)) {
+ if (this.isRemoteCellEdit()) {
+ this.handleCellChange(rowId, column.dataField, newValue);
+ } else {
+ store.edit(rowId, column.dataField, newValue);
if (_.isFunction(afterSaveCell)) afterSaveCell(oldValue, newValue, row, column);
this.completeEditing();
}
@@ -49,7 +59,7 @@ export default (Base, parentProps) =>
ridx: null,
cidx: null,
message: null,
- editing: false
+ isDataChanged: true
}));
}
@@ -58,7 +68,7 @@ export default (Base, parentProps) =>
this.setState(() => ({
ridx,
cidx,
- editing: true
+ isDataChanged: false
}));
};
@@ -69,27 +79,21 @@ export default (Base, parentProps) =>
escapeEditing() {
this.setState(() => ({
ridx: null,
- cidx: null,
- editing: false
- }));
- }
-
- updateEditingWithErr(message) {
- this.setState(() => ({
- ...this.state,
- message
+ cidx: null
}));
}
render() {
+ const { isDataChanged, ...rest } = this.state;
return (
);
}
diff --git a/packages/react-bootstrap-table2/src/container.js b/packages/react-bootstrap-table2/src/container.js
index 04fb908..30fe195 100644
--- a/packages/react-bootstrap-table2/src/container.js
+++ b/packages/react-bootstrap-table2/src/container.js
@@ -16,11 +16,10 @@ const withDataStore = Base =>
this.store = new Store(props.keyField);
this.store.data = props.data;
this.wrapComponents();
- this.handleUpdateCell = this.handleUpdateCell.bind(this);
}
componentWillReceiveProps(nextProps) {
- this.store.data = nextProps.data;
+ this.store.setAllData(nextProps.data);
}
wrapComponents() {
@@ -45,41 +44,13 @@ const withDataStore = Base =>
});
}
+ if (cellEdit) {
+ this.BaseComponent = withCellEdit(this.BaseComponent);
+ }
+
if (selectRow) {
this.BaseComponent = withSelection(this.BaseComponent);
}
-
- if (cellEdit) {
- this.BaseComponent = withCellEdit(this.BaseComponent, {
- ref: node => this.cellEditWrapper = node,
- onUpdateCell: this.handleUpdateCell
- });
- }
- }
-
- 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.cellEditWrapper.completeEditing();
- }
- }).catch((e) => {
- this.cellEditWrapper.updateEditingWithErr(e.message);
- });
- }
- return false;
}
render() {
diff --git a/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js b/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js
index b59c9e3..6a44e57 100644
--- a/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js
+++ b/packages/react-bootstrap-table2/src/props-resolver/remote-resolver.js
@@ -10,7 +10,7 @@ export default ExtendBase =>
filters: store.filters,
sortField: store.sortField,
sortOrder: store.sortOrder,
- data: store.data,
+ data: store.getAllData(),
...state
};
}
@@ -30,6 +30,11 @@ export default ExtendBase =>
return remote === true || (_.isObject(remote) && remote.sort);
}
+ isRemoteCellEdit() {
+ const { remote } = this.props;
+ return remote === true || (_.isObject(remote) && remote.cellEdit);
+ }
+
handleRemotePageChange() {
this.props.onTableChange('pagination', this.getNewestState());
}
@@ -46,4 +51,9 @@ export default ExtendBase =>
handleSortChange() {
this.props.onTableChange('sort', this.getNewestState());
}
+
+ handleCellChange(rowId, dataField, newValue) {
+ const cellEdit = { rowId, dataField, newValue };
+ this.props.onTableChange('cellEdit', this.getNewestState({ cellEdit }));
+ }
};
diff --git a/packages/react-bootstrap-table2/src/store/index.js b/packages/react-bootstrap-table2/src/store/index.js
index 9c6e108..6711c99 100644
--- a/packages/react-bootstrap-table2/src/store/index.js
+++ b/packages/react-bootstrap-table2/src/store/index.js
@@ -34,6 +34,10 @@ export default class Store {
return this._data;
}
+ setAllData(data) {
+ this._data = data;
+ }
+
get data() {
if (Object.keys(this._filters).length > 0) {
return this._filteredData;
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 8f21f5f..c195b16 100644
--- a/packages/react-bootstrap-table2/test/cell-edit/wrapper.test.js
+++ b/packages/react-bootstrap-table2/test/cell-edit/wrapper.test.js
@@ -31,13 +31,10 @@ describe('CellEditWrapper', () => {
};
const keyField = 'id';
- let onUpdateCellCB = sinon.stub();
const store = new Store(keyField);
store.data = data;
- const CellEditWrapper = wrapperFactory(Container, {
- onUpdateCell: onUpdateCellCB
- });
+ const CellEditWrapper = wrapperFactory(Container);
beforeEach(() => {
wrapper = shallow(
@@ -60,24 +57,24 @@ describe('CellEditWrapper', () => {
expect(wrapper.state().ridx).toBeNull();
expect(wrapper.state().cidx).toBeNull();
expect(wrapper.state().message).toBeNull();
- expect(wrapper.state().editing).toBeFalsy();
+ expect(wrapper.state().isDataChanged).toBeFalsy();
});
it('should inject correct props to base component', () => {
expect(wrapper.props().onCellUpdate).toBeDefined();
expect(wrapper.props().onStartEditing).toBeDefined();
expect(wrapper.props().onEscapeEditing).toBeDefined();
+ expect(wrapper.props().isDataChanged).toBe(wrapper.state().isDataChanged);
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', () => {
+ describe('and cellEdit is not work on remote', () => {
beforeEach(() => {
wrapper = shallow(
{
store={ store }
/>
);
- wrapper.setProps({ cellEdit: { ...cellEdit, editing: false } });
+ wrapper.setProps({ cellEdit: { ...cellEdit } });
});
- 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();
+ it('should always setting state.isDataChanged as false', () => {
+ expect(wrapper.state().isDataChanged).toBeFalsy();
});
});
- describe('and cellEdit.editing is true', () => {
- const errorMessage = 'test';
+ describe('and cellEdit is work on remote', () => {
+ let errorMessage;
const ridx = 1;
const cidx = 2;
- beforeEach(() => {
- wrapper = shallow(
-
- );
- wrapper.setState({ ridx, cidx, editing: true });
- wrapper.setProps({ cellEdit: { ...cellEdit, editing: true, errorMessage } });
+ describe('and cellEdit.errorMessage is defined', () => {
+ beforeEach(() => {
+ wrapper = shallow(
+
+ );
+ errorMessage = 'test';
+ wrapper.setState({ ridx, cidx });
+ wrapper.setProps({ cellEdit: { ...cellEdit, errorMessage } });
+ });
+
+ it('should setting correct state', () => {
+ expect(wrapper.state().ridx).toEqual(ridx);
+ expect(wrapper.state().cidx).toEqual(cidx);
+ expect(wrapper.state().isDataChanged).toBeFalsy();
+ expect(wrapper.state().message).toEqual(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('and cellEdit.errorMessage is undefined', () => {
+ beforeEach(() => {
+ wrapper = shallow(
+
+ );
+ errorMessage = null;
+ wrapper.setState({ ridx, cidx });
+ wrapper.setProps({ cellEdit: { ...cellEdit, errorMessage } });
+ });
- describe('call updateEditingWithErr function', () => {
- it('should set state.message correctly', () => {
- const message = 'test';
- wrapper.instance().updateEditingWithErr(message);
- expect(wrapper.state().message).toEqual(message);
+ it('should setting correct state', () => {
+ expect(wrapper.state().isDataChanged).toBeTruthy();
+ });
+
+ it('should escape current editing', () => {
+ expect(spy).toHaveBeenCalled();
+ });
+ });
});
});
@@ -144,7 +156,6 @@ describe('CellEditWrapper', () => {
wrapper.instance().escapeEditing();
expect(wrapper.state().ridx).toBeNull();
expect(wrapper.state().cidx).toBeNull();
- expect(wrapper.state().editing).toBeFalsy();
});
});
@@ -155,7 +166,7 @@ describe('CellEditWrapper', () => {
wrapper.instance().startEditing(ridx, cidx);
expect(wrapper.state().ridx).toEqual(ridx);
expect(wrapper.state().cidx).toEqual(cidx);
- expect(wrapper.state().editing).toBeTruthy();
+ expect(wrapper.state().isDataChanged).toBeFalsy();
});
describe('if selectRow.clickToSelect is defined', () => {
@@ -199,7 +210,6 @@ describe('CellEditWrapper', () => {
wrapper.instance().startEditing(ridx, cidx);
expect(wrapper.state().ridx).toEqual(ridx);
expect(wrapper.state().cidx).toEqual(cidx);
- expect(wrapper.state().editing).toBeTruthy();
});
});
});
@@ -210,7 +220,7 @@ describe('CellEditWrapper', () => {
expect(wrapper.state().ridx).toBeNull();
expect(wrapper.state().cidx).toBeNull();
expect(wrapper.state().message).toBeNull();
- expect(wrapper.state().editing).toBeFalsy();
+ expect(wrapper.state().isDataChanged).toBeTruthy();
});
});
@@ -219,35 +229,75 @@ describe('CellEditWrapper', () => {
const column = columns[1];
const newValue = 'new name';
- beforeEach(() => {
- wrapper = shallow(
-
- );
- wrapper.instance().handleCellUpdate(row, column, newValue);
+ describe('when cell edit is work on remote', () => {
+ const spy = jest.spyOn(CellEditWrapper.prototype, 'handleCellChange');
+ const onTableChangeCB = jest.fn();
+
+ beforeEach(() => {
+ wrapper = shallow(
+
+ );
+ wrapper.instance().handleCellUpdate(row, column, newValue);
+ });
+
+ it('should calling handleCellChange correctly', () => {
+ expect(spy).toHaveBeenCalled();
+ expect(spy.mock.calls).toHaveLength(1);
+ expect(spy.mock.calls[0]).toHaveLength(3);
+ expect(spy.mock.calls[0][0]).toEqual(row[keyField]);
+ expect(spy.mock.calls[0][1]).toEqual(column.dataField);
+ expect(spy.mock.calls[0][2]).toEqual(newValue);
+ });
});
- it('should calling onUpdateCell callback correctly', () => {
- expect(onUpdateCellCB.callCount).toBe(1);
- expect(onUpdateCellCB.calledWith(row.id, column.dataField, newValue)).toBe(true);
- });
+ describe('when cell edit is not work on remote', () => {
+ const spyOnCompleteEditing = jest.spyOn(CellEditWrapper.prototype, 'completeEditing');
+ const spyOnStoreEdit = jest.spyOn(Store.prototype, 'edit');
- describe('when onUpdateCell function return true', () => {
- const spy = jest.spyOn(CellEditWrapper.prototype, 'completeEditing');
+ beforeEach(() => {
+ wrapper = shallow(
+
+ );
+ wrapper.instance().handleCellUpdate(row, column, newValue);
+ });
+
+ afterEach(() => {
+ spyOnStoreEdit.mockReset();
+ spyOnCompleteEditing.mockReset();
+ });
+
+ it('should calling props.store.edit', () => {
+ expect(spyOnStoreEdit).toHaveBeenCalled();
+ expect(spyOnStoreEdit.mock.calls).toHaveLength(1);
+ expect(spyOnStoreEdit.mock.calls[0]).toHaveLength(3);
+ expect(spyOnStoreEdit.mock.calls[0][0]).toEqual(row[keyField]);
+ expect(spyOnStoreEdit.mock.calls[0][1]).toEqual(column.dataField);
+ expect(spyOnStoreEdit.mock.calls[0][2]).toEqual(newValue);
+ });
it('should calling completeEditing function', () => {
- expect(spy).toHaveBeenCalled();
+ expect(spyOnCompleteEditing).toHaveBeenCalled();
});
describe('if cellEdit.afterSaveCell prop defined', () => {
const aftereSaveCellCallBack = sinon.stub();
+
beforeEach(() => {
- cellEdit.beforeSaveCell = aftereSaveCellCallBack;
+ cellEdit.afterSaveCell = aftereSaveCellCallBack;
wrapper = shallow(
{
});
});
- describe('when onUpdateCell function return false', () => {
- const spy = jest.spyOn(CellEditWrapper.prototype, 'completeEditing');
-
- beforeEach(() => {
- onUpdateCellCB = 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(() => {
diff --git a/packages/react-bootstrap-table2/test/container.test.js b/packages/react-bootstrap-table2/test/container.test.js
index 42c67bf..066a948 100644
--- a/packages/react-bootstrap-table2/test/container.test.js
+++ b/packages/react-bootstrap-table2/test/container.test.js
@@ -1,12 +1,10 @@
/* eslint react/prefer-stateless-function: 0 */
/* eslint react/no-multi-comp: 0 */
import React from 'react';
-import sinon from 'sinon';
import { shallow } from 'enzyme';
import BootstrapTable from '../src/bootstrap-table';
import Container from '../src';
-import { getRowByRowId } from '../src/store/rows';
describe('container', () => {
let wrapper;
@@ -79,63 +77,6 @@ describe('container', () => {
it('should render BootstrapTable component successfully', () => {
expect(wrapper.dive().find(BootstrapTable)).toHaveLength(1);
});
-
- 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 = getRowByRowId(store)(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 = getRowByRowId(store)(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', () => {});
- });
});
describe('when selectRow prop is defined', () => {
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 79eabb4..7f4cf59 100644
--- a/packages/react-bootstrap-table2/test/props-resolver/index.test.js
+++ b/packages/react-bootstrap-table2/test/props-resolver/index.test.js
@@ -93,7 +93,6 @@ describe('TableResolver', () => {
const expectNonEditableRows = [1, 2];
const cellEdit = {
mode: Const.DBCLICK_TO_CELL_EDIT,
- onUpdate: sinon.stub(),
blurToSave: true,
beforeSaveCell: sinon.stub(),
afterSaveCell: sinon.stub(),
diff --git a/packages/react-bootstrap-table2/test/props-resolver/remote-resolver.test.js b/packages/react-bootstrap-table2/test/props-resolver/remote-resolver.test.js
index a6e260d..b88760f 100644
--- a/packages/react-bootstrap-table2/test/props-resolver/remote-resolver.test.js
+++ b/packages/react-bootstrap-table2/test/props-resolver/remote-resolver.test.js
@@ -134,6 +134,58 @@ describe('remoteResolver', () => {
});
});
+ describe('isRemoteCellEdit', () => {
+ describe('when remote is false', () => {
+ beforeEach(() => {
+ shallowContainer();
+ });
+
+ it('should return false', () => {
+ expect(wrapper.instance().isRemoteCellEdit()).toBeFalsy();
+ });
+ });
+
+ describe('when remote is true', () => {
+ beforeEach(() => {
+ shallowContainer({ remote: true });
+ });
+
+ it('should return true', () => {
+ expect(wrapper.instance().isRemoteCellEdit()).toBeTruthy();
+ });
+ });
+
+ describe('when remote.cellEdit is true', () => {
+ beforeEach(() => {
+ shallowContainer({ remote: { cellEdit: true } });
+ });
+
+ it('should return true', () => {
+ expect(wrapper.instance().isRemoteCellEdit()).toBeTruthy();
+ });
+ });
+ });
+
+ describe('handleCellChange', () => {
+ const onTableChangeCB = sinon.stub();
+ const rowId = 1;
+ const dataField = 'name';
+ const newValue = 'test';
+
+ beforeEach(() => {
+ onTableChangeCB.reset();
+ shallowContainer({ onTableChange: onTableChangeCB });
+ wrapper.instance().handleCellChange(rowId, dataField, newValue);
+ });
+
+ it('should calling props.onTableChange correctly', () => {
+ const cellEdit = { rowId, dataField, newValue };
+ expect(onTableChangeCB.calledOnce).toBeTruthy();
+ expect(onTableChangeCB.calledWith(
+ 'cellEdit', wrapper.instance().getNewestState({ cellEdit }))).toBeTruthy();
+ });
+ });
+
describe('handleSortChange', () => {
const onTableChangeCB = sinon.stub();
beforeEach(() => {
diff --git a/packages/react-bootstrap-table2/test/row.test.js b/packages/react-bootstrap-table2/test/row.test.js
index 11ccdd6..a44e52f 100644
--- a/packages/react-bootstrap-table2/test/row.test.js
+++ b/packages/react-bootstrap-table2/test/row.test.js
@@ -273,7 +273,6 @@ describe('Row', () => {
cellEdit.cidx = editingColIndex;
cellEdit.onUpdate = sinon.stub();
cellEdit.onEscape = sinon.stub();
- cellEdit.onUpdate = sinon.stub();
wrapper = shallow(
|