diff --git a/docs/README.md b/docs/README.md index 3fc1c58..dc12d3a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -33,6 +33,7 @@ * [pagination](#pagination) * [filter](#filter) * [onTableChange](#onTableChange) +* [onDataSizeChange](#onDataSizeChange) ### keyField(**required**) - [String] Tells `react-bootstrap-table2` which column is unique. @@ -317,4 +318,20 @@ Following is a shape of `newState` newValue } } +``` + +### onDataSizeChange - [Function] +This callback function will be called only when data size change by search/filter etc. This function have one argument which is an object contains below props: + +* `dataSize`: The new data size + +```js +handleDataChange = ({ dataSize }) => { + this.setState({ rowCount: dataSize }); +} + + ``` \ No newline at end of file diff --git a/packages/react-bootstrap-table2-example/.storybook/webpack.config.js b/packages/react-bootstrap-table2-example/.storybook/webpack.config.js index a3995c8..75e6df6 100644 --- a/packages/react-bootstrap-table2-example/.storybook/webpack.config.js +++ b/packages/react-bootstrap-table2-example/.storybook/webpack.config.js @@ -9,6 +9,7 @@ const sourceStylePath = path.join(__dirname, '../../react-bootstrap-table2/style const paginationStylePath = path.join(__dirname, '../../react-bootstrap-table2-paginator/style'); const filterStylePath = path.join(__dirname, '../../react-bootstrap-table2-filter/style'); const toolkitSourcePath = path.join(__dirname, '../../react-bootstrap-table2-toolkit/index.js'); +const toolkitStylePath = path.join(__dirname, '../../react-bootstrap-table2-toolkit/style'); const storyPath = path.join(__dirname, '../stories'); const examplesPath = path.join(__dirname, '../examples'); const srcPath = path.join(__dirname, '../src'); @@ -43,7 +44,13 @@ const loaders = [{ }, { test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'], - include: [storyPath, sourceStylePath, paginationStylePath, filterStylePath], + include: [ + storyPath, + sourceStylePath, + paginationStylePath, + filterStylePath, + toolkitStylePath + ], }, { test: /\.(jpg|png|woff|woff2|eot|ttf|svg)$/, loader: 'url-loader?limit=100000', diff --git a/packages/react-bootstrap-table2-example/examples/data/data-change-listener.js b/packages/react-bootstrap-table2-example/examples/data/data-change-listener.js new file mode 100644 index 0000000..3b9f737 --- /dev/null +++ b/packages/react-bootstrap-table2-example/examples/data/data-change-listener.js @@ -0,0 +1,151 @@ +/* eslint react/no-multi-comp: 0 */ +import React from 'react'; +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { textFilter } from 'react-bootstrap-table2-filter'; +import paginationFactory from 'react-bootstrap-table2-paginator'; +import Code from 'components/common/code-block'; +import { productsGenerator } from 'utils/common'; + +const columns = [{ + dataField: 'id', + text: 'Product ID' +}, { + dataField: 'name', + text: 'Product Name', + filter: textFilter() +}, { + dataField: 'price', + text: 'Product Price', + filter: textFilter() +}]; + +const sourceCode1 = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { textFilter } from 'react-bootstrap-table2-filter'; + +class Case1 extends React.Component { + constructor(props) { + super(props); + this.state = { rowCount: products.length }; + } + + handleDataChange = ({ dataSize }) => { + this.setState({ rowCount: dataSize }); + } + + render() { + return ( +
+
Row Count:{ this.state.rowCount }
+ + { sourceCode } +
+ ); + } +`; + +const sourceCode2 = `\ +import BootstrapTable from 'react-bootstrap-table-next'; +import filterFactory, { textFilter } from 'react-bootstrap-table2-filter'; +import paginationFactory from 'react-bootstrap-table2-paginator'; + +class Case2 extends React.Component { + constructor(props) { + super(props); + this.state = { rowCount: products.length }; + } + + handleDataChange = ({ dataSize }) => { + this.setState({ rowCount: dataSize }); + } + + render() { + return ( +
+
Row Count:{ this.state.rowCount }
+ + { sourceCode } +
+ ); + } +`; + +const products1 = productsGenerator(8); +class WithoutPaginationCase extends React.Component { + constructor(props) { + super(props); + this.state = { rowCount: products1.length }; + } + + handleDataChange = ({ dataSize }) => { + this.setState({ rowCount: dataSize }); + } + + render() { + return ( +
+

Without Pagination Case

+
Row Count:{ this.state.rowCount }
+ + { sourceCode2 } +
+ ); + } +} + +const products2 = productsGenerator(88); +class WithPaginationCase extends React.Component { + constructor(props) { + super(props); + this.state = { rowCount: products2.length }; + } + + handleDataChange = ({ dataSize }) => { + this.setState({ rowCount: dataSize }); + } + + render() { + return ( +
+

Without Pagination Case

+
Row Count:{ this.state.rowCount }
+ + { sourceCode1 } +
+ ); + } +} + +export default () => ( +
+ + +
+); + diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js index 9fc20d1..9bd79b8 100644 --- a/packages/react-bootstrap-table2-example/stories/index.js +++ b/packages/react-bootstrap-table2-example/stories/index.js @@ -217,6 +217,7 @@ import RemoteCellEdit from 'examples/remote/remote-celledit'; import RemoteAll from 'examples/remote/remote-all'; // data +import DataChangeListener from 'examples/data/data-change-listener'; import LoadDataWithFilter from 'examples/data/load-data-on-the-fly-with-filter'; import LoadDataWithDefaultFilter from 'examples/data/load-data-on-the-fly-with-default-filter'; import LoadDataWithSearch from 'examples/data/load-data-on-the-fly-with-search'; @@ -229,6 +230,7 @@ import 'stories/stylesheet/storybook.scss'; import '../../react-bootstrap-table2/style/react-bootstrap-table2.scss'; import '../../react-bootstrap-table2-paginator/style/react-bootstrap-table2-paginator.scss'; import '../../react-bootstrap-table2-filter/style/react-bootstrap-table2-filter.scss'; +import '../../react-bootstrap-table2-toolkit/style/react-bootstrap-table2-toolkit.scss'; // import bootstrap style by given version import bootstrapStyle, { BOOTSTRAP_VERSION } from './bootstrap-style'; @@ -466,6 +468,7 @@ storiesOf('Remote', module) storiesOf('Data', module) .addDecorator(bootstrapStyle()) + .add('Data Change Listener', () => ) .add('Load data with Filter', () => ) .add('Load data with Default Filter', () => ) .add('Load data with Search', () => ) diff --git a/packages/react-bootstrap-table2-filter/src/components/date.js b/packages/react-bootstrap-table2-filter/src/components/date.js index 45717ae..578f558 100644 --- a/packages/react-bootstrap-table2-filter/src/components/date.js +++ b/packages/react-bootstrap-table2-filter/src/components/date.js @@ -132,24 +132,35 @@ class DateFilter extends Component { className={ `filter date-filter ${className}` } style={ style } > - - this.inputDate = n } - className={ `filter date-filter-input form-control ${dateClassName}` } - style={ dateStyle } - type="date" - onChange={ this.onChangeDate } - placeholder={ placeholder || `Enter ${text}...` } - defaultValue={ this.getDefaultDate() } - /> + Filter comparator + + + ); } diff --git a/packages/react-bootstrap-table2-filter/src/components/multiselect.js b/packages/react-bootstrap-table2-filter/src/components/multiselect.js index 5f4623b..2de86f9 100644 --- a/packages/react-bootstrap-table2-filter/src/components/multiselect.js +++ b/packages/react-bootstrap-table2-filter/src/components/multiselect.js @@ -111,18 +111,25 @@ class MultiSelectFilter extends Component { `filter select-filter form-control ${className} ${this.state.isSelected ? '' : 'placeholder-selected'}`; return ( - + Filter by {column.text} + + ); } } diff --git a/packages/react-bootstrap-table2-filter/src/components/number.js b/packages/react-bootstrap-table2-filter/src/components/number.js index 447eed2..29e0370 100644 --- a/packages/react-bootstrap-table2-filter/src/components/number.js +++ b/packages/react-bootstrap-table2-filter/src/components/number.js @@ -173,35 +173,53 @@ class NumberFilter extends Component { className={ `filter number-filter ${className}` } style={ style } > - + Filter comparator + + { options ? - : - this.numberFilter = n } - type="number" - style={ numberStyle } - className={ `number-filter-input form-control ${numberClassName}` } - placeholder={ placeholder || `Enter ${column.text}...` } - onChange={ this.onChangeNumber } - defaultValue={ defaultValue ? defaultValue.number : '' } - /> + {`Select ${column.text}`} + + : + } ); diff --git a/packages/react-bootstrap-table2-filter/src/components/select.js b/packages/react-bootstrap-table2-filter/src/components/select.js index 973bdb3..db5348a 100644 --- a/packages/react-bootstrap-table2-filter/src/components/select.js +++ b/packages/react-bootstrap-table2-filter/src/components/select.js @@ -8,15 +8,18 @@ import { FILTER_TYPE } from '../const'; function optionsEquals(currOpts, prevOpts) { if (Array.isArray(currOpts)) { - for (let i = 0; i < currOpts.length; i += 1) { - if ( - currOpts[i].value !== prevOpts[i].value || - currOpts[i].label !== prevOpts[i].label - ) { - return false; + if (currOpts.length === prevOpts.length) { + for (let i = 0; i < currOpts.length; i += 1) { + if ( + currOpts[i].value !== prevOpts[i].value || + currOpts[i].label !== prevOpts[i].label + ) { + return false; + } } + return true; } - return currOpts.length === prevOpts.length; + return false; } const keys = Object.keys(currOpts); for (let i = 0; i < keys.length; i += 1) { @@ -136,17 +139,24 @@ class SelectFilter extends Component { `filter select-filter form-control ${className} ${this.state.isSelected ? '' : 'placeholder-selected'}`; return ( - + Filter by { column.text } + + ); } } diff --git a/packages/react-bootstrap-table2-filter/src/components/text.js b/packages/react-bootstrap-table2-filter/src/components/text.js index a17c3ea..1995ebe 100644 --- a/packages/react-bootstrap-table2-filter/src/components/text.js +++ b/packages/react-bootstrap-table2-filter/src/components/text.js @@ -94,17 +94,24 @@ class TextFilter extends Component { // stopPropagation for onClick event is try to prevent sort was triggered. return ( - this.input = n } - type="text" - className={ `filter text-filter form-control ${className}` } - style={ style } - onChange={ this.filter } - onClick={ this.handleClick } - placeholder={ placeholder || `Enter ${text}...` } - value={ this.state.value } - /> + ); } } diff --git a/packages/react-bootstrap-table2-filter/style/react-bootstrap-table2-filter.scss b/packages/react-bootstrap-table2-filter/style/react-bootstrap-table2-filter.scss index e2304b6..445579e 100644 --- a/packages/react-bootstrap-table2-filter/style/react-bootstrap-table2-filter.scss +++ b/packages/react-bootstrap-table2-filter/style/react-bootstrap-table2-filter.scss @@ -1,3 +1,7 @@ +.react-bootstrap-table > table > thead > tr > th .filter-label { + display: block !important; +} + .react-bootstrap-table > table > thead > tr > th .filter { font-weight: normal; } diff --git a/packages/react-bootstrap-table2-paginator/src/data-context.js b/packages/react-bootstrap-table2-paginator/src/data-context.js index e33f45f..654e9f8 100644 --- a/packages/react-bootstrap-table2-paginator/src/data-context.js +++ b/packages/react-bootstrap-table2-paginator/src/data-context.js @@ -46,6 +46,9 @@ class PaginationDataProvider extends Provider { this.currPage = newPage; } } + if (nextProps.onDataSizeChange && nextProps.data.length !== this.props.data.length) { + nextProps.onDataSizeChange({ dataSize: nextProps.data.length }); + } } isRemotePagination = () => this.props.isRemotePagination(); diff --git a/packages/react-bootstrap-table2-toolkit/src/search/SearchBar.js b/packages/react-bootstrap-table2-toolkit/src/search/SearchBar.js index f51f09c..64e148f 100644 --- a/packages/react-bootstrap-table2-toolkit/src/search/SearchBar.js +++ b/packages/react-bootstrap-table2-toolkit/src/search/SearchBar.js @@ -54,20 +54,28 @@ class SearchBar extends React.Component { const { className, style, - placeholder + placeholder, + tableId } = this.props; return ( - this.input = n } - type="text" - style={ style } - onKeyUp={ () => this.onKeyup() } - onChange={ this.onChangeValue } - className={ `form-control ${className}` } - value={ this.state.value } - placeholder={ placeholder || SearchBar.defaultProps.placeholder } - /> + ); } } @@ -78,7 +86,8 @@ SearchBar.propTypes = { placeholder: PropTypes.string, style: PropTypes.object, delay: PropTypes.number, - searchText: PropTypes.string + searchText: PropTypes.string, + tableId: PropTypes.string }; SearchBar.defaultProps = { @@ -86,7 +95,8 @@ SearchBar.defaultProps = { style: {}, placeholder: 'Search', delay: 250, - searchText: '' + searchText: '', + tableId: 0 }; export default SearchBar; diff --git a/packages/react-bootstrap-table2-toolkit/style/react-bootstrap-table2-toolkit.scss b/packages/react-bootstrap-table2-toolkit/style/react-bootstrap-table2-toolkit.scss index e69de29..c840023 100644 --- a/packages/react-bootstrap-table2-toolkit/style/react-bootstrap-table2-toolkit.scss +++ b/packages/react-bootstrap-table2-toolkit/style/react-bootstrap-table2-toolkit.scss @@ -0,0 +1,3 @@ +.search-label { + display: block !important; +} \ No newline at end of file diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js index 0df430c..bc0bc79 100644 --- a/packages/react-bootstrap-table2/src/bootstrap-table.js +++ b/packages/react-bootstrap-table2/src/bootstrap-table.js @@ -18,6 +18,14 @@ class BootstrapTable extends PropsBaseResolver(Component) { this.validateProps(); } + componentWillReceiveProps(nextProps) { + if (nextProps.onDataSizeChange && !nextProps.pagination) { + if (nextProps.data.length !== this.props.data.length) { + nextProps.onDataSizeChange({ dataSize: nextProps.data.length }); + } + } + } + // Exposed APIs getData = () => { return this.visibleRows(); @@ -192,6 +200,7 @@ BootstrapTable.propTypes = { onSort: PropTypes.func, onFilter: PropTypes.func, onExternalFilter: PropTypes.func, + onDataSizeChange: PropTypes.func, // Inject from toolkit search: PropTypes.shape({ searchText: PropTypes.string, diff --git a/packages/react-bootstrap-table2/src/contexts/index.js b/packages/react-bootstrap-table2/src/contexts/index.js index 148e805..1df8d6c 100644 --- a/packages/react-bootstrap-table2/src/contexts/index.js +++ b/packages/react-bootstrap-table2/src/contexts/index.js @@ -211,6 +211,7 @@ const withContext = Base => bootstrap4={ this.props.bootstrap4 } isRemotePagination={ this.isRemotePagination } remoteEmitter={ this.remoteEmitter } + onDataSizeChange={ this.props.onDataSizeChange } > { diff --git a/packages/react-bootstrap-table2/src/contexts/row-expand-context.js b/packages/react-bootstrap-table2/src/contexts/row-expand-context.js index 601c338..0c4789a 100644 --- a/packages/react-bootstrap-table2/src/contexts/row-expand-context.js +++ b/packages/react-bootstrap-table2/src/contexts/row-expand-context.js @@ -17,13 +17,17 @@ class RowExpandProvider extends React.Component { componentWillReceiveProps(nextProps) { if (nextProps.expandRow) { + const nextExpanded = nextProps.expandRow.expanded || this.state.expanded; const isClosing = this.state.expanded.reduce((acc, cur) => { - if (!nextProps.expandRow.expanded.includes(cur)) { + if (!nextExpanded.includes(cur)) { acc.push(cur); } return acc; }, []); - this.setState(() => ({ expanded: nextProps.expandRow.expanded, isClosing })); + this.setState(() => ({ + expanded: nextExpanded, + isClosing + })); } else { this.setState(() => ({ expanded: this.state.expanded