Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,21 @@ Following is a `cellEdit` object:
{
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.
`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.
24 changes: 23 additions & 1 deletion docs/columns.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Available properties in a column object:
* [headerAlign](#headerAlign)
* [headerAttrs](#headerAttrs)
* [editable](#editable)
* [validator](#validator)

Following is a most simplest and basic usage:

Expand Down Expand Up @@ -417,4 +418,25 @@ A new `Object` will be the result of element headerAttrs.
> overwrited when other props related to HTML attributes were given.

## <a name='editable'>column.editable - [Bool]</a>
`column.editable` default is true, means every column is editable if you configure [`cellEdit`](./README.md#cellEdit). But you can disable some columns editable via setting `false`.
`column.editable` default is true, means every column is editable if you configure [`cellEdit`](./README.md#cellEdit). But you can disable some columns editable via setting `false`.

## <a name='validator'>column.validator - [Function]</a>
`column.validator` used for validate the data when cell on updating. it's should accept a callback function with following argument:
`newValue`, `row` and `column`:

```js
{
// omit...
validator: (newValue, row, column) => {
return ...;
}
}
```

The return value can be a bool or an object. If your valiation is pass, return `true` explicitly. If your valiation is invalid, return following object instead:
```js
{
valid: false,
message: 'SOME_REASON_HERE'
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React from 'react';
/* eslint no-unused-vars: 0 */
import { BootstrapTableful } 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',
validator: (newValue, row, column) => {
if (isNaN(newValue)) {
return {
valid: false,
message: 'Price should be numeric'
};
}
if (newValue < 2000) {
return {
valid: false,
message: 'Price should bigger than 2000'
};
}
return true;
}
}];

const sourceCode = `\
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'price',
text: 'Product Price',
validator: (newValue, row, column) => {
if (isNaN(newValue)) {
return {
valid: false,
message: 'Price should be numeric'
};
}
if (newValue < 2000) {
return {
valid: false,
message: 'Price should bigger than 2000'
};
}
return true;
}
}];

const cellEdit = {
mode: 'click',
blurToSave: true
};

<BootstrapTableful
keyField='id'
data={ products }
columns={ columns }
cellEdit={ cellEdit }
/>
`;

const cellEdit = {
mode: 'click',
blurToSave: true
};
export default () => (
<div>
<h3>Product Price should bigger than $2000</h3>
<BootstrapTableful keyField="id" data={ products } columns={ columns } cellEdit={ cellEdit } />
<Code>{ sourceCode }</Code>
</div>
);
4 changes: 3 additions & 1 deletion packages/react-bootstrap-table2-example/stories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import BlurToSaveTable from 'examples/cell-edit/blur-to-save-table';
import RowLevelEditableTable from 'examples/cell-edit/row-level-editable-table';
import ColumnLevelEditableTable from 'examples/cell-edit/column-level-editable-table';
import CellEditHooks from 'examples/cell-edit/cell-edit-hooks-table';
import CellEditValidator from 'examples/cell-edit/cell-edit-validator-table';

// css style
import 'bootstrap/dist/css/bootstrap.min.css';
Expand Down Expand Up @@ -92,4 +93,5 @@ storiesOf('Cell Editing', module)
.add('Blur to Save Cell', () => <BlurToSaveTable />)
.add('Row Level Editable', () => <RowLevelEditableTable />)
.add('Column Level Editable', () => <ColumnLevelEditableTable />)
.add('Rich Hook Functions', () => <CellEditHooks />);
.add('Rich Hook Functions', () => <CellEditHooks />)
.add('Validation', () => <CellEditValidator />);
3 changes: 2 additions & 1 deletion packages/react-bootstrap-table2/src/bootstrap-table.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ BootstrapTable.propTypes = {
blurToSave: PropTypes.bool,
beforeSaveCell: PropTypes.func,
afterSaveCell: PropTypes.func,
nonEditableRows: PropTypes.func
nonEditableRows: PropTypes.func,
timeToCloseMessage: PropTypes.number
})
};

Expand Down
3 changes: 2 additions & 1 deletion packages/react-bootstrap-table2/src/const.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ export default {
SORT_DESC: 'desc',
UNABLE_TO_CELL_EDIT: 'none',
CLICK_TO_CELL_EDIT: 'click',
DBCLICK_TO_CELL_EDIT: 'dbclick'
DBCLICK_TO_CELL_EDIT: 'dbclick',
TIME_TO_CLOSE_MESSAGE: 3000
};
68 changes: 61 additions & 7 deletions packages/react-bootstrap-table2/src/editing-cell.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,86 @@
/* eslint arrow-body-style: 0 */
/* eslint react/prop-types: 0 */
/* eslint no-return-assign: 0 */
import React, { Component } from 'react';
import cs from 'classnames';
import PropTypes from 'prop-types';

import _ from './utils';
import Const from './const';
import TextEditor from './text-editor';
import EditorIndicator from './editor-indicator';

class EditingCell extends Component {
constructor(props) {
super(props);
this.indicatorTimer = null;
this.clearTimer = this.clearTimer.bind(this);
this.handleBlur = this.handleBlur.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
this.beforeComplete = this.beforeComplete.bind(this);
this.state = {
invalidMessage: null
};
}

componentWillUnmount() {
this.clearTimer();
}

clearTimer() {
if (this.indicatorTimer) {
clearTimeout(this.indicatorTimer);
}
}

beforeComplete(row, column, newValue) {
this.clearTimer();
const { onComplete, timeToCloseMessage } = 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);
return;
}
}
onComplete(row, column, newValue);
}

handleBlur() {
const { onEscape, onComplete, blurToSave, row, column } = this.props;
const { onEscape, blurToSave, row, column } = this.props;
if (blurToSave) {
const value = this.editor.text.value;
if (!_.isDefined(value)) {
// TODO: for other custom or embed editor
}
onComplete(row, column, value);
this.beforeComplete(row, column, value);
} else {
onEscape();
}
}

handleKeyDown(e) {
const { onEscape, onComplete, row, column } = this.props;
const { onEscape, row, column } = this.props;
if (e.keyCode === 27) { // ESC
onEscape();
} else if (e.keyCode === 13) { // ENTER
const value = e.currentTarget.value;
if (!_.isDefined(value)) {
// TODO: for other custom or embed editor
}
onComplete(row, column, value);
this.beforeComplete(row, column, value);
}
}

render() {
const { invalidMessage } = this.state;
const { row, column } = this.props;
const { dataField } = column;

Expand All @@ -48,9 +89,17 @@ class EditingCell extends Component {
onKeyDown: this.handleKeyDown,
onBlur: this.handleBlur
};

const editorClass = invalidMessage ? cs('animated', 'shake') : null;
return (
<td>
<TextEditor ref={ node => this.editor = node } defaultValue={ value } { ...editorAttrs } />
<td className="react-bootstrap-table-editing-cell">
<TextEditor
ref={ node => this.editor = node }
defaultValue={ value }
classNames={ editorClass }
{ ...editorAttrs }
/>
{ invalidMessage ? <EditorIndicator invalidMessage={ invalidMessage } /> : null }
</td>
);
}
Expand All @@ -60,7 +109,12 @@ EditingCell.propTypes = {
row: PropTypes.object.isRequired,
column: PropTypes.object.isRequired,
onComplete: PropTypes.func.isRequired,
onEscape: PropTypes.func.isRequired
onEscape: PropTypes.func.isRequired,
timeToCloseMessage: PropTypes.number
};

EditingCell.defaultProps = {
timeToCloseMessage: Const.TIME_TO_CLOSE_MESSAGE
};

export default EditingCell;
19 changes: 19 additions & 0 deletions packages/react-bootstrap-table2/src/editor-indicator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* eslint no-return-assign: 0 */
import React from 'react';
import PropTypes from 'prop-types';

const EditorIndicator = ({ invalidMessage }) =>
(
<div className="alert alert-danger fade in">
<strong>{ invalidMessage }</strong>
</div>
);

EditorIndicator.propTypes = {
invalidMessage: PropTypes.string
};

EditorIndicator.defaultProps = {
invalidMessage: null
};
export default EditorIndicator;
4 changes: 3 additions & 1 deletion packages/react-bootstrap-table2/src/header-cell.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ HeaderCell.propTypes = {
headerAlign: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
align: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
sort: PropTypes.bool,
sortFunc: PropTypes.func
sortFunc: PropTypes.func,
editable: PropTypes.bool,
validator: PropTypes.func
}).isRequired,
index: PropTypes.number.isRequired,
onSort: PropTypes.func,
Expand Down
12 changes: 4 additions & 8 deletions packages/react-bootstrap-table2/src/row.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,11 @@ const Row = (props) => {
editable: editableRow
} = props;
const {
ridx: editingRowIdx,
cidx: editingColIdx,
mode,
onStart,
onEscape,
onComplete,
blurToSave
ridx: editingRowIdx,
cidx: editingColIdx,
...rest
} = cellEdit;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @AllenFang,

Sorry for comment late. I saw cellEdit was passed as prop but neither marked as required nor with defaultProps. Therefore, I have one suggestion to set defaultProp of cellEdit to empty object or anthing else to prevent missing prop issue :)

return (
<tr>
Expand All @@ -36,9 +34,7 @@ const Row = (props) => {
key={ _.get(row, column.dataField) }
row={ row }
column={ column }
blurToSave={ blurToSave }
onComplete={ onComplete }
onEscape={ onEscape }
{ ...rest }
/>
);
}
Expand Down
Loading