Skip to content
Next Next commit
support async cell editing
  • Loading branch information
AllenFang committed Oct 17, 2017
commit 9a1cffb7c60393d18ff1e7506c3500beeeeee60d
50 changes: 41 additions & 9 deletions packages/react-bootstrap-table2/src/bootstrap-table.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,32 @@ class BootstrapTable extends PropsBaseResolver(Component) {
this.completeEditing = this.completeEditing.bind(this);
this.handleRowSelect = this.handleRowSelect.bind(this);
this.handleAllRowsSelect = this.handleAllRowsSelect.bind(this);
this.handleCellUpdate = this.handleCellUpdate.bind(this);
this.state = {
data: this.store.get(),
selectedRowKeys: this.store.getSelectedRowKeys(),
currEditCell: {
ridx: null,
cidx: null
cidx: null,
message: null
}
};
}

componentWillReceiveProps({ cellEdit }) {
if (_.isDefined(cellEdit) && _.isDefined(cellEdit.errorMessage)) {
const { currEditCell } = this.state;
this.setState(() => {
return {
currEditCell: {
...currEditCell,
message: cellEdit.errorMessage
}
};
});
}
}

render() {
const {
columns,
Expand All @@ -56,7 +72,7 @@ class BootstrapTable extends PropsBaseResolver(Component) {
const cellEditInfo = this.resolveCellEditProps({
onStart: this.startEditing,
onEscape: this.escapeEditing,
onComplete: this.completeEditing
onUpdate: this.handleCellUpdate
});

const cellSelectionInfo = this.resolveCellSelectionProps({
Expand Down Expand Up @@ -149,15 +165,20 @@ class BootstrapTable extends PropsBaseResolver(Component) {
});
}

completeEditing(row, column, newValue) {
const { cellEdit, keyField } = this.props;
const { beforeSaveCell, onEditing, afterSaveCell } = cellEdit;
handleCellUpdate(row, column, newValue) {
const { cellEdit, keyField, 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);
onEditing(rowId, column.dataField, newValue);
if (_.isFunction(afterSaveCell)) afterSaveCell(oldValue, newValue, row, column);

if (onUpdateCell(rowId, column.dataField, newValue)) {
if (_.isFunction(afterSaveCell)) afterSaveCell(oldValue, newValue, row, column);
this.completeEditing();
}
}

completeEditing() {
this.setState(() => {
return {
data: this.store.get(),
Expand All @@ -181,6 +202,15 @@ class BootstrapTable extends PropsBaseResolver(Component) {
};
});
}

updateEditingWithErr(message) {
this.setState(() => {
const { currEditCell } = this.state;
return {
currEditCell: { ...currEditCell, message }
};
});
}
}

BootstrapTable.propTypes = {
Expand All @@ -199,13 +229,15 @@ BootstrapTable.propTypes = {
]),
cellEdit: PropTypes.shape({
mode: PropTypes.oneOf([Const.CLICK_TO_CELL_EDIT, Const.DBCLICK_TO_CELL_EDIT]).isRequired,
onEditing: PropTypes.func.isRequired,
onEditing: PropTypes.func,
blurToSave: PropTypes.bool,
beforeSaveCell: PropTypes.func,
afterSaveCell: PropTypes.func,
nonEditableRows: PropTypes.func,
timeToCloseMessage: PropTypes.number
timeToCloseMessage: PropTypes.number,
errorMessage: PropTypes.string
}),
onUpdateCell: PropTypes.func,
selectRow: PropTypes.shape({
mode: PropTypes.oneOf([Const.ROW_SELECT_SINGLE, Const.ROW_SELECT_MULTIPLE]).isRequired
})
Expand Down
36 changes: 26 additions & 10 deletions packages/react-bootstrap-table2/src/editing-cell.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ class EditingCell extends Component {
};
}

componentWillReceiveProps({ message }) {
if (_.isDefined(message)) {
this.clearTimer();
this.createTimer();
this.setState(() => {
return { invalidMessage: message };
});
}
}

componentWillUnmount() {
this.clearTimer();
}
Expand All @@ -33,24 +43,29 @@ class EditingCell extends Component {
}
}

createTimer() {
const { timeToCloseMessage } = this.props;
this.indicatorTimer = _.sleep(() => {
this.setState(() => {
return { invalidMessage: null };
});
}, timeToCloseMessage);
}

beforeComplete(row, column, newValue) {
this.clearTimer();
const { onComplete, timeToCloseMessage } = this.props;
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() {
Expand Down Expand Up @@ -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
Expand All @@ -99,7 +115,7 @@ class EditingCell extends Component {
classNames={ editorClass }
{ ...editorAttrs }
/>
{ invalidMessage ? <EditorIndicator invalidMessage={ invalidMessage } /> : null }
{ hasError ? <EditorIndicator invalidMessage={ invalidMessage } /> : null }
</td>
);
}
Expand All @@ -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
};
Expand Down
43 changes: 34 additions & 9 deletions packages/react-bootstrap-table2/src/stateful-layer.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,51 @@
/* 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 _ 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);
handleUpdateCell(rowId, dataField, newValue) {
const { cellEdit } = this.props;
// handle cell editing internal
if (!cellEdit.onEditing) {
this.store.edit(rowId, dataField, newValue);
return true;
}

// handle cell editing external
const result = cellEdit.onEditing(rowId, dataField, newValue);
if (_.isDefined(result)) { // TODO: should be a promise here
result.then((response) => {
if (response.forceUpdate) {
this.updateCell(rowId, dataField, response.value || newValue);
this.table.completeEditing();
}
}).catch((e) => {
this.table.updateEditingWithErr(e.message);
});
}
return false;
}

render() {
const { props } = this;
const newProps = { ...props };
if (newProps.cellEdit && !newProps.cellEdit.onEditing) {
newProps.cellEdit.onEditing = this.edit;
}
return <Base { ...newProps } store={ this.store } />;
return (
<Base
{ ...this.props }
ref={ node => this.table = node }
store={ this.store }
onUpdateCell={ this.handleUpdateCell }
/>
);
}
}
return StatefulComponent;
Expand Down
7 changes: 6 additions & 1 deletion packages/react-bootstrap-table2/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
};