diff --git a/packages/react-bootstrap-table2-example/examples/column-filter/custom-filter-logic.js b/packages/react-bootstrap-table2-example/examples/column-filter/custom-filter-logic.js new file mode 100644 index 0000000..906d9d0 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/column-filter/custom-filter-logic.js @@ -0,0 +1,81 @@ +/* eslint eqeqeq: 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 sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { textFilter } from 'react-bootstrap-table2-filter'; + +class Table extends React.Component { + filterByPrice = filterVal => + products.filter(product => product.price == filterVal); + + render() { + const columns = [{ + dataField: 'id', + text: 'Product ID' + }, { + dataField: 'name', + text: 'Product Name', + filter: textFilter() + }, { + dataField: 'price', + text: 'Product Price', + filter: textFilter({ + onFilter: this.filterByPrice + }) + }]; + + return ( +
+ +
+ ); + } +} +`; + +export default class Table extends React.Component { + filterByPrice = filterVal => + products.filter(product => product.price == filterVal); + + render() { + const columns = [{ + dataField: 'id', + text: 'Product ID' + }, { + dataField: 'name', + text: 'Product Name', + filter: textFilter() + }, { + dataField: 'price', + text: 'Product Price', + filter: textFilter({ + onFilter: this.filterByPrice + }) + }]; + + return ( +
+

Implement a eq filter on product price column

+ + { sourceCode } +
+ ); + } +} diff --git a/packages/react-bootstrap-table2-example/examples/pagination/custom-page-list-with-search.js b/packages/react-bootstrap-table2-example/examples/pagination/custom-page-list-with-search.js new file mode 100644 index 0000000..87d5d74 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/pagination/custom-page-list-with-search.js @@ -0,0 +1,139 @@ +/* eslint react/prefer-stateless-function: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import paginationFactory, { PaginationProvider, PaginationListStandalone } from 'react-bootstrap-table2-paginator'; +import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(21); +const { SearchBar } = Search; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name' +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import paginationFactory, { PaginationProvider, PaginationListStandalone } from 'react-bootstrap-table2-paginator'; +import filterFactory, { textFilter } from 'react-bootstrap-table2-filter'; + +class Table extends React.Component { + render() { + const options = { + custom: true, + paginationSize: 4, + pageStartIndex: 1, + firstPageText: 'First', + prePageText: 'Back', + nextPageText: 'Next', + lastPageText: 'Last', + nextPageTitle: 'First page', + prePageTitle: 'Pre page', + firstPageTitle: 'Next page', + lastPageTitle: 'Last page', + showTotal: true, + totalSize: products.length + }; + const contentTable = ({ paginationProps, paginationTableProps }) => ( +
+ +
+
+ +
+
+ +
+ ); + + return ( +
+

PaginationProvider will care the data size change. You dont do anything

+ + { contentTable } + + { sourceCode } +
+ ); + } +} +`; + +export default class Table extends React.Component { + render() { + const options = { + custom: true, + paginationSize: 4, + pageStartIndex: 1, + firstPageText: 'First', + prePageText: 'Back', + nextPageText: 'Next', + lastPageText: 'Last', + nextPageTitle: 'First page', + prePageTitle: 'Pre page', + firstPageTitle: 'Next page', + lastPageTitle: 'Last page', + showTotal: true, + totalSize: products.length + }; + const contentTable = ({ paginationProps, paginationTableProps }) => ( +
+ + + { + toolkitprops => ( +
+ + +
+ ) + } +
+ +
+ ); + + return ( +
+

PaginationProvider will care the data size change. You dont do anything

+ + { contentTable } + + { sourceCode } +
+ ); + } +} diff --git a/packages/react-bootstrap-table2-example/examples/pagination/custome-page-list-with-filter.js b/packages/react-bootstrap-table2-example/examples/pagination/custome-page-list-with-filter.js new file mode 100644 index 0000000..eefd780 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/pagination/custome-page-list-with-filter.js @@ -0,0 +1,133 @@ +/* eslint react/prefer-stateless-function: 0 */ +import React from 'react'; + +import BootstrapTable from 'react-bootstrap-table-next'; +import paginationFactory, { PaginationProvider, PaginationListStandalone } from 'react-bootstrap-table2-paginator'; +import filterFactory, { textFilter } from 'react-bootstrap-table2-filter'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const products = productsGenerator(21); + +const columns = [{ + dataField: 'id', + text: 'Product ID', + filter: textFilter({}) +}, { + dataField: 'name', + text: 'Product Name', + filter: textFilter() +}]; + +const sourceCode = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import paginationFactory, { PaginationProvider, PaginationListStandalone } from 'react-bootstrap-table2-paginator'; +import filterFactory, { textFilter } from 'react-bootstrap-table2-filter'; + +class Table extends React.Component { + render() { + const options = { + custom: true, + paginationSize: 4, + pageStartIndex: 1, + firstPageText: 'First', + prePageText: 'Back', + nextPageText: 'Next', + lastPageText: 'Last', + nextPageTitle: 'First page', + prePageTitle: 'Pre page', + firstPageTitle: 'Next page', + lastPageTitle: 'Last page', + showTotal: true, + totalSize: products.length + }; + const contentTable = ({ paginationProps, paginationTableProps }) => ( +
+ +
+
+ +
+
+ +
+ ); + + return ( +
+

PaginationProvider will care the data size change. You dont do anything

+ + { contentTable } + + { sourceCode } +
+ ); + } +} +`; + +export default class Table extends React.Component { + render() { + const options = { + custom: true, + paginationSize: 4, + pageStartIndex: 1, + firstPageText: 'First', + prePageText: 'Back', + nextPageText: 'Next', + lastPageText: 'Last', + nextPageTitle: 'First page', + prePageTitle: 'Pre page', + firstPageTitle: 'Next page', + lastPageTitle: 'Last page', + showTotal: true, + totalSize: products.length + }; + const contentTable = ({ paginationProps, paginationTableProps }) => ( +
+ +
+
+ +
+
+ +
+ ); + + return ( +
+

PaginationProvider will care the data size change. You dont do anything

+ + { contentTable } + + { sourceCode } +
+ ); + } +} diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js index 09ac547..a7ed50f 100644 --- a/packages/react-bootstrap-table2-example/stories/index.js +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -87,6 +87,7 @@ 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'; +import CustomFilterLogic from 'examples/column-filter/custom-filter-logic'; // work on rows import RowStyleTable from 'examples/rows/row-style'; @@ -169,6 +170,8 @@ import StandalonePaginationList from 'examples/pagination/standalone-pagination- 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'; +import CustomePaginationWithFilter from 'examples/pagination/custome-page-list-with-filter'; +import CustomePaginationWithSearch from 'examples/pagination/custom-page-list-with-search'; // search import SearchTable from 'examples/search'; @@ -289,7 +292,8 @@ storiesOf('Column Filter', module) .add('Advance Custom Filter', () => ) .add('Preserved Option Order on Select Filter', () => ) .add('Clear All Filters', () => ) - .add('Filter Hooks', () => ); + .add('Filter Hooks', () => ) + .add('Implement custom filter logic', () => ); storiesOf('Work on Rows', module) .addDecorator(bootstrapStyle()) @@ -390,7 +394,9 @@ storiesOf('Pagination', module) .add('Standalone Pagination List', () => ) .add('Standalone SizePerPage Dropdown', () => ) .add('Fully Custom Pagination', () => ) - .add('Remote Fully Custom Pagination', () => ); + .add('Remote Fully Custom Pagination', () => ) + .add('Custom Pagination with Filter', () => ) + .add('Custom Pagination with Search', () => ); storiesOf('Table Search', module) .addDecorator(bootstrapStyle()) diff --git a/packages/react-bootstrap-table2-filter/src/context.js b/packages/react-bootstrap-table2-filter/src/context.js index e82e53e..f3edd1f 100644 --- a/packages/react-bootstrap-table2-filter/src/context.js +++ b/packages/react-bootstrap-table2-filter/src/context.js @@ -17,7 +17,8 @@ export default ( class FilterProvider extends React.Component { static propTypes = { data: PropTypes.array.isRequired, - columns: PropTypes.array.isRequired + columns: PropTypes.array.isRequired, + dataChangeListener: PropTypes.object } constructor(props) { @@ -25,6 +26,9 @@ export default ( this.currFilters = {}; this.onFilter = this.onFilter.bind(this); this.onExternalFilter = this.onExternalFilter.bind(this); + this.state = { + data: props.data + }; } componentDidMount() { @@ -33,6 +37,14 @@ export default ( } } + componentWillReceiveProps(nextProps) { + if (isRemoteFiltering()) { + this.setState({ + data: nextProps.data + }); + } + } + onFilter(column, filterType, initialize = false) { return (filterVal) => { // watch out here if migration to context API, #334 @@ -64,11 +76,17 @@ export default ( return; } + let result; if (filter.props.onFilter) { - filter.props.onFilter(filterVal); + result = filter.props.onFilter(filterVal); } - this.forceUpdate(); + const { dataChangeListener, data } = this.props; + result = result || filters(data, this.props.columns, _)(this.currFilters); + if (dataChangeListener) { + dataChangeListener.emit('filterChanged', result.length); + } + this.setState({ data: result }); }; } @@ -79,13 +97,9 @@ export default ( } render() { - let { data } = this.props; - if (!isRemoteFiltering()) { - data = filters(data, this.props.columns, _)(this.currFilters); - } return ( { function shallowContext( enableRemote = false, - tableColumns = columns + tableColumns = columns, + dataChangeListener, ) { mockBase.mockReset(); handleFilterChange.mockReset(); @@ -59,6 +60,7 @@ describe('FilterContext', () => { { @@ -252,6 +254,58 @@ describe('FilterContext', () => { }); }); + describe('if filter.props.onFilter is defined and return an undefined data', () => { + const mockReturn = [{ + id: 1, + name: 'A' + }]; + const filterVal = 'A'; + const onFilter = jest.fn().mockReturnValue(mockReturn); + 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); + }); + + it('should set state.data correctly', () => { + instance.onFilter(customColumns[1], FILTER_TYPE.TEXT)(filterVal); + expect(instance.state.data).toEqual(mockReturn); + }); + }); + + describe('when props.dataChangeListener is defined', () => { + const filterVal = '3'; + const newDataLength = 0; + const dataChangeListener = { emit: jest.fn() }; + + beforeEach(() => { + wrapper = shallow(shallowContext(false, columns, dataChangeListener)); + wrapper.render(); + instance = wrapper.instance(); + }); + + it('should call dataChangeListener.emit correctly', () => { + instance.onFilter(columns[1], FILTER_TYPE.TEXT)(filterVal); + expect(dataChangeListener.emit).toHaveBeenCalledWith('filterChanged', newDataLength); + }); + }); + describe('combination', () => { beforeEach(() => { wrapper = shallow(shallowContext()); diff --git a/packages/react-bootstrap-table2-paginator/src/state-context.js b/packages/react-bootstrap-table2-paginator/src/state-context.js index 758dd95..91159f6 100644 --- a/packages/react-bootstrap-table2-paginator/src/state-context.js +++ b/packages/react-bootstrap-table2-paginator/src/state-context.js @@ -2,7 +2,9 @@ /* eslint react/require-default-props: 0 */ /* eslint no-lonely-if: 0 */ import React from 'react'; +import EventEmitter from 'events'; import Const from './const'; +import { alignPage } from './page'; const StateContext = React.createContext(); @@ -10,6 +12,7 @@ class StateProvider extends React.Component { constructor(props) { super(props); this.handleChangePage = this.handleChangePage.bind(this); + this.handleDataSizeChange = this.handleDataSizeChange.bind(this); this.handleChangeSizePerPage = this.handleChangeSizePerPage.bind(this); let currPage; @@ -36,7 +39,10 @@ class StateProvider extends React.Component { } this.currPage = currPage; + this.dataSize = options.totalSize; this.currSizePerPage = currSizePerPage; + this.dataChangeListener = new EventEmitter(); + this.dataChangeListener.on('filterChanged', this.handleDataSizeChange); } componentWillReceiveProps(nextProps) { @@ -46,12 +52,13 @@ class StateProvider extends React.Component { if (this.isRemotePagination() || custom) { this.currPage = nextProps.pagination.options.page; this.currSizePerPage = nextProps.pagination.options.sizePerPage; + this.dataSize = nextProps.pagination.options.totalSize; } } getPaginationProps = () => { const { pagination: { options }, bootstrap4 } = this.props; - const { currPage, currSizePerPage } = this; + const { currPage, currSizePerPage, dataSize } = this; const withFirstAndLast = typeof options.withFirstAndLast === 'undefined' ? Const.With_FIRST_AND_LAST : options.withFirstAndLast; const alwaysShowAllBtns = typeof options.alwaysShowAllBtns === 'undefined' ? @@ -72,7 +79,7 @@ class StateProvider extends React.Component { hideSizePerPage, alwaysShowAllBtns, withFirstAndLast, - dataSize: options.totalSize, + dataSize, sizePerPageList: options.sizePerPageList || Const.SIZE_PER_PAGE_LIST, paginationSize: options.paginationSize || Const.PAGINATION_SIZE, showTotal: options.showTotal, @@ -106,6 +113,20 @@ class StateProvider extends React.Component { return e.result; }; + handleDataSizeChange(newDataSize) { + const { pagination: { options } } = this.props; + const pageStartIndex = typeof options.pageStartIndex === 'undefined' ? + Const.PAGE_START_INDEX : options.pageStartIndex; + this.dataSize = newDataSize; + this.currPage = alignPage( + newDataSize, + this.currPage, + this.currSizePerPage, + pageStartIndex + ); + this.forceUpdate(); + } + handleChangePage(currPage) { const { currSizePerPage } = this; const { pagination: { options } } = this.props; @@ -153,7 +174,8 @@ class StateProvider extends React.Component { paginationProps, paginationTableProps: { pagination, - setPaginationRemoteEmitter: this.setPaginationRemoteEmitter + setPaginationRemoteEmitter: this.setPaginationRemoteEmitter, + dataChangeListener: this.dataChangeListener } } } > diff --git a/packages/react-bootstrap-table2-paginator/test/state-context.test.js b/packages/react-bootstrap-table2-paginator/test/state-context.test.js index 828c667..bc4a91c 100644 --- a/packages/react-bootstrap-table2-paginator/test/state-context.test.js +++ b/packages/react-bootstrap-table2-paginator/test/state-context.test.js @@ -91,6 +91,10 @@ describe('PaginationStateContext', () => { expect(wrapper.instance().currSizePerPage).toEqual(Const.SIZE_PER_PAGE_LIST[0]); }); + it('should have correct dataSize', () => { + expect(wrapper.instance().dataSize).toEqual(options.totalSize); + }); + it('should get correct pagination props', () => { const instance = wrapper.instance(); expect(wrapper.length).toBe(1); @@ -102,7 +106,8 @@ describe('PaginationStateContext', () => { createContext: expect.any(Function), options: instance.getPaginationProps() }, - setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter + setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter, + dataChangeListener: expect.any(Object) } }); }); @@ -149,7 +154,7 @@ describe('PaginationStateContext', () => { setRemotePaginationEmitter(instance, true); nextProps = { data, - pagination: { ...defaultPagination, options: { page: 3, sizePerPage: 5 } } + pagination: { ...defaultPagination, options: { page: 3, sizePerPage: 5, totalSize: 50 } } }; instance.componentWillReceiveProps(nextProps); }); @@ -157,6 +162,7 @@ describe('PaginationStateContext', () => { it('should always reset currPage and currSizePerPage', () => { expect(instance.currPage).toEqual(nextProps.pagination.options.page); expect(instance.currSizePerPage).toEqual(nextProps.pagination.options.sizePerPage); + expect(instance.dataSize).toEqual(nextProps.pagination.options.totalSize); }); }); @@ -170,7 +176,10 @@ describe('PaginationStateContext', () => { setRemotePaginationEmitter(instance, true); nextProps = { data, - pagination: { ...defaultPagination, options: { page: 3, sizePerPage: 5, custom: true } } + pagination: { + ...defaultPagination, + options: { page: 3, sizePerPage: 5, custom: true, totalSize: 50 } + } }; instance.componentWillReceiveProps(nextProps); }); @@ -178,10 +187,36 @@ describe('PaginationStateContext', () => { it('should always reset currPage and currSizePerPage', () => { expect(instance.currPage).toEqual(nextProps.pagination.options.page); expect(instance.currSizePerPage).toEqual(nextProps.pagination.options.sizePerPage); + expect(instance.dataSize).toEqual(nextProps.pagination.options.totalSize); }); }); }); + describe('handleDataSizeChange', () => { + let instance; + const newTotalSize = 8; + beforeEach(() => { + wrapper = shallow(shallowContext({ + ...defaultPagination, + page: 3 + })); + instance = wrapper.instance(); + setRemotePaginationEmitter(instance); + jest.spyOn(instance, 'forceUpdate'); + instance.handleDataSizeChange(newTotalSize); + }); + + it('should update dataSize correctly', () => { + expect(instance.dataSize).toEqual(newTotalSize); + expect(instance.forceUpdate).toHaveBeenCalledTimes(1); + }); + + it('should update currPage correctly if page list shrink', () => { + expect(instance.currPage).toEqual(Const.PAGE_START_INDEX); + expect(instance.forceUpdate).toHaveBeenCalledTimes(1); + }); + }); + describe('handleChangePage', () => { let instance; const newPage = 3; @@ -343,7 +378,8 @@ describe('PaginationStateContext', () => { createContext: expect.any(Function), options: instance.getPaginationProps() }, - setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter + setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter, + dataChangeListener: expect.any(Object) } }); }); @@ -374,7 +410,8 @@ describe('PaginationStateContext', () => { createContext: expect.any(Function), options: instance.getPaginationProps() }, - setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter + setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter, + dataChangeListener: expect.any(Object) } }); }); @@ -401,7 +438,8 @@ describe('PaginationStateContext', () => { createContext: expect.any(Function), options: instance.getPaginationProps() }, - setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter + setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter, + dataChangeListener: expect.any(Object) } }); }); @@ -428,7 +466,8 @@ describe('PaginationStateContext', () => { createContext: expect.any(Function), options: instance.getPaginationProps() }, - setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter + setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter, + dataChangeListener: expect.any(Object) } }); }); @@ -455,7 +494,8 @@ describe('PaginationStateContext', () => { createContext: expect.any(Function), options: instance.getPaginationProps() }, - setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter + setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter, + dataChangeListener: expect.any(Object) } }); }); @@ -482,7 +522,8 @@ describe('PaginationStateContext', () => { createContext: expect.any(Function), options: instance.getPaginationProps() }, - setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter + setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter, + dataChangeListener: expect.any(Object) } }); }); @@ -509,7 +550,8 @@ describe('PaginationStateContext', () => { createContext: expect.any(Function), options: instance.getPaginationProps() }, - setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter + setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter, + dataChangeListener: expect.any(Object) } }); }); @@ -536,7 +578,8 @@ describe('PaginationStateContext', () => { createContext: expect.any(Function), options: instance.getPaginationProps() }, - setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter + setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter, + dataChangeListener: expect.any(Object) } }); }); @@ -563,7 +606,8 @@ describe('PaginationStateContext', () => { createContext: expect.any(Function), options: instance.getPaginationProps() }, - setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter + setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter, + dataChangeListener: expect.any(Object) } }); }); @@ -590,7 +634,8 @@ describe('PaginationStateContext', () => { createContext: expect.any(Function), options: instance.getPaginationProps() }, - setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter + setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter, + dataChangeListener: expect.any(Object) } }); }); @@ -617,7 +662,8 @@ describe('PaginationStateContext', () => { createContext: expect.any(Function), options: instance.getPaginationProps() }, - setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter + setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter, + dataChangeListener: expect.any(Object) } }); }); @@ -644,7 +690,8 @@ describe('PaginationStateContext', () => { createContext: expect.any(Function), options: instance.getPaginationProps() }, - setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter + setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter, + dataChangeListener: expect.any(Object) } }); }); @@ -671,7 +718,8 @@ describe('PaginationStateContext', () => { createContext: expect.any(Function), options: instance.getPaginationProps() }, - setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter + setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter, + dataChangeListener: expect.any(Object) } }); }); @@ -698,7 +746,8 @@ describe('PaginationStateContext', () => { createContext: expect.any(Function), options: instance.getPaginationProps() }, - setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter + setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter, + dataChangeListener: expect.any(Object) } }); }); @@ -725,7 +774,8 @@ describe('PaginationStateContext', () => { createContext: expect.any(Function), options: instance.getPaginationProps() }, - setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter + setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter, + dataChangeListener: expect.any(Object) } }); }); @@ -752,7 +802,8 @@ describe('PaginationStateContext', () => { createContext: expect.any(Function), options: instance.getPaginationProps() }, - setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter + setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter, + dataChangeListener: expect.any(Object) } }); }); @@ -779,7 +830,8 @@ describe('PaginationStateContext', () => { createContext: expect.any(Function), options: instance.getPaginationProps() }, - setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter + setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter, + dataChangeListener: expect.any(Object) } }); }); @@ -806,7 +858,8 @@ describe('PaginationStateContext', () => { createContext: expect.any(Function), options: instance.getPaginationProps() }, - setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter + setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter, + dataChangeListener: expect.any(Object) } }); }); @@ -833,7 +886,8 @@ describe('PaginationStateContext', () => { createContext: expect.any(Function), options: instance.getPaginationProps() }, - setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter + setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter, + dataChangeListener: expect.any(Object) } }); }); diff --git a/packages/react-bootstrap-table2-toolkit/src/search/context.js b/packages/react-bootstrap-table2-toolkit/src/search/context.js index 90484bf..270a15d 100644 --- a/packages/react-bootstrap-table2-toolkit/src/search/context.js +++ b/packages/react-bootstrap-table2-toolkit/src/search/context.js @@ -1,6 +1,7 @@ /* eslint react/prop-types: 0 */ /* eslint react/require-default-props: 0 */ /* eslint no-continue: 0 */ +/* eslint no-lonely-if: 0 */ import React from 'react'; import PropTypes from 'prop-types'; @@ -17,36 +18,48 @@ export default (options = { static propTypes = { data: PropTypes.array.isRequired, columns: PropTypes.array.isRequired, - searchText: PropTypes.string + searchText: PropTypes.string, + dataChangeListener: PropTypes.object } constructor(props) { super(props); - this.performRemoteSearch = props.searchText !== ''; + let initialData = props.data; + if (isRemoteSearch() && this.props.searchText !== '') { + handleRemoteSearchChange(this.props.searchText); + } else { + initialData = this.search(props.searchText.toLowerCase()); + this.triggerListener(initialData); + } + this.state = { data: initialData }; } componentWillReceiveProps(nextProps) { - if (isRemoteSearch()) { - if (nextProps.searchText !== this.props.searchText) { - this.performRemoteSearch = true; + if (nextProps.searchText !== this.props.searchText) { + if (isRemoteSearch()) { + handleRemoteSearchChange(nextProps.searchText); } else { - this.performRemoteSearch = false; + const result = this.search(nextProps.searchText.toLowerCase()); + this.triggerListener(result); + this.setState({ + data: result + }); + } + } else { + if (isRemoteSearch()) { + this.setState({ data: nextProps.data }); } } } - search() { - const { data, columns } = this.props; - let { searchText } = this.props; - - if (isRemoteSearch()) { - if (this.performRemoteSearch) { - handleRemoteSearchChange(searchText); - } - return data; + triggerListener(result) { + if (this.props.dataChangeListener) { + this.props.dataChangeListener.emit('filterChanged', result.length); } + } - searchText = searchText.toLowerCase(); + search(searchText) { + const { data, columns } = this.props; return data.filter((row, ridx) => { for (let cidx = 0; cidx < columns.length; cidx += 1) { const column = columns[cidx]; @@ -69,9 +82,8 @@ export default (options = { } render() { - const data = this.search(); return ( - + { this.props.children } ); diff --git a/packages/react-bootstrap-table2/src/body.js b/packages/react-bootstrap-table2/src/body.js index a32a82e..2d445fc 100644 --- a/packages/react-bootstrap-table2/src/body.js +++ b/packages/react-bootstrap-table2/src/body.js @@ -15,9 +15,36 @@ import withRowExpansion from './row-expand/row-consumer'; class Body extends React.Component { constructor(props) { super(props); - if (props.cellEdit.createContext) { - this.EditingCell = props.cellEdit.createEditingCell(_, props.cellEdit.options.onStartEdit); + const { + keyField, + visibleColumnSize, + cellEdit, + selectRow, + expandRow + } = props; + + // Construct Editing Cell Component + if (cellEdit.createContext) { + this.EditingCell = cellEdit.createEditingCell(_, cellEdit.options.onStartEdit); } + + // Construct Row Component + let RowComponent = SimpleRow; + const selectRowEnabled = selectRow.mode !== Const.ROW_SELECT_DISABLED; + const expandRowEnabled = !!expandRow.renderer; + + if (expandRowEnabled) { + RowComponent = withRowExpansion(RowAggregator, visibleColumnSize); + } + + if (selectRowEnabled) { + RowComponent = withRowSelection(expandRowEnabled ? RowComponent : RowAggregator); + } + + if (cellEdit.createContext) { + RowComponent = cellEdit.withRowLevelCellEdit(RowComponent, selectRowEnabled, keyField, _); + } + this.RowComponent = RowComponent; } render() { @@ -46,21 +73,12 @@ class Body extends React.Component { } content = ; } else { - let RowComponent = SimpleRow; const selectRowEnabled = selectRow.mode !== Const.ROW_SELECT_DISABLED; const expandRowEnabled = !!expandRow.renderer; const additionalRowProps = {}; - if (expandRowEnabled) { - RowComponent = withRowExpansion(RowAggregator, visibleColumnSize); - } - - if (selectRowEnabled) { - RowComponent = withRowSelection(expandRowEnabled ? RowComponent : RowAggregator); - } if (cellEdit.createContext) { - RowComponent = cellEdit.withRowLevelCellEdit(RowComponent, selectRowEnabled, keyField, _); additionalRowProps.EditingCellComponent = this.EditingCell; } @@ -88,7 +106,7 @@ class Body extends React.Component { baseRowProps.style = _.isFunction(rowStyle) ? rowStyle(row, index) : rowStyle; baseRowProps.className = (_.isFunction(rowClasses) ? rowClasses(row, index) : rowClasses); - return ; + return ; }); } diff --git a/packages/react-bootstrap-table2/src/contexts/index.js b/packages/react-bootstrap-table2/src/contexts/index.js index 7caf60c..4367ae1 100644 --- a/packages/react-bootstrap-table2/src/contexts/index.js +++ b/packages/react-bootstrap-table2/src/contexts/index.js @@ -217,6 +217,7 @@ const withContext = Base => ref={ n => this.searchContext = n } data={ rootProps.getData(filterProps) } searchText={ this.props.search.searchText } + dataChangeListener={ this.props.dataChangeListener } > { @@ -237,6 +238,7 @@ const withContext = Base => { ...baseProps } ref={ n => this.filterContext = n } data={ rootProps.getData() } + dataChangeListener={ this.props.dataChangeListener } > {