From 32e455e65f5772ac3a808a086d73f7aada345e90 Mon Sep 17 00:00:00 2001 From: AllenFang Date: Sat, 5 Jan 2019 14:37:07 +0800 Subject: [PATCH 1/6] add remote standalone pagination story --- .../remote-standalone-pagination.js | 190 ++++++++++++++++++ .../stories/index.js | 4 +- 2 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 packages/react-bootstrap-table2-example/examples/pagination/remote-standalone-pagination.js diff --git a/packages/react-bootstrap-table2-example/examples/pagination/remote-standalone-pagination.js b/packages/react-bootstrap-table2-example/examples/pagination/remote-standalone-pagination.js new file mode 100644 index 0000000..cb5609b --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/pagination/remote-standalone-pagination.js @@ -0,0 +1,190 @@ +/* eslint react/no-multi-comp: 0 */ +import React from 'react'; +import PropTypes from 'prop-types'; +import BootstrapTable from 'react-bootstrap-table-next'; +import paginationFactory, { PaginationProvider, PaginationListStandalone } from 'react-bootstrap-table2-paginator'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(87); + +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 paginationFactory, { PaginationProvider, PaginationListStandalone } from 'react-bootstrap-table2-paginator'; +// ... +const RemotePagination = ({ data, page, sizePerPage, onTableChange, totalSize }) => ( +
+ + { + ({ + paginationProps, + paginationTableProps + }) => ( +
+
+

Current Page: { paginationProps.page }

+

Current SizePerPage: { paginationProps.sizePerPage }

+
+
+ +
+ +
+ ) + } +
+
+); + +class Container extends React.Component { + constructor(props) { + super(props); + this.state = { + page: 1, + data: products.slice(0, 10), + sizePerPage: 10 + }; + } + + handleTableChange = (type, { page, sizePerPage }) => { + const currentIndex = (page - 1) * sizePerPage; + setTimeout(() => { + this.setState(() => ({ + page, + data: products.slice(currentIndex, currentIndex + sizePerPage), + sizePerPage + })); + }, 2000); + } + + render() { + const { data, sizePerPage, page } = this.state; + return ( + + ); + } +} +`; + +const RemotePagination = ({ data, page, sizePerPage, onTableChange, totalSize }) => ( +
+ + { + ({ + paginationProps, + paginationTableProps + }) => ( +
+
+

Current Page: { paginationProps.page }

+

Current SizePerPage: { paginationProps.sizePerPage }

+
+
+ +
+ +
+ ) + } +
+ { sourceCode } +
+); + +RemotePagination.propTypes = { + data: PropTypes.array.isRequired, + page: PropTypes.number.isRequired, + totalSize: PropTypes.number.isRequired, + sizePerPage: PropTypes.number.isRequired, + onTableChange: PropTypes.func.isRequired +}; + +class Container extends React.Component { + constructor(props) { + super(props); + this.state = { + page: 1, + data: products.slice(0, 10), + sizePerPage: 10 + }; + } + + handleTableChange = (type, { page, sizePerPage }) => { + const currentIndex = (page - 1) * sizePerPage; + setTimeout(() => { + this.setState(() => ({ + page, + data: products.slice(currentIndex, currentIndex + sizePerPage), + sizePerPage + })); + }, 2000); + } + + render() { + const { data, sizePerPage, page } = this.state; + return ( + + ); + } +} + +export default Container; diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js index ef20d12..169a235 100644 --- a/packages/react-bootstrap-table2-example/stories/index.js +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -154,6 +154,7 @@ import CustomPageListTable from 'examples/pagination/custom-page-list'; import StandalonePaginationList from 'examples/pagination/standalone-pagination-list'; import StandaloneSizePerPage from 'examples/pagination/standalone-size-per-page'; import FullyCustomPaginationTable from 'examples/pagination/fully-custom-pagination'; +import RemoteStandalonePaginationTable from 'examples/pagination/remote-standalone-pagination'; // search import SearchTable from 'examples/search'; @@ -359,7 +360,8 @@ storiesOf('Pagination', module) .add('Custom SizePerPage', () => ) .add('Standalone Pagination List', () => ) .add('Standalone SizePerPage Dropdown', () => ) - .add('Fully Custom Pagination', () => ); + .add('Fully Custom Pagination', () => ) + .add('Remote Fully Custom Pagination', () => ); storiesOf('Table Search', module) .addDecorator(bootstrapStyle()) From 340ddb8722095ba41349a88912687d8a8b5ac5d9 Mon Sep 17 00:00:00 2001 From: Allen Date: Sat, 5 Jan 2019 16:09:12 +0800 Subject: [PATCH 2/6] fix #719 (#741) --- .../src/row/should-updater.js | 13 ++++++ .../src/row/simple-row.js | 3 +- .../test/row/should-updater.test.js | 44 +++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/packages/react-bootstrap-table2/src/row/should-updater.js b/packages/react-bootstrap-table2/src/row/should-updater.js index 7756b73..813dbb5 100644 --- a/packages/react-bootstrap-table2/src/row/should-updater.js +++ b/packages/react-bootstrap-table2/src/row/should-updater.js @@ -20,6 +20,19 @@ export default ExtendBase => ); } + // Only use for simple-row + shouldUpdateByColumnsForSimpleCheck(nextProps) { + if (this.props.columns.length !== nextProps.columns.length) { + return true; + } + for (let i = 0; i < this.props.columns.length; i += 1) { + if (this.props.columns[i].hidden !== nextProps.columns[i].hidden) { + return true; + } + } + return false; + } + shouldUpdatedByNormalProps(nextProps) { const shouldUpdate = this.props.rowIndex !== nextProps.rowIndex || diff --git a/packages/react-bootstrap-table2/src/row/simple-row.js b/packages/react-bootstrap-table2/src/row/simple-row.js index 83aff66..96ca92f 100644 --- a/packages/react-bootstrap-table2/src/row/simple-row.js +++ b/packages/react-bootstrap-table2/src/row/simple-row.js @@ -15,7 +15,8 @@ class SimpleRow extends shouldUpdater(eventDelegater(Component)) { shouldComponentUpdate(nextProps) { this.shouldUpdateRowContent = false; - this.shouldUpdateRowContent = this.shouldUpdateChild(nextProps); + this.shouldUpdateRowContent = + this.shouldUpdateChild(nextProps) || this.shouldUpdateByColumnsForSimpleCheck(nextProps); if (this.shouldUpdateRowContent) return true; return this.shouldUpdatedBySelfProps(nextProps); diff --git a/packages/react-bootstrap-table2/test/row/should-updater.test.js b/packages/react-bootstrap-table2/test/row/should-updater.test.js index ab1ca05..1f5fefd 100644 --- a/packages/react-bootstrap-table2/test/row/should-updater.test.js +++ b/packages/react-bootstrap-table2/test/row/should-updater.test.js @@ -104,6 +104,50 @@ describe('Row shouldUpdater', () => { }); }); + describe('shouldUpdateByColumnsForSimpleCheck', () => { + describe('when nextProps.columns.length is not eq props.columns.length', () => { + beforeEach(() => { + props = { + columns: [{ dataField: 'price', text: 'Price' }] + }; + wrapper = shallow(); + }); + + it('should return true', () => { + nextProps = { ...props, columns: [...props.columns, { dataField: 'name', text: 'Name' }] }; + expect(wrapper.instance().shouldUpdateByColumnsForSimpleCheck(nextProps)).toBeTruthy(); + }); + }); + + describe('when any nextProps.columns.hidden is change', () => { + beforeEach(() => { + props = { + columns: [{ dataField: 'price', text: 'Price' }] + }; + wrapper = shallow(); + }); + + it('should return true', () => { + nextProps = { ...props, columns: [{ dataField: 'price', text: 'Price', hidden: true }] }; + expect(wrapper.instance().shouldUpdateByColumnsForSimpleCheck(nextProps)).toBeTruthy(); + }); + }); + + describe('if any nextProps.columns.hidden is not change and column length is same', () => { + beforeEach(() => { + props = { + columns: [{ dataField: 'price', text: 'Price' }] + }; + wrapper = shallow(); + }); + + it('should return false', () => { + nextProps = { ...props, columns: [...props.columns] }; + expect(wrapper.instance().shouldUpdateByColumnsForSimpleCheck(nextProps)).toBeFalsy(); + }); + }); + }); + describe('shouldUpdatedByNormalProps', () => { describe('when nextProps.rowIndex is not eq props.rowIndex', () => { beforeEach(() => { From a83b3d0d78cb8aa706e7742cbea602411a504d00 Mon Sep 17 00:00:00 2001 From: Allen Date: Sat, 5 Jan 2019 17:00:52 +0800 Subject: [PATCH 3/6] fix #729 (#742) --- .../react-bootstrap-table2-paginator/src/page-resolver.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-bootstrap-table2-paginator/src/page-resolver.js b/packages/react-bootstrap-table2-paginator/src/page-resolver.js index cb77b17..b9134d9 100644 --- a/packages/react-bootstrap-table2-paginator/src/page-resolver.js +++ b/packages/react-bootstrap-table2-paginator/src/page-resolver.js @@ -135,8 +135,8 @@ export default ExtendBase => calculateSizePerPageStatus() { const { sizePerPageList } = this.props; return sizePerPageList.map((_sizePerPage) => { - const pageText = _sizePerPage.text || _sizePerPage; - const pageNumber = _sizePerPage.value || _sizePerPage; + const pageText = typeof _sizePerPage.text !== 'undefined' ? _sizePerPage.text : _sizePerPage; + const pageNumber = typeof _sizePerPage.value !== 'undefined' ? _sizePerPage.value : _sizePerPage; return { text: `${pageText}`, page: pageNumber From 22cc79961f155d20ec34ff2d78b0d02060adcdd1 Mon Sep 17 00:00:00 2001 From: Allen Date: Sat, 5 Jan 2019 20:45:18 +0800 Subject: [PATCH 4/6] fix #715 (#743) --- .../examples/column-filter/filter-hooks.js | 57 +++++++++++++++++++ .../stories/index.js | 4 +- .../src/context.js | 4 ++ .../test/context.test.js | 31 +++++++++- 4 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 packages/react-bootstrap-table2-example/examples/column-filter/filter-hooks.js diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/filter-hooks.js b/packages/react-bootstrap-table2-example/examples/column-filter/filter-hooks.js new file mode 100644 index 0000000..db3b2bf --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/column-filter/filter-hooks.js @@ -0,0 +1,57 @@ +/* eslint no-console: 0 */ +import React from 'react'; +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { textFilter } from 'react-bootstrap-table2-filter'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(8); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + filter: textFilter() +}, { + dataField: 'price', + text: 'Product Price', + filter: textFilter({ + onFilter: filterVal => console.log(`Filter Value: ${filterVal}`) + }) +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { textFilter } from 'react-bootstrap-table2-filter'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + filter: textFilter() +}, { + dataField: 'price', + text: 'Product Price', + filter: textFilter({ + onFilter: filterVal => console.log(\`Filter Value: $\{filterVal}\`) + }) +}]; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js index 169a235..56735cb 100644 --- a/packages/react-bootstrap-table2-example/stories/index.js +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -74,6 +74,7 @@ import ProgrammaticallyMultiSelectFilter from 'examples/column-filter/programmat import CustomFilter from 'examples/column-filter/custom-filter'; import AdvanceCustomFilter from 'examples/column-filter/advance-custom-filter'; import ClearAllFilters from 'examples/column-filter/clear-all-filters'; +import FilterHooks from 'examples/column-filter/filter-hooks'; // work on rows import RowStyleTable from 'examples/rows/row-style'; @@ -274,7 +275,8 @@ storiesOf('Column Filter', module) .add('Custom Filter', () => ) .add('Advance Custom Filter', () => ) .add('Preserved Option Order on Select Filter', () => ) - .add('Clear All Filters', () => ); + .add('Clear All Filters', () => ) + .add('Filter Hooks', () => ); storiesOf('Work on Rows', module) .addDecorator(bootstrapStyle()) diff --git a/packages/react-bootstrap-table2-filter/src/context.js b/packages/react-bootstrap-table2-filter/src/context.js index 790ce00..e82e53e 100644 --- a/packages/react-bootstrap-table2-filter/src/context.js +++ b/packages/react-bootstrap-table2-filter/src/context.js @@ -64,6 +64,10 @@ export default ( return; } + if (filter.props.onFilter) { + filter.props.onFilter(filterVal); + } + this.forceUpdate(); }; } diff --git a/packages/react-bootstrap-table2-filter/test/context.test.js b/packages/react-bootstrap-table2-filter/test/context.test.js index f4b97ca..b80d0c5 100644 --- a/packages/react-bootstrap-table2-filter/test/context.test.js +++ b/packages/react-bootstrap-table2-filter/test/context.test.js @@ -44,7 +44,8 @@ describe('FilterContext', () => { const handleFilterChange = jest.fn(); function shallowContext( - enableRemote = false + enableRemote = false, + tableColumns = columns ) { mockBase.mockReset(); handleFilterChange.mockReset(); @@ -56,7 +57,7 @@ describe('FilterContext', () => { return ( @@ -225,6 +226,32 @@ describe('FilterContext', () => { }); }); + describe('if filter.props.onFilter is defined', () => { + const filterVal = '3'; + const onFilter = jest.fn(); + const customColumns = columns.map((column, i) => { + if (i === 1) { + return { + ...column, + filter: textFilter({ onFilter }) + }; + } + return column; + }); + + beforeEach(() => { + wrapper = shallow(shallowContext(false, customColumns)); + wrapper.render(); + instance = wrapper.instance(); + }); + + it('should call filter.props.onFilter correctly', () => { + instance.onFilter(customColumns[1], FILTER_TYPE.TEXT)(filterVal); + expect(onFilter).toHaveBeenCalledTimes(1); + expect(onFilter).toHaveBeenCalledWith(filterVal); + }); + }); + describe('combination', () => { beforeEach(() => { wrapper = shallow(shallowContext()); From 782c630e58a269c3ea3d7050d63c95f1a034a26b Mon Sep 17 00:00:00 2001 From: Allen Date: Sun, 6 Jan 2019 14:47:22 +0800 Subject: [PATCH 5/6] fix #734, fix #620 (#745) --- .../src/op/csv.js | 10 ++++++++-- .../statelessOp.js | 6 ++---- .../src/bootstrap-table.js | 5 +---- .../src/contexts/index.js | 12 +++++++++-- .../src/contexts/selection-context.js | 8 -------- .../test/bootstrap-table.test.js | 20 ------------------- 6 files changed, 21 insertions(+), 40 deletions(-) diff --git a/packages/react-bootstrap-table2-toolkit/src/op/csv.js b/packages/react-bootstrap-table2-toolkit/src/op/csv.js index 5f05a4e..47a6339 100644 --- a/packages/react-bootstrap-table2-toolkit/src/op/csv.js +++ b/packages/react-bootstrap-table2-toolkit/src/op/csv.js @@ -25,13 +25,19 @@ export default Base => let data; if (typeof source !== 'undefined') { data = source; + } else if (options.exportAll) { + data = this.props.data; } else { - data = options.exportAll ? this.props.data : this.getData(); + const payload = {}; + this.tableExposedAPIEmitter.emit('get.table.data', payload); + data = payload.result; } // filter data if (options.onlyExportSelection) { - const selections = this.getSelected(); + const payload = {}; + this.tableExposedAPIEmitter.emit('get.selected.rows', payload); + const selections = payload.result; data = data.filter(row => !!selections.find(sel => row[keyField] === sel)); } const content = transform(data, meta, this._.get, options); diff --git a/packages/react-bootstrap-table2-toolkit/statelessOp.js b/packages/react-bootstrap-table2-toolkit/statelessOp.js index 6c248c1..85444bf 100644 --- a/packages/react-bootstrap-table2-toolkit/statelessOp.js +++ b/packages/react-bootstrap-table2-toolkit/statelessOp.js @@ -2,9 +2,7 @@ import Operation from './src/op'; export default Base => class StatelessOperation extends Operation.csvOperation(Base) { - registerExposedAPI = (...exposedFuncs) => { - exposedFuncs.forEach((func) => { - this[func.name] = func; - }); + registerExposedAPI = (tableExposedAPIEmitter) => { + this.tableExposedAPIEmitter = tableExposedAPIEmitter; } }; diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js index 8bcd7c1..c77c107 100644 --- a/packages/react-bootstrap-table2/src/bootstrap-table.js +++ b/packages/react-bootstrap-table2/src/bootstrap-table.js @@ -14,13 +14,10 @@ class BootstrapTable extends PropsBaseResolver(Component) { constructor(props) { super(props); this.validateProps(); - if (props.registerExposedAPI) { - props.registerExposedAPI(this.getData); - } } // Exposed APIs - getData() { + getData = () => { return this.visibleRows(); } diff --git a/packages/react-bootstrap-table2/src/contexts/index.js b/packages/react-bootstrap-table2/src/contexts/index.js index 12bfb9a..7caf60c 100644 --- a/packages/react-bootstrap-table2/src/contexts/index.js +++ b/packages/react-bootstrap-table2/src/contexts/index.js @@ -1,6 +1,8 @@ /* eslint no-return-assign: 0 */ +/* eslint no-param-reassign: 0 */ /* eslint class-methods-use-this: 0 */ import React, { Component } from 'react'; +import EventEmitter from 'events'; import _ from '../utils'; import createDataContext from './data-context'; import createSortContext from './sort-context'; @@ -16,6 +18,13 @@ const withContext = Base => super(props); this.DataContext = createDataContext(); + if (props.registerExposedAPI) { + const exposedAPIEmitter = new EventEmitter(); + exposedAPIEmitter.on('get.table.data', payload => payload.result = this.table.getData()); + exposedAPIEmitter.on('get.selected.rows', payload => payload.result = this.selectionContext.getSelected()); + props.registerExposedAPI(exposedAPIEmitter); + } + if (props.columns.filter(col => col.sort).length > 0) { this.SortContext = createSortContext( dataOperator, this.isRemoteSort, this.handleRemoteSortChange); @@ -255,9 +264,8 @@ const withContext = Base => } render() { - const { keyField, columns, bootstrap4, registerExposedAPI } = this.props; + const { keyField, columns, bootstrap4 } = this.props; const baseProps = { keyField, columns }; - if (registerExposedAPI) baseProps.registerExposedAPI = registerExposedAPI; let base = this.renderBase(); diff --git a/packages/react-bootstrap-table2/src/contexts/selection-context.js b/packages/react-bootstrap-table2/src/contexts/selection-context.js index 2bdd321..9c44cff 100644 --- a/packages/react-bootstrap-table2/src/contexts/selection-context.js +++ b/packages/react-bootstrap-table2/src/contexts/selection-context.js @@ -14,14 +14,6 @@ class SelectionProvider extends React.Component { keyField: PropTypes.string.isRequired } - constructor(props) { - super(props); - if (props.registerExposedAPI) { - const getSelected = () => this.getSelected(); - props.registerExposedAPI(getSelected); - } - } - state = { selected: this.props.selectRow.selected || [] }; componentWillReceiveProps(nextProps) { diff --git a/packages/react-bootstrap-table2/test/bootstrap-table.test.js b/packages/react-bootstrap-table2/test/bootstrap-table.test.js index 23986a8..a543111 100644 --- a/packages/react-bootstrap-table2/test/bootstrap-table.test.js +++ b/packages/react-bootstrap-table2/test/bootstrap-table.test.js @@ -60,26 +60,6 @@ describe('BootstrapTable', () => { }); }); - describe('when props.registerExposedAPI is defined', () => { - const registerExposedAPI = jest.fn(); - beforeEach(() => { - registerExposedAPI.mockClear(); - wrapper = shallow( - - ); - }); - - it('should call props.registerExposedAPI correctly', () => { - expect(registerExposedAPI).toHaveBeenCalledTimes(1); - expect(registerExposedAPI.mock.calls[0][0].name).toEqual('getData'); - }); - }); - describe('when props.classes was defined', () => { const classes = 'foo'; From 7d30804da902b800da925cf96c1ce943fe48eaa2 Mon Sep 17 00:00:00 2001 From: Allen Date: Sun, 6 Jan 2019 16:25:02 +0800 Subject: [PATCH 6/6] fix #636 (#746) --- docs/row-expand.md | 12 +++ .../row-expand/expand-column-position.js | 76 +++++++++++++++ .../stories/index.js | 2 + .../src/bootstrap-table.js | 6 +- packages/react-bootstrap-table2/src/const.js | 4 +- packages/react-bootstrap-table2/src/header.js | 68 ++++++++------ .../src/row/aggregate-row.js | 92 +++++++++++-------- .../test/contexts/index.test.js | 27 ++++++ .../test/header.test.js | 28 ++++++ .../test/row/aggregate-row.test.js | 22 +++++ 10 files changed, 272 insertions(+), 65 deletions(-) create mode 100644 packages/react-bootstrap-table2-example/examples/row-expand/expand-column-position.js diff --git a/docs/row-expand.md b/docs/row-expand.md index ce710de..025d03d 100644 --- a/docs/row-expand.md +++ b/docs/row-expand.md @@ -15,6 +15,7 @@ * [showExpandColumn](#showExpandColumn) * [onlyOneExpanding](#onlyOneExpanding) * [expandByColumnOnly](#expandByColumnOnly) +* [expandColumnPosition](#expandColumnPosition) * [expandColumnRenderer](#expandColumnRenderer) * [expandHeaderColumnRenderer](#expandHeaderColumnRenderer) @@ -153,3 +154,14 @@ const expandRow = { expandByColumnOnly: true }; ``` + +### expandRow.expandColumnPosition - [String] +Default is `left`. You can give this as `right` for rendering expand column in the right side. + +```js +const expandRow = { + renderer: (row) => ..., + showExpandColumn: true, + expandColumnPosition: 'right' +}; +``` diff --git a/packages/react-bootstrap-table2-example/examples/row-expand/expand-column-position.js b/packages/react-bootstrap-table2-example/examples/row-expand/expand-column-position.js new file mode 100644 index 0000000..8b07b34 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/row-expand/expand-column-position.js @@ -0,0 +1,76 @@ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import Code from 'components/common/code-block'; +import { productsExpandRowsGenerator } from 'utils/common'; + +const products = productsExpandRowsGenerator(); + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const expandRow = { + renderer: row => ( +
+

{ `This Expand row is belong to rowKey ${row.id}` }

+

You can render anything here, also you can add additional data on every row object

+

expandRow.renderer callback will pass the origin row object to you

+
+ ), + showExpandColumn: true, + expandColumnPosition: 'right' +}; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}, { + dataField: 'price', + text: 'Product Price' +}]; + +const expandRow = { + renderer: row => ( +
+

{ \`This Expand row is belong to rowKey $\{row.id}\` }

+

You can render anything here, also you can add additional data on every row object

+

expandRow.renderer callback will pass the origin row object to you

+
+ ), + showExpandColumn: true, + expandColumnPosition: 'right' +}; + + +`; + +export default () => ( +
+ + { sourceCode } +
+); diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js index 56735cb..0461549 100644 --- a/packages/react-bootstrap-table2-example/stories/index.js +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -142,6 +142,7 @@ import ExpandColumn from 'examples/row-expand/expand-column'; import OnlyExpandByColumn from 'examples/row-expand/expand-by-column-only.js'; import ExpandOnlyOne from 'examples/row-expand/expand-only-one'; import CustomExpandColumn from 'examples/row-expand/custom-expand-column'; +import ExpandColumnPosition from 'examples/row-expand/expand-column-position'; import ExpandHooks from 'examples/row-expand/expand-hooks'; // pagination @@ -349,6 +350,7 @@ storiesOf('Row Expand', module) .add('Only Expand by Indicator', () => ) .add('Expand Only One Row at The Same Time', () => ) .add('Custom Expand Indicator', () => ) + .add('Expand Column Position', () => ) .add('Expand Hooks', () => ); storiesOf('Pagination', module) diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js index c77c107..7c3a727 100644 --- a/packages/react-bootstrap-table2/src/bootstrap-table.js +++ b/packages/react-bootstrap-table2/src/bootstrap-table.js @@ -158,7 +158,11 @@ BootstrapTable.propTypes = { onlyOneExpanding: PropTypes.bool, expandByColumnOnly: PropTypes.bool, expandColumnRenderer: PropTypes.func, - expandHeaderColumnRenderer: PropTypes.func + expandHeaderColumnRenderer: PropTypes.func, + expandColumnPosition: PropTypes.oneOf([ + Const.INDICATOR_POSITION_LEFT, + Const.INDICATOR_POSITION_RIGHT + ]) }), rowStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), rowEvents: PropTypes.object, diff --git a/packages/react-bootstrap-table2/src/const.js b/packages/react-bootstrap-table2/src/const.js index 6c47b1c..ce1b7e4 100644 --- a/packages/react-bootstrap-table2/src/const.js +++ b/packages/react-bootstrap-table2/src/const.js @@ -6,5 +6,7 @@ export default { ROW_SELECT_DISABLED: 'ROW_SELECT_DISABLED', CHECKBOX_STATUS_CHECKED: 'checked', CHECKBOX_STATUS_INDETERMINATE: 'indeterminate', - CHECKBOX_STATUS_UNCHECKED: 'unchecked' + CHECKBOX_STATUS_UNCHECKED: 'unchecked', + INDICATOR_POSITION_LEFT: 'left', + INDICATOR_POSITION_RIGHT: 'right' }; diff --git a/packages/react-bootstrap-table2/src/header.js b/packages/react-bootstrap-table2/src/header.js index e8a6fb3..2606852 100644 --- a/packages/react-bootstrap-table2/src/header.js +++ b/packages/react-bootstrap-table2/src/header.js @@ -7,6 +7,7 @@ import SelectionHeaderCell from './row-selection/selection-header-cell'; import ExpandHeaderCell from './row-expand/expand-header-cell'; import withHeaderSelection from './row-selection/selection-header-cell-consumer'; import withHeaderExpansion from './row-expand/expand-header-cell-consumer'; +import Const from './const'; const Header = (props) => { const { @@ -32,36 +33,49 @@ const Header = (props) => { SelectionHeaderCellComp = withHeaderSelection(SelectionHeaderCell); } + const isRenderExpandColumnInLeft = ( + expandColumnPosition = Const.INDICATOR_POSITION_LEFT + ) => expandColumnPosition === Const.INDICATOR_POSITION_LEFT; + + const childrens = [ + columns.map((column, i) => { + if (!column.hidden) { + const currSort = column.dataField === sortField; + const isLastSorting = column.dataField === sortField; + + return ( + ); + } + return false; + }) + ]; + + if (!selectRow.hideSelectColumn) { + childrens.unshift(); + } + + if (expandRow.showExpandColumn) { + if (isRenderExpandColumnInLeft(expandRow.expandColumnPosition)) { + childrens.unshift(); + } else { + childrens.push(); + } + } + return ( - - { - !selectRow.hideSelectColumn ? - : null - } - { - columns.map((column, i) => { - if (!column.hidden) { - const currSort = column.dataField === sortField; - const isLastSorting = column.dataField === sortField; - - return ( - ); - } - return false; - }) - } + { childrens } ); diff --git a/packages/react-bootstrap-table2/src/row/aggregate-row.js b/packages/react-bootstrap-table2/src/row/aggregate-row.js index dfba8b5..96b7dbe 100644 --- a/packages/react-bootstrap-table2/src/row/aggregate-row.js +++ b/packages/react-bootstrap-table2/src/row/aggregate-row.js @@ -1,3 +1,4 @@ +/* eslint class-methods-use-this: 0 */ /* eslint react/prop-types: 0 */ /* eslint no-plusplus: 0 */ import React from 'react'; @@ -8,6 +9,7 @@ import SelectionCell from '../row-selection/selection-cell'; import shouldUpdater from './should-updater'; import eventDelegater from './event-delegater'; import RowPureContent from './row-pure-content'; +import Const from '../const'; export default class RowAggregator extends shouldUpdater(eventDelegater(React.Component)) { static propTypes = { @@ -43,6 +45,12 @@ export default class RowAggregator extends shouldUpdater(eventDelegater(React.Co return this.shouldUpdateRowContent; } + isRenderExpandColumnInLeft( + expandColumnPosition = Const.INDICATOR_POSITION_LEFT + ) { + return expandColumnPosition === Const.INDICATOR_POSITION_LEFT; + } + render() { const { row, @@ -64,7 +72,7 @@ export default class RowAggregator extends shouldUpdater(eventDelegater(React.Co } = this.props; const key = _.get(row, keyField); const { hideSelectColumn, clickToSelect } = selectRow; - const { showExpandColumn } = expandRow; + const { showExpandColumn, expandColumnPosition } = expandRow; const newAttrs = this.delegate({ ...attrs }); if (clickToSelect || !!expandRow.renderer) { @@ -73,47 +81,59 @@ export default class RowAggregator extends shouldUpdater(eventDelegater(React.Co let tabIndexStart = (rowIndex * visibleColumnSize) + 1; + const childrens = [( + + )]; + + if (!hideSelectColumn) { + childrens.unshift(( + + )); + } + + if (showExpandColumn) { + const expandCell = ( + + ); + if (this.isRenderExpandColumnInLeft(expandColumnPosition)) { + childrens.unshift(expandCell); + } else { + childrens.push(expandCell); + } + } + return ( - { - showExpandColumn ? ( - - ) : null - } - { - !hideSelectColumn - ? ( - - ) - : null - } - + { childrens } ); } diff --git a/packages/react-bootstrap-table2/test/contexts/index.test.js b/packages/react-bootstrap-table2/test/contexts/index.test.js index 76caecb..737fcb9 100644 --- a/packages/react-bootstrap-table2/test/contexts/index.test.js +++ b/packages/react-bootstrap-table2/test/contexts/index.test.js @@ -225,4 +225,31 @@ describe('Context', () => { expect(wrapper.instance().PaginationContext).toBeDefined(); }); }); + + describe('if registerExposedAPI props is defined', () => { + const registerExposedAPI = jest.fn(); + beforeEach(() => { + const PaginationContext = React.createContext(); + const paginator = { + createContext: jest.fn().mockReturnValue({ + Provider: PaginationContext.Provider, + Consumer: PaginationContext.Consumer + }) + }; + wrapper = shallow( + + ); + wrapper.render(); + }); + + it('should call props.registerExposedAPI correctly', () => { + expect(registerExposedAPI).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/packages/react-bootstrap-table2/test/header.test.js b/packages/react-bootstrap-table2/test/header.test.js index ecb2318..263ac54 100644 --- a/packages/react-bootstrap-table2/test/header.test.js +++ b/packages/react-bootstrap-table2/test/header.test.js @@ -254,5 +254,33 @@ describe('Header', () => { expect(wrapper.find(ExpandHeaderCell).length).toBe(1); }); }); + + describe('if props.expandRow.showExpandColumn is true but props.expandRow.expandColumnPosition is "right"', () => { + beforeEach(() => { + const expandRow = { + renderer: jest.fn(), + showExpandColumn: true, + expandColumnPosition: Const.INDICATOR_POSITION_RIGHT + }; + wrapper = mount( + +
+ + ); + }); + + it('should render expansion column correctly', () => { + const header = wrapper.find(Header).children(); + expect(header.children().children().last().find(ExpandHeaderCell)).toHaveLength(1); + }); + }); }); }); diff --git a/packages/react-bootstrap-table2/test/row/aggregate-row.test.js b/packages/react-bootstrap-table2/test/row/aggregate-row.test.js index 76aa625..b314470 100644 --- a/packages/react-bootstrap-table2/test/row/aggregate-row.test.js +++ b/packages/react-bootstrap-table2/test/row/aggregate-row.test.js @@ -9,6 +9,7 @@ import bindExpansion from '../../src/row-expand/row-consumer'; import ExpandCell from '../../src/row-expand/expand-cell'; import SelectionCell from '../../src/row-selection/selection-cell'; import RowAggregator from '../../src/row/aggregate-row'; +import Const from '../../src/const'; describe('Row Aggregator', () => { let wrapper; @@ -157,6 +158,27 @@ describe('Row Aggregator', () => { expect(expandCell.props().expanded).toEqual(rowAggregator.props().expanded); }); }); + + describe('if props.expandRow.showExpandColumn is true but props.expandRow.expandColumnPosition is "right"', () => { + beforeEach(() => { + const expandRow = { + renderer: jest.fn(), + showExpandColumn: true, + expandColumnPosition: Const.INDICATOR_POSITION_RIGHT + }; + wrapper = mount( + + + + ); + }); + + it('should render expansion column correctly', () => { + rowAggregator = wrapper.find(RowAggregator); + expect(rowAggregator).toHaveLength(1); + expect(rowAggregator.children().children().last().type()).toEqual(ExpandCell); + }); + }); }); describe('createClickEventHandler', () => {