diff --git a/docs/cell-edit.md b/docs/cell-edit.md index 6c46151..cc56f6c 100644 --- a/docs/cell-edit.md +++ b/docs/cell-edit.md @@ -62,6 +62,24 @@ const cellEdit = { } ``` +If you want to perform a async `beforeSaveCell`, you can do it like that: + +```js +const cellEdit: { + // omit... + beforeSaveCell(oldValue, newValue, row, column, done) { + setTimeout(() => { + if (confirm('Do you want to accep this change?')) { + done(); // contine to save the changes + } else { + done(false); // reject the changes + } + }, 0); + return { async: true }; + } +}; +``` + ### cellEdit.afterSaveCell - [Function] This callback function will be called after updating cell. diff --git a/docs/columns.md b/docs/columns.md index 5bb23a6..34b10c0 100644 --- a/docs/columns.md +++ b/docs/columns.md @@ -379,17 +379,27 @@ A new `String` will be the result of element headerAlign. ## column.events - [Object] -You can assign any [HTML Event](https://www.w3schools.com/tags/ref_eventattributes.asp) on table column via event property: +You can assign any [HTML Event](https://www.w3schools.com/tags/ref_eventattributes.asp) on table column via `events` property. + +`react-bootstrap-table2` currently only support following events which will receive some specific information: + +* onClick +* onDoubleClick +* onMouseEnter +* onMouseLeave +* onContextMenu ```js { // omit... events: { - onClick: e => { ... } + onClick: (e, column, columnIndex, row, rowIndex) => { ... }, } } ``` +If the events is not listed above, the callback function will only pass the `event` object. + ## column.headerEvents - [Object] `headerEvents` same as [`column.events`](#events) but this is for header column. @@ -543,6 +553,28 @@ The return value can be a bool or an object. If your validation is pass, return } ``` +If you want to perform a asycn validation, you can do it like this: +```js +{ + // omit... + validator: (newValue, row, column, done) => { + settimeout(() => { + // async validation ok + return done(); + + // async validation not ok + return done({ + valid: false, + message: 'SOME_REASON_HERE' + }); + + }, 2000); + return { async: true }; + } +} +``` + + ## column.editCellStyle - [Object | Function] You can use `column.editCellStyle` to custom the style of `` when cell editing. It like most of customizable functionality, it also accept a callback function with following params: diff --git a/docs/development.md b/docs/development.md index ade502e..50a0464 100644 --- a/docs/development.md +++ b/docs/development.md @@ -3,7 +3,7 @@ ### Setup ```bash $ git clone https://github.com/react-bootstrap-table/react-bootstrap-table2.git -$ cd react-bootstrap-table +$ cd react-bootstrap-table2 $ npm install $ lerna bootstrap # ./node_modules/.bin/lerna bootstrap ``` @@ -25,4 +25,4 @@ $ npm run storybook $ npm test $ npm run test:watch # for watch mode $ npm run test:coverage # generate coverage report -``` \ No newline at end of file +``` diff --git a/packages/react-bootstrap-table2-editor/src/context.js b/packages/react-bootstrap-table2-editor/src/context.js index c3f37c5..e9317e2 100644 --- a/packages/react-bootstrap-table2-editor/src/context.js +++ b/packages/react-bootstrap-table2-editor/src/context.js @@ -31,6 +31,7 @@ export default ( constructor(props) { super(props); + this.doUpdate = this.doUpdate.bind(this); this.startEditing = this.startEditing.bind(this); this.escapeEditing = this.escapeEditing.bind(this); this.completeEditing = this.completeEditing.bind(this); @@ -55,11 +56,36 @@ export default ( } handleCellUpdate(row, column, newValue) { - const { keyField, cellEdit, data } = this.props; - const { beforeSaveCell, afterSaveCell } = cellEdit.options; + const { cellEdit } = this.props; + const { beforeSaveCell } = cellEdit.options; const oldValue = _.get(row, column.dataField); + const beforeSaveCellDone = (result = true) => { + if (result) { + this.doUpdate(row, column, newValue); + } else { + this.escapeEditing(); + } + }; + if (_.isFunction(beforeSaveCell)) { + const result = beforeSaveCell( + oldValue, + newValue, + row, + column, + beforeSaveCellDone + ); + if (_.isObject(result) && result.async) { + return; + } + } + this.doUpdate(row, column, newValue); + } + + doUpdate(row, column, newValue) { + const { keyField, cellEdit, data } = this.props; + const { afterSaveCell } = cellEdit.options; const rowId = _.get(row, keyField); - if (_.isFunction(beforeSaveCell)) beforeSaveCell(oldValue, newValue, row, column); + const oldValue = _.get(row, column.dataField); if (isRemoteCellEdit()) { handleCellChange(rowId, column.dataField, newValue); } else { diff --git a/packages/react-bootstrap-table2-editor/src/editing-cell.js b/packages/react-bootstrap-table2-editor/src/editing-cell.js index 39aa9a5..7c693b1 100644 --- a/packages/react-bootstrap-table2-editor/src/editing-cell.js +++ b/packages/react-bootstrap-table2-editor/src/editing-cell.js @@ -44,6 +44,8 @@ export default (_, onStartEdit) => this.handleClick = this.handleClick.bind(this); this.handleKeyDown = this.handleKeyDown.bind(this); this.beforeComplete = this.beforeComplete.bind(this); + this.asyncbeforeCompete = this.asyncbeforeCompete.bind(this); + this.displayErrorMessage = this.displayErrorMessage.bind(this); this.state = { invalidMessage: null }; @@ -79,16 +81,41 @@ export default (_, onStartEdit) => }, timeToCloseMessage); } + displayErrorMessage(message) { + this.setState(() => ({ + invalidMessage: message + })); + this.createTimer(); + } + + asyncbeforeCompete(newValue) { + return (result = { valid: true }) => { + const { valid, message } = result; + const { onUpdate, row, column } = this.props; + if (!valid) { + this.displayErrorMessage(message); + return; + } + onUpdate(row, column, newValue); + }; + } + beforeComplete(newValue) { const { onUpdate, row, column } = this.props; if (_.isFunction(column.validator)) { - const validateForm = column.validator(newValue, row, column); - if (_.isObject(validateForm) && !validateForm.valid) { - this.setState(() => ({ - invalidMessage: validateForm.message - })); - this.createTimer(); - return; + const validateForm = column.validator( + newValue, + row, + column, + this.asyncbeforeCompete(newValue) + ); + if (_.isObject(validateForm)) { + if (validateForm.async) { + return; + } else if (!validateForm.valid) { + this.displayErrorMessage(validateForm.message); + return; + } } } onUpdate(row, column, newValue); diff --git a/packages/react-bootstrap-table2-editor/test/context.test.js b/packages/react-bootstrap-table2-editor/test/context.test.js index 03cfd74..57b56bf 100644 --- a/packages/react-bootstrap-table2-editor/test/context.test.js +++ b/packages/react-bootstrap-table2-editor/test/context.test.js @@ -235,7 +235,8 @@ describe('CellEditContext', () => { it('should call cellEdit.beforeSaveCell correctly', () => { expect(beforeSaveCell).toHaveBeenCalledTimes(1); - expect(beforeSaveCell).toHaveBeenCalledWith(oldValue, newValue, row, column); + expect(beforeSaveCell) + .toHaveBeenCalledWith(oldValue, newValue, row, column, expect.anything()); }); }); diff --git a/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-async-hooks-table.js b/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-async-hooks-table.js new file mode 100644 index 0000000..9839437 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-async-hooks-table.js @@ -0,0 +1,86 @@ +/* eslint no-unused-vars: 0 */ +/* eslint no-console: 0 */ +/* eslint no-alert: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; +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 = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +function beforeSaveCell(oldValue, newValue, row, column, done) { + setTimeout(() => { + if (confirm('Do you want to accep this change?')) { + done(true); + } else { + done(false); + } + }, 0); + return { async: true }; +} + + +`; + +function beforeSaveCell(oldValue, newValue, row, column, done) { + setTimeout(() => { + if (confirm('Do you want to accep this change?')) { + done(true); + } else { + done(false); + } + }, 0); + return { async: true }; +} + +export default () => ( +
+

You will get a confirm prompt when you try to save a cell

+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-async-validator-table.js b/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-async-validator-table.js new file mode 100644 index 0000000..cd3c934 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-async-validator-table.js @@ -0,0 +1,101 @@ +/* eslint no-unused-vars: 0 */ +import React from 'react'; +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; +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, done) => { + setTimeout(() => { + if (isNaN(newValue)) { + return done({ + valid: false, + message: 'Price should be numeric' + }); + } + if (newValue < 2000) { + return done({ + valid: false, + message: 'Price should bigger than 2000' + }); + } + return done(); + }, 2000); + return { + async: true + }; + } +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import cellEditFactory from 'react-bootstrap-table2-editor'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price', + validator: (newValue, row, column, done) => { + setTimeout(() => { + if (isNaN(newValue)) { + return done({ + valid: false, + message: 'Price should be numeric' + }); + } + if (newValue < 2000) { + return done({ + valid: false, + message: 'Price should bigger than 2000' + }); + } + return done(); + }, 2000); + return { + async: true + }; + } +}]; + + +`; + +export default () => ( +
+

Product Price should bigger than $2000

+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/columns/column-event-table.js b/packages/react-bootstrap-table2-example/examples/columns/column-event-table.js index 32f0d69..7f3e308 100644 --- a/packages/react-bootstrap-table2-example/examples/columns/column-event-table.js +++ b/packages/react-bootstrap-table2-example/examples/columns/column-event-table.js @@ -1,5 +1,6 @@ /* eslint no-unused-vars: 0 */ /* eslint no-alert: 0 */ +/* eslint no-console: 0 */ import React from 'react'; import BootstrapTable from 'react-bootstrap-table-next'; @@ -12,7 +13,22 @@ const columns = [{ dataField: 'id', text: 'Product ID', events: { - onClick: () => alert('Click on Product ID field') + onClick: (e, column, columnIndex, row, rowIndex) => { + console.log(e); + console.log(column); + console.log(columnIndex); + console.log(row); + console.log(rowIndex); + alert('Click on Product ID field'); + }, + onMouseEnter: (e, column, columnIndex, row, rowIndex) => { + console.log(e); + console.log(column); + console.log(columnIndex); + console.log(row); + console.log(rowIndex); + console.log('onMouseEnter on Product ID field'); + } } }, { dataField: 'name', @@ -29,7 +45,22 @@ const columns = [{ dataField: 'id', text: 'Product ID', events: { - onClick: () => alert('Click on Product ID field') + onClick: (e, column, columnIndex, row, rowIndex) => { + console.log(e); + console.log(column); + console.log(columnIndex); + console.log(row); + console.log(rowIndex); + alert('Click on Product ID field'); + }, + onMouseEnter: (e, column, columnIndex, row, rowIndex) => { + console.log(e); + console.log(column); + console.log(columnIndex); + console.log(row); + console.log(rowIndex); + console.log('onMouseEnter on Product ID field'); + } } }, { dataField: 'name', @@ -44,7 +75,7 @@ const columns = [{ export default () => (
-

Try to Click on Product ID columns

+

Try to Click or Mouse over on Product ID columns

{ sourceCode }
diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js index 519542e..7ba6b1b 100644 --- a/packages/react-bootstrap-table2-example/stories/index.js +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -97,7 +97,9 @@ import RowLevelEditableTable from 'examples/cell-edit/row-level-editable-table'; import ColumnLevelEditableTable from 'examples/cell-edit/column-level-editable-table'; import CellLevelEditable from 'examples/cell-edit/cell-level-editable-table'; import CellEditHooks from 'examples/cell-edit/cell-edit-hooks-table'; +import AsyncCellEditHooks from 'examples/cell-edit/cell-edit-async-hooks-table'; import CellEditValidator from 'examples/cell-edit/cell-edit-validator-table'; +import AsyncCellEditValidator from 'examples/cell-edit/cell-edit-async-validator-table'; import CellEditStyleTable from 'examples/cell-edit/cell-edit-style-table'; import CellEditClassTable from 'examples/cell-edit/cell-edit-class-table'; import AutoSelectTextInput from 'examples/cell-edit/auto-select-text-input-table'; @@ -288,7 +290,9 @@ storiesOf('Cell Editing', module) .add('Column Level Editable', () => ) .add('Cell Level Editable', () => ) .add('Rich Hook Functions', () => ) + .add('Async Hook Functions', () => ) .add('Validation', () => ) + .add('Async Validation', () => ) .add('Auto Select Text Input', () => ) .add('Custom Cell Style', () => ) .add('Custom Cell Classes', () => ) diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js index 2bf37a3..0c5c862 100644 --- a/packages/react-bootstrap-table2/src/bootstrap-table.js +++ b/packages/react-bootstrap-table2/src/bootstrap-table.js @@ -15,13 +15,12 @@ class BootstrapTable extends PropsBaseResolver(Component) { super(props); this.validateProps(); if (props.registerExposedAPI) { - const getData = () => this.getData(); - props.registerExposedAPI(getData); + props.registerExposedAPI(this.getData); } } // Exposed APIs - getData = () => { + getData() { return this.props.data; } diff --git a/packages/react-bootstrap-table2/src/row-event-delegater.js b/packages/react-bootstrap-table2/src/cell-event-delegater.js similarity index 80% rename from packages/react-bootstrap-table2/src/row-event-delegater.js rename to packages/react-bootstrap-table2/src/cell-event-delegater.js index 4727134..41e0f71 100644 --- a/packages/react-bootstrap-table2/src/row-event-delegater.js +++ b/packages/react-bootstrap-table2/src/cell-event-delegater.js @@ -7,17 +7,16 @@ const events = [ ]; export default ExtendBase => - class RowEventDelegater extends ExtendBase { + class CellEventDelegater extends ExtendBase { constructor(props) { super(props); - this.clickNum = 0; this.createDefaultEventHandler = this.createDefaultEventHandler.bind(this); } createDefaultEventHandler(cb) { return (e) => { - const { row, rowIndex } = this.props; - cb(e, row, rowIndex); + const { column, columnIndex } = this.props; + cb(e, column, columnIndex); }; } diff --git a/packages/react-bootstrap-table2/src/cell.js b/packages/react-bootstrap-table2/src/cell.js index 70b2633..46b10ea 100644 --- a/packages/react-bootstrap-table2/src/cell.js +++ b/packages/react-bootstrap-table2/src/cell.js @@ -2,9 +2,10 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import eventDelegater from './cell-event-delegater'; import _ from './utils'; -class Cell extends Component { +class Cell extends eventDelegater(Component) { constructor(props) { super(props); this.handleEditingCell = this.handleEditingCell.bind(this); @@ -73,7 +74,7 @@ class Cell extends Component { formatter, formatExtraData } = column; - const attrs = { ...rest }; + const attrs = this.delegate({ ...rest }); let content = column.isDummyField ? null : _.get(row, dataField); if (formatter) { diff --git a/packages/react-bootstrap-table2/src/row/row-pure-content.js b/packages/react-bootstrap-table2/src/row/row-pure-content.js index 5b498ff..95ab857 100644 --- a/packages/react-bootstrap-table2/src/row/row-pure-content.js +++ b/packages/react-bootstrap-table2/src/row/row-pure-content.js @@ -50,13 +50,21 @@ export default class RowPureContent extends React.Component { // render cell let cellTitle; let cellStyle = {}; - const cellAttrs = { + let cellAttrs = { ..._.isFunction(column.attrs) ? column.attrs(content, row, rowIndex, index) - : column.attrs, - ...column.events + : column.attrs }; + if (column.events) { + const events = Object.assign({}, column.events); + Object.keys(Object.assign({}, column.events)).forEach((key) => { + const originFn = events[key]; + events[key] = (...rest) => originFn(...rest, row, rowIndex); + }); + cellAttrs = { ...cellAttrs, ...events }; + } + const cellClasses = _.isFunction(column.classes) ? column.classes(content, row, rowIndex, index) : column.classes;