From cb6410bbe487669a472b18307f924b790c336274 Mon Sep 17 00:00:00 2001 From: Allen Date: Wed, 18 Oct 2017 09:15:41 -0500 Subject: [PATCH] fix #101 * implement cell editor style/class * add stories for custom cell editor style and class * patch testing for cell editor style and class * patch docs for cell editor style and class --- docs/columns.md | 44 +++++++ .../cell-edit/cell-edit-class-table.js | 59 +++++++++ .../cell-edit/cell-edit-style-table.js | 67 ++++++++++ .../stories/index.js | 4 + .../stories/stylesheet/cell-edit/_index.scss | 11 ++ .../stories/stylesheet/storybook.scss | 1 + .../src/cell-edit/editing-cell.js | 15 ++- .../react-bootstrap-table2/src/header-cell.js | 2 + packages/react-bootstrap-table2/src/row.js | 10 ++ .../test/cell-edit/editing-cell.test.js | 40 ++++++ .../react-bootstrap-table2/test/row.test.js | 116 ++++++++++++++++++ 11 files changed, 365 insertions(+), 4 deletions(-) create mode 100644 packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-class-table.js create mode 100644 packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-style-table.js create mode 100644 packages/react-bootstrap-table2-example/stories/stylesheet/cell-edit/_index.scss diff --git a/docs/columns.md b/docs/columns.md index f465c3b..7fba38a 100644 --- a/docs/columns.md +++ b/docs/columns.md @@ -27,6 +27,8 @@ Available properties in a column object: * [headerAttrs](#headerAttrs) * [editable](#editable) * [validator](#validator) +* [editCellStyle](#editCellStyle) +* [editCellClasses](#editCellClasses) Following is a most simplest and basic usage: @@ -450,4 +452,46 @@ The return value can be a bool or an object. If your valiation is pass, return ` valid: false, message: 'SOME_REASON_HERE' } +``` + +## 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: + +**Parameters** +* `cell`: The value of current cell. +* `row`: The object of `row` being processed in the `BootstrapTable`. +* `rowIndex`: The index of the current `row` being processed in the `BootstrapTable`. +* `colIndex`: The index of the current `column` being processed in `BootstrapTable`. + +```js +{ + editCellStyle: { ... } +} +``` +Or take a callback function + +```js +{ + editCellStyle: (cell, row, rowIndex, colIndex) => { + // it is suppose to return an object + } +} +``` + +## column.editCellClasses - [Object | Function] +You can use `column.editCellClasses` to add custom class on `` when cell editing. It's same as [`column.editCellStyle`](#editCellStyle) which also accept a callback function to able to custom your class more flexible. Following is the arguments of this callback function: `cell`, `row`, `rowIndex`, `colIndex`. + +```js +{ + editCellClasses: 'custom-class' +} +``` +Or take a callback function + +```js +{ + editCellClasses: (cell, row, rowIndex, colIndex) => { + // it is suppose to return a string + } +} ``` \ No newline at end of file diff --git a/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-class-table.js b/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-class-table.js new file mode 100644 index 0000000..3486700 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-class-table.js @@ -0,0 +1,59 @@ +/* eslint no-unused-vars: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table2'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + editCellClasses: 'editing-name' +}, { + dataField: 'price', + text: 'Product Price', + editCellClasses: (cell, row, rowIndex, colIndex) => + (cell > 2101 ? 'editing-price-bigger-than-2101' : 'editing-price-small-than-2101') +}]; + +const sourceCode = `\ +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + editCellClasses: 'editing-name' +}, { + dataField: 'price', + text: 'Product Price', + editCellClasses: (cell, row, rowIndex, colIndex) => + (cell > 2101 ? 'editing-price-bigger-than-2101' : 'editing-price-small-than-2101') +}]; + +const cellEdit = { + mode: 'click' +}; + + +`; + +const cellEdit = { + mode: 'click' +}; +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-style-table.js b/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-style-table.js new file mode 100644 index 0000000..2101bd3 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/cell-edit/cell-edit-style-table.js @@ -0,0 +1,67 @@ +/* eslint no-unused-vars: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table2'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + editCellStyle: { + backgroundColor: '#20B2AA' + } +}, { + dataField: 'price', + text: 'Product Price', + editCellStyle: (cell, row, rowIndex, colIndex) => { + const backgroundColor = cell > 2101 ? '#00BFFF' : '#00FFFF'; + return { backgroundColor }; + } +}]; + +const sourceCode = `\ +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + editCellStyle: { + backgroundColor: '#20B2AA' + } +}, { + dataField: 'price', + text: 'Product Price', + editCellStyle: (cell, row, rowIndex, colIndex) => { + const backgroundColor = cell > 2101 ? '#00BFFF' : '#00FFFF'; + return { backgroundColor }; + } +}]; + +const cellEdit = { + mode: 'click' +}; + + +`; + +const cellEdit = { + mode: 'click' +}; +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js index d3c1bec..b96d8b9 100644 --- a/packages/react-bootstrap-table2-example/stories/index.js +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -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 CellEditStyleTable from 'examples/cell-edit/cell-edit-style-table'; +import CellEditClassTable from 'examples/cell-edit/cell-edit-class-table'; import CellEditWithPromise from 'examples/cell-edit/cell-edit-with-promise-table'; import CellEditWithRedux from 'examples/cell-edit/cell-edit-with-redux-table'; @@ -105,6 +107,8 @@ storiesOf('Cell Editing', module) .add('Cell Level Editable', () => ) .add('Rich Hook Functions', () => ) .add('Validation', () => ) + .add('Custom Cell Style When Editing', () => ) + .add('Custom Cell Classes When Editing', () => ) .add('Async Cell Editing(Promise)', () => ) .add('Async Cell Editing(Redux)', () => ); diff --git a/packages/react-bootstrap-table2-example/stories/stylesheet/cell-edit/_index.scss b/packages/react-bootstrap-table2-example/stories/stylesheet/cell-edit/_index.scss new file mode 100644 index 0000000..23f543a --- /dev/null +++ b/packages/react-bootstrap-table2-example/stories/stylesheet/cell-edit/_index.scss @@ -0,0 +1,11 @@ +.editing-name { + background-color: #20B2AA; +} + +.editing-price-bigger-than-2101 { + background-color: #00BFFF; +} + +.editing-price-small-than-2101 { + background-color: #00FFFF; +} \ No newline at end of file diff --git a/packages/react-bootstrap-table2-example/stories/stylesheet/storybook.scss b/packages/react-bootstrap-table2-example/stories/stylesheet/storybook.scss index d5de950..3c33215 100644 --- a/packages/react-bootstrap-table2-example/stories/stylesheet/storybook.scss +++ b/packages/react-bootstrap-table2-example/stories/stylesheet/storybook.scss @@ -5,3 +5,4 @@ @import "welcome/index"; @import "columns/index"; +@import "cell-edit/index"; \ No newline at end of file diff --git a/packages/react-bootstrap-table2/src/cell-edit/editing-cell.js b/packages/react-bootstrap-table2/src/cell-edit/editing-cell.js index 3102f58..ee432dd 100644 --- a/packages/react-bootstrap-table2/src/cell-edit/editing-cell.js +++ b/packages/react-bootstrap-table2/src/cell-edit/editing-cell.js @@ -96,7 +96,7 @@ class EditingCell extends Component { render() { const { invalidMessage } = this.state; - const { row, column } = this.props; + const { row, column, className, style } = this.props; const { dataField } = column; const value = _.get(row, dataField); @@ -108,7 +108,10 @@ class EditingCell extends Component { const hasError = _.isDefined(invalidMessage); const editorClass = hasError ? cs('animated', 'shake') : null; return ( - + this.editor = node } defaultValue={ value } @@ -126,11 +129,15 @@ EditingCell.propTypes = { column: PropTypes.object.isRequired, onUpdate: PropTypes.func.isRequired, onEscape: PropTypes.func.isRequired, - timeToCloseMessage: PropTypes.number + timeToCloseMessage: PropTypes.number, + className: PropTypes.string, + style: PropTypes.object }; EditingCell.defaultProps = { - timeToCloseMessage: Const.TIME_TO_CLOSE_MESSAGE + timeToCloseMessage: Const.TIME_TO_CLOSE_MESSAGE, + className: null, + style: {} }; export default EditingCell; diff --git a/packages/react-bootstrap-table2/src/header-cell.js b/packages/react-bootstrap-table2/src/header-cell.js index 30960bf..15f7b52 100644 --- a/packages/react-bootstrap-table2/src/header-cell.js +++ b/packages/react-bootstrap-table2/src/header-cell.js @@ -105,6 +105,8 @@ HeaderCell.propTypes = { sort: PropTypes.bool, sortFunc: PropTypes.func, editable: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]), + editCellStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), + editCellClasses: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), validator: PropTypes.func }).isRequired, index: PropTypes.number.isRequired, diff --git a/packages/react-bootstrap-table2/src/row.js b/packages/react-bootstrap-table2/src/row.js index b0b99a9..b183cd9 100644 --- a/packages/react-bootstrap-table2/src/row.js +++ b/packages/react-bootstrap-table2/src/row.js @@ -53,11 +53,21 @@ const Row = (props) => { editable = column.editable(content, row, rowIndex, index); } if (rowIndex === editingRowIdx && index === editingColIdx) { + let style = column.editCellStyle || {}; + let classes = column.editCellClasses; + if (_.isFunction(column.editCellStyle)) { + style = column.editCellStyle(content, row, rowIndex, index); + } + if (_.isFunction(column.editCellClasses)) { + classes = column.editCellClasses(content, row, rowIndex, index); + } return ( ); diff --git a/packages/react-bootstrap-table2/test/cell-edit/editing-cell.test.js b/packages/react-bootstrap-table2/test/cell-edit/editing-cell.test.js index c6157ab..1eee68f 100644 --- a/packages/react-bootstrap-table2/test/cell-edit/editing-cell.test.js +++ b/packages/react-bootstrap-table2/test/cell-edit/editing-cell.test.js @@ -75,6 +75,46 @@ describe('EditingCell', () => { expect(onEscape.callCount).toBe(1); }); + describe('if style prop is defined', () => { + const customStyle = { backgroundColor: 'red' }; + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should render component with style successfully', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.find('td').prop('style')).toEqual(customStyle); + }); + }); + + describe('if className prop is defined', () => { + const className = 'test-class'; + beforeEach(() => { + wrapper = shallow( + + ); + }); + + it('should render component with style successfully', () => { + expect(wrapper.length).toBe(1); + expect(wrapper.hasClass(className)).toBe(true); + }); + }); + describe('if blurToSave prop is true', () => { beforeEach(() => { wrapper = mount( diff --git a/packages/react-bootstrap-table2/test/row.test.js b/packages/react-bootstrap-table2/test/row.test.js index f304dd8..56b6b32 100644 --- a/packages/react-bootstrap-table2/test/row.test.js +++ b/packages/react-bootstrap-table2/test/row.test.js @@ -249,6 +249,122 @@ describe('Row', () => { expect(wrapper.find(EditingCell).length).toBe(1); expect(complexComponents.at(editingColIndex).type()).toEqual(EditingCell); }); + + describe('if column.editCellStyle defined as object', () => { + const definedStyleColIndex = editingColIndex; + + beforeEach(() => { + columns[definedStyleColIndex].editCellStyle = { backgroundColor: 'red' }; + wrapper = shallow( + + ); + }); + + it('should also rendering EditingCell with correct style object', () => { + expect(wrapper.find(EditingCell).length).toBe(1); + expect(wrapper.find(EditingCell).props().style) + .toEqual(columns[definedStyleColIndex].editCellStyle); + }); + }); + + describe('if column.editCellStyle defined as function', () => { + const definedStyleColIndex = editingColIndex; + const customStyle = { backgroundColor: 'red' }; + let editCellStyleCallBack; + + beforeEach(() => { + editCellStyleCallBack = sinon.stub().returns(customStyle); + columns[definedStyleColIndex].editCellStyle = editCellStyleCallBack; + wrapper = shallow( + + ); + }); + + it('should calling custom column.editCellStyle callback correctly', () => { + expect(editCellStyleCallBack.callCount).toBe(1); + expect( + editCellStyleCallBack.calledWith( + row[columns[editingColIndex].dataField], row, rowIndex, editingColIndex) + ).toBe(true); + }); + + it('should also rendering EditingCell with correct style object', () => { + expect(wrapper.find(EditingCell).length).toBe(1); + expect(wrapper.find(EditingCell).props().style).toEqual(customStyle); + }); + }); + + describe('if column.editCellClasses defined as string', () => { + const definedStyleColIndex = editingColIndex; + + beforeEach(() => { + columns[definedStyleColIndex].editCellClasses = 'custom-class'; + wrapper = shallow( + + ); + }); + + it('should also rendering EditingCell with correct class', () => { + expect(wrapper.find(EditingCell).length).toBe(1); + expect(wrapper.find(EditingCell).props().className) + .toEqual(columns[definedStyleColIndex].editCellClasses); + }); + }); + + describe('if column.editCellClasses defined as function', () => { + const definedStyleColIndex = editingColIndex; + const customClass = 'custom-class'; + let editCellClassesCallBack; + + beforeEach(() => { + editCellClassesCallBack = sinon.stub().returns(customClass); + columns[definedStyleColIndex].editCellClasses = editCellClassesCallBack; + wrapper = shallow( + + ); + }); + + it('should calling custom column.editCellStyle callback correctly', () => { + expect(editCellClassesCallBack.callCount).toBe(1); + expect( + editCellClassesCallBack.calledWith( + row[columns[editingColIndex].dataField], row, rowIndex, editingColIndex) + ).toBe(true); + }); + + it('should also rendering EditingCell with correct class', () => { + expect(wrapper.find(EditingCell).length).toBe(1); + expect(wrapper.find(EditingCell).props().className).toEqual(customClass); + }); + }); }); describe('and cellEdit.ridx is not match to current row index', () => {