mirror of
https://github.com/gosticks/react-bootstrap-table2.git
synced 2026-08-11 19:50:17 +00:00
fix #63
* support async cell editing * refine cellEdit.onUpdate and cellEdit.editing * refine cell edit * add redux * add stories for async cell edit * fix test case and patch tests for async cell editing * patch docs for cellEdit prop * fix bug produced by rebasing lol
This commit is contained in:
+1
-30
@@ -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
|
||||
|
||||
### <a name='cellEdit'>cellEdit - [Object]</a>
|
||||
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: () => { ... }
|
||||
}
|
||||
```
|
||||
#### <a name='cellEdit.mode'>cellEdit.mode - [String]</a>
|
||||
`cellEdit.mode` possible value is `click` and `dbclick`. **It's required value** that tell `react-bootstrap-table2` how to trigger the cell editing.
|
||||
|
||||
#### <a name='cellEdit.blurToSave'>cellEdit.blurToSave - [Bool]</a>
|
||||
Default is `false`, enable it will be able to save the cell automatically when blur from the cell editor.
|
||||
|
||||
#### <a name='cellEdit.nonEditableRows'>cellEdit.nonEditableRows - [Function]</a>
|
||||
`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`)
|
||||
|
||||
#### <a name='cellEdit.timeToCloseMessage'>cellEdit.timeToCloseMessage - [Function]</a>
|
||||
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.
|
||||
|
||||
### <a name='selectRow'>selectRow - [Object]</a>
|
||||
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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
## <a name='cellEdit'>cellEdit - [Object]</a>
|
||||
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: () => { ... }
|
||||
}
|
||||
```
|
||||
|
||||
### <a name='mode'>cellEdit.mode - [String]</a>
|
||||
`cellEdit.mode` possible value is `click` and `dbclick`. **It's required value** that tell `react-bootstrap-table2` how to trigger the cell editing.
|
||||
|
||||
### <a name='blurToSave'>cellEdit.blurToSave - [Bool]</a>
|
||||
Default is `false`, enable it will be able to save the cell automatically when blur from the cell editor.
|
||||
|
||||
### <a name='nonEditableRows'>cellEdit.nonEditableRows - [Function]</a>
|
||||
`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`)
|
||||
|
||||
### <a name='timeToCloseMessage'>cellEdit.timeToCloseMessage - [Function]</a>
|
||||
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.
|
||||
|
||||
### <a name='beforeSaveCell'>cellEdit.beforeSaveCell - [Function]</a>
|
||||
This callback function will be called before triggering cell update.
|
||||
|
||||
```js
|
||||
const cellEdit = {
|
||||
// omit...
|
||||
beforeSaveCell: (oldValue, newValue, row, column) => { ... }
|
||||
}
|
||||
```
|
||||
|
||||
### <a name='afterSaveCell'>cellEdit.afterSaveCell - [Function]</a>
|
||||
This callback function will be called after updating cell.
|
||||
|
||||
```js
|
||||
const cellEdit = {
|
||||
// omit...
|
||||
afterSaveCell: (oldValue, newValue, row, column) => { ... }
|
||||
};
|
||||
```
|
||||
|
||||
### <a name='onUpdate'>cellEdit.onUpdate - [Function]</a>
|
||||
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
|
||||
|
||||
### <a name='editing'>cellEdit.editing - [Bool]</a>
|
||||
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.
|
||||
|
||||
### <a name='errorMessage'>cellEdit.errorMessage - [String]</a>
|
||||
Same as [`cellEdit.editing`](#editing). This prop is not often used. Only used when you keep the error message in your application state.
|
||||
|
||||
### <a name='onErrorMessageDisappear'>cellEdit.onErrorMessageDisappear - [Function]</a>
|
||||
This callback function will be called when error message discard.
|
||||
|
||||
+75
@@ -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 (
|
||||
<div>
|
||||
<BootstrapTableful keyField="id" data={ products } columns={ columns } cellEdit={ cellEdit } />
|
||||
<Code>{ sourceCode }</Code>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
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 (
|
||||
<div>
|
||||
<BootstrapTableful keyField="id" data={ products } columns={ columns } cellEdit={ cellEdit } />
|
||||
<Code>{ sourceCode }</Code>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default CellEditWithPromise;
|
||||
|
||||
+212
@@ -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 (
|
||||
<div>
|
||||
<BootstrapTableful keyField="id" data={ this.props.data } columns={ columns } cellEdit={ cellEdit } />
|
||||
<Code>{ sourceCode }</Code>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
// 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 = () => (
|
||||
<Provider store={store}>
|
||||
<CellEditWithRedux />
|
||||
</Provider>
|
||||
);
|
||||
`;
|
||||
|
||||
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 (
|
||||
<div>
|
||||
<BootstrapTableful keyField="id" data={ this.props.data } columns={ columns } cellEdit={ cellEdit } />
|
||||
<Code>{ sourceCode }</Code>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
// 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 = () => (
|
||||
<Provider store={store}>
|
||||
<CellEditWithRedux />
|
||||
</Provider>
|
||||
);
|
||||
|
||||
export default Index;
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
+68
-16
@@ -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) =>
|
||||
(<h3>{ cell }::: ${ row.price }</h3>)
|
||||
(<h3>{ cell }::: ${ row.price }</h3>),
|
||||
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(
|
||||
<BootstrapTableful keyField="id" data={ products } columns={ columns } />,
|
||||
<BootstrapTableful keyField="id" data={ products } columns={ columns } cellEdit={ cellEdit } />,
|
||||
document.getElementById('example'));
|
||||
|
||||
@@ -19,3 +19,5 @@ export const productsGenerator = (quantity = 5, callback) => {
|
||||
}))
|
||||
);
|
||||
};
|
||||
|
||||
export const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
@@ -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', () => <ColumnLevelEditableTable />)
|
||||
.add('Cell Level Editable', () => <CellLevelEditable />)
|
||||
.add('Rich Hook Functions', () => <CellEditHooks />)
|
||||
.add('Validation', () => <CellEditValidator />);
|
||||
.add('Validation', () => <CellEditValidator />)
|
||||
.add('Async Cell Editing(Promise)', () => <CellEditWithPromise />)
|
||||
.add('Async Cell Editing(Redux)', () => <CellEditWithRedux />);
|
||||
|
||||
storiesOf('Row Selection', module)
|
||||
.add('Single selection', () => <SingleSelectionTable />)
|
||||
.add('Multiple selection', () => <MultipleSelectionTable />);
|
||||
|
||||
|
||||
+42
-62
@@ -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>{ caption }</Caption>
|
||||
<Header
|
||||
columns={ columns }
|
||||
sortField={ this.store.sortField }
|
||||
sortOrder={ this.store.sortOrder }
|
||||
sortField={ store.sortField }
|
||||
sortOrder={ store.sortOrder }
|
||||
onSort={ this.handleSort }
|
||||
selectRow={ headerCellSelectionInfo }
|
||||
/>
|
||||
@@ -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
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
+27
-11
@@ -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 (
|
||||
<td className="react-bootstrap-table-editing-cell">
|
||||
<TextEditor
|
||||
@@ -99,7 +115,7 @@ class EditingCell extends Component {
|
||||
classNames={ editorClass }
|
||||
{ ...editorAttrs }
|
||||
/>
|
||||
{ invalidMessage ? <EditorIndicator invalidMessage={ invalidMessage } /> : null }
|
||||
{ hasError ? <EditorIndicator invalidMessage={ invalidMessage } /> : null }
|
||||
</td>
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
+54
-8
@@ -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 (
|
||||
<CellEditWrapper
|
||||
keyField={ this.props.keyField }
|
||||
cellEdit={ this.props.cellEdit }
|
||||
ref={ node => 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 <Base { ...newProps } store={ this.store } />;
|
||||
return element;
|
||||
}
|
||||
}
|
||||
return StatefulComponent;
|
||||
|
||||
+6
-1
@@ -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);
|
||||
}
|
||||
|
||||
+6
-1
@@ -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
|
||||
};
|
||||
|
||||
+38
-14
@@ -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(<BootstrapTable keyField="id" columns={ columns } data={ data } />);
|
||||
wrapper = shallow(
|
||||
<BootstrapTable keyField="id" columns={ columns } data={ data } store={ store } />);
|
||||
});
|
||||
|
||||
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(<BootstrapTable keyField="id" columns={ columns } data={ data } hover />);
|
||||
wrapper = shallow(
|
||||
<BootstrapTable keyField="id" columns={ columns } data={ data } store={ store } hover />);
|
||||
});
|
||||
|
||||
it('should have table-hover class on table', () => {
|
||||
@@ -62,7 +66,8 @@ describe('BootstrapTable', () => {
|
||||
|
||||
describe('when striped props is true', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<BootstrapTable keyField="id" columns={ columns } data={ data } striped />);
|
||||
wrapper = shallow(
|
||||
<BootstrapTable keyField="id" columns={ columns } data={ data } store={ store } striped />);
|
||||
});
|
||||
|
||||
it('should have table-striped class on table', () => {
|
||||
@@ -72,7 +77,8 @@ describe('BootstrapTable', () => {
|
||||
|
||||
describe('when condensed props is true', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<BootstrapTable keyField="id" columns={ columns } data={ data } condensed />);
|
||||
wrapper = shallow(
|
||||
<BootstrapTable keyField="id" columns={ columns } data={ data } store={ store } condensed />);
|
||||
});
|
||||
|
||||
it('should have table-condensed class on table', () => {
|
||||
@@ -82,7 +88,8 @@ describe('BootstrapTable', () => {
|
||||
|
||||
describe('when bordered props is false', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<BootstrapTable keyField="id" columns={ columns } data={ data } bordered={ false } />);
|
||||
wrapper = shallow(
|
||||
<BootstrapTable keyField="id" columns={ columns } data={ data } store={ store } bordered={ false } />);
|
||||
});
|
||||
|
||||
it('should not have table-condensed class on table', () => {
|
||||
@@ -94,6 +101,7 @@ describe('BootstrapTable', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(
|
||||
<BootstrapTable
|
||||
store={ store }
|
||||
caption={ <span className="table-caption">test</span> }
|
||||
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(
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
store={ store }
|
||||
columns={ columns }
|
||||
data={ data }
|
||||
selectRow={{ mode: 'radio' }}
|
||||
@@ -170,6 +192,7 @@ describe('BootstrapTable', () => {
|
||||
wrapper = shallow(
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
store={ store }
|
||||
columns={ columns }
|
||||
data={ data }
|
||||
selectRow={{ mode: 'checkbox' }}
|
||||
@@ -199,6 +222,7 @@ describe('BootstrapTable', () => {
|
||||
wrapper = shallow(
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
store={ store }
|
||||
columns={ columns }
|
||||
data={ data }
|
||||
/>
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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(
|
||||
<CellEditWrapper
|
||||
keyField={ keyField }
|
||||
cellEdit={ cellEdit }
|
||||
elem={ elem }
|
||||
onUpdateCell={ sinon.stub() }
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
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(
|
||||
<CellEditWrapper
|
||||
keyField={ keyField }
|
||||
cellEdit={ cellEdit }
|
||||
elem={ elem }
|
||||
onUpdateCell={ sinon.stub() }
|
||||
/>
|
||||
);
|
||||
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(
|
||||
<CellEditWrapper
|
||||
keyField={ keyField }
|
||||
cellEdit={ cellEdit }
|
||||
elem={ elem }
|
||||
onUpdateCell={ sinon.stub() }
|
||||
/>
|
||||
);
|
||||
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(
|
||||
<CellEditWrapper
|
||||
keyField={ keyField }
|
||||
cellEdit={ cellEdit }
|
||||
elem={ elem }
|
||||
onUpdateCell={ onUpdateCellCallBack }
|
||||
/>
|
||||
);
|
||||
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(
|
||||
<CellEditWrapper
|
||||
keyField={ keyField }
|
||||
cellEdit={ cellEdit }
|
||||
elem={ elem }
|
||||
onUpdateCell={ onUpdateCellCallBack }
|
||||
/>
|
||||
);
|
||||
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(
|
||||
<CellEditWrapper
|
||||
keyField={ keyField }
|
||||
cellEdit={ cellEdit }
|
||||
elem={ elem }
|
||||
onUpdateCell={ onUpdateCellCallBack }
|
||||
/>
|
||||
);
|
||||
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(
|
||||
<CellEditWrapper
|
||||
keyField={ keyField }
|
||||
cellEdit={ cellEdit }
|
||||
elem={ elem }
|
||||
onUpdateCell={ onUpdateCellCallBack }
|
||||
/>
|
||||
);
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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(
|
||||
<EditingCell
|
||||
row={ row }
|
||||
column={ column }
|
||||
onUpdate={ onUpdate }
|
||||
onEscape={ onEscape }
|
||||
onComplete={ onComplete }
|
||||
/>
|
||||
);
|
||||
});
|
||||
@@ -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', () => {
|
||||
<EditingCell
|
||||
row={ row }
|
||||
column={ column }
|
||||
onUpdate={ onUpdate }
|
||||
onEscape={ onEscape }
|
||||
onComplete={ onComplete }
|
||||
blurToSave
|
||||
/>
|
||||
</TableRowWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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(
|
||||
<Row
|
||||
{...mockBodyResolvedProps}
|
||||
@@ -255,7 +256,7 @@ describe('Row', () => {
|
||||
beforeEach(() => {
|
||||
cellEdit.ridx = 3;
|
||||
cellEdit.cidx = editingColIndex;
|
||||
cellEdit.onComplete = sinon.stub();
|
||||
cellEdit.onUpdate = sinon.stub();
|
||||
cellEdit.onEscape = sinon.stub();
|
||||
wrapper = shallow(
|
||||
<Row
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { BootstrapTable, BootstrapTableful } from '../src';
|
||||
|
||||
describe('withStateful', () => {
|
||||
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(
|
||||
<BootstrapTableful keyField={ keyField } data={ data } columns={ columns } />
|
||||
);
|
||||
});
|
||||
|
||||
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(
|
||||
<BootstrapTableful
|
||||
keyField={ keyField }
|
||||
data={ data }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEdit }
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
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(
|
||||
<BootstrapTableful
|
||||
keyField={ keyField }
|
||||
data={ data }
|
||||
columns={ columns }
|
||||
cellEdit={ cellEdit }
|
||||
/>
|
||||
);
|
||||
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', () => {});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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; }
|
||||
|
||||
Reference in New Issue
Block a user