mirror of
https://github.com/gosticks/react-bootstrap-table2.git
synced 2026-06-29 05:30:05 +00:00
Compare commits
16 Commits
react-boot
...
react-boot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2585a62697 | ||
|
|
6afe58a081 | ||
|
|
6f5bd1a13d | ||
|
|
85a9ab72af | ||
|
|
258ea43225 | ||
|
|
7a7b708029 | ||
|
|
0cf89861af | ||
|
|
eb74625835 | ||
|
|
cbaec4c655 | ||
|
|
04c21cb63d | ||
|
|
7d72002b6e | ||
|
|
279cc25da0 | ||
|
|
1152bb8440 | ||
|
|
88befb8136 | ||
|
|
42c6bc0337 | ||
|
|
6e753bb955 |
@@ -18,6 +18,7 @@
|
||||
* [id](#id)
|
||||
* [classes](#classes)
|
||||
* [wrapperClasses](#wrapperClasses)
|
||||
* [headerClasses](#headerClasses)
|
||||
* [cellEdit](#cellEdit)
|
||||
* [selectRow](#selectRow)
|
||||
* [rowStyle](#rowStyle)
|
||||
@@ -111,6 +112,10 @@ Customize class on `table` element.
|
||||
|
||||
### <a name='wrapperClasses'>wrapperClasses - [String]</a>
|
||||
Customize class on the outer element which wrap up the `table` element.
|
||||
|
||||
### <a name='headerClasses'>headerClasses - [String]</a>
|
||||
Customize class on the header row(`tr`).
|
||||
|
||||
### <a name='cellEdit'>cellEdit - [Object]</a>
|
||||
Makes table cells editable, please see [cellEdit definition](./cell-edit.md) for more detail.
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ Please see [available filter configuration](https://react-bootstrap-table.github
|
||||
- [x] Text Filter
|
||||
- [x] Custom Text Filter
|
||||
- [x] Remote Filter
|
||||
- [ ] Custom Filter Component
|
||||
- [x] Custom Filter Component
|
||||
- [ ] Regex Filter
|
||||
- [x] Select Filter
|
||||
- [x] Custom Select Filter
|
||||
|
||||
184
packages/react-bootstrap-table2-example/examples/column-filter/advance-custom-filter.js
vendored
Normal file
184
packages/react-bootstrap-table2-example/examples/column-filter/advance-custom-filter.js
vendored
Normal file
@@ -0,0 +1,184 @@
|
||||
/* eslint no-return-assign: 0 */
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import BootstrapTable from 'react-bootstrap-table-next';
|
||||
import filterFactory, { textFilter, customFilter, Comparator, FILTER_TYPES } from 'react-bootstrap-table2-filter';
|
||||
import Code from 'components/common/code-block';
|
||||
import { productsGenerator } from 'utils/common';
|
||||
|
||||
const products = productsGenerator(8);
|
||||
|
||||
class PriceFilter extends React.Component {
|
||||
static propTypes = {
|
||||
column: PropTypes.object.isRequired,
|
||||
onFilter: PropTypes.func.isRequired
|
||||
}
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.filter = this.filter.bind(this);
|
||||
this.getValue = this.getValue.bind(this);
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.state = { value: 2100 };
|
||||
}
|
||||
onChange(e) {
|
||||
this.setState({ value: e.target.value });
|
||||
}
|
||||
getValue() {
|
||||
return parseInt(this.range.value, 10);
|
||||
}
|
||||
filter() {
|
||||
this.props.onFilter({
|
||||
number: this.getValue(),
|
||||
comparator: this.select.value
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return [
|
||||
<input
|
||||
key="range"
|
||||
ref={ node => this.range = node }
|
||||
type="range"
|
||||
min="2100"
|
||||
max="2110"
|
||||
onChange={ this.onChange }
|
||||
/>,
|
||||
<p
|
||||
key="show"
|
||||
ref={ node => this.showValue = node }
|
||||
style={ { textAlign: 'center' } }
|
||||
>
|
||||
{ this.state.value }
|
||||
</p>,
|
||||
<select
|
||||
key="select"
|
||||
ref={ node => this.select = node }
|
||||
className="form-control"
|
||||
>
|
||||
<option value={ Comparator.GT }>></option>
|
||||
<option value={ Comparator.EQ }>=</option>
|
||||
<option value={ Comparator.LT }><</option>
|
||||
</select>,
|
||||
<button
|
||||
key="submit"
|
||||
className="btn btn-warning"
|
||||
onClick={ this.filter }
|
||||
>
|
||||
{ `Filter ${this.props.column.text}` }
|
||||
</button>
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
const columns = [{
|
||||
dataField: 'id',
|
||||
text: 'Product ID'
|
||||
}, {
|
||||
dataField: 'name',
|
||||
text: 'Product Name',
|
||||
filter: textFilter()
|
||||
}, {
|
||||
dataField: 'price',
|
||||
text: 'Product Price',
|
||||
filter: customFilter({
|
||||
type: FILTER_TYPES.NUMBER // ask react-bootstrap-table to filter data as number
|
||||
}),
|
||||
filterRenderer: (onFilter, column) =>
|
||||
<PriceFilter onFilter={ onFilter } column={ column } />
|
||||
}];
|
||||
|
||||
const sourceCode = `\
|
||||
import BootstrapTable from 'react-bootstrap-table-next';
|
||||
import filterFactory, { textFilter, customFilter, Comparator, FILTER_TYPES } from 'react-bootstrap-table2-filter';
|
||||
|
||||
class PriceFilter extends React.Component {
|
||||
static propTypes = {
|
||||
column: PropTypes.object.isRequired,
|
||||
onFilter: PropTypes.func.isRequired
|
||||
}
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.filter = this.filter.bind(this);
|
||||
this.getValue = this.getValue.bind(this);
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.state = { value: 2100 };
|
||||
}
|
||||
onChange(e) {
|
||||
this.setState({ value: e.target.value });
|
||||
}
|
||||
getValue() {
|
||||
return parseInt(this.range.value, 10);
|
||||
}
|
||||
filter() {
|
||||
this.props.onFilter({
|
||||
number: this.getValue(),
|
||||
comparator: this.select.value
|
||||
});
|
||||
}
|
||||
render() {
|
||||
return [
|
||||
<input
|
||||
key="range"
|
||||
ref={ node => this.range = node }
|
||||
type="range"
|
||||
min="2100"
|
||||
max="2110"
|
||||
onChange={ this.onChange }
|
||||
/>,
|
||||
<p
|
||||
key="show"
|
||||
ref={ node => this.showValue = node }
|
||||
style={ { textAlign: 'center' } }
|
||||
>
|
||||
{ this.state.value }
|
||||
</p>,
|
||||
<select
|
||||
key="select"
|
||||
ref={ node => this.select = node }
|
||||
className="form-control"
|
||||
>
|
||||
<option value={ Comparator.GT }>></option>
|
||||
<option value={ Comparator.EQ }>=</option>
|
||||
<option value={ Comparator.LT }><</option>
|
||||
</select>,
|
||||
<button
|
||||
key="submit"
|
||||
className="btn btn-warning"
|
||||
onClick={ this.filter }
|
||||
>
|
||||
{ \`Filter $\{this.props.column.text}\` }
|
||||
</button>
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
const columns = [{
|
||||
dataField: 'id',
|
||||
text: 'Product ID'
|
||||
}, {
|
||||
dataField: 'name',
|
||||
text: 'Product Name',
|
||||
filter: textFilter()
|
||||
}, {
|
||||
dataField: 'price',
|
||||
text: 'Product Price',
|
||||
filter: customFilter({
|
||||
type: FILTER_TYPES.NUMBER // ask react-bootstrap-table to filter data as number
|
||||
}),
|
||||
filterRenderer: (onFilter, column) =>
|
||||
<PriceFilter onFilter={ onFilter } column={ column } />
|
||||
}];
|
||||
|
||||
<BootstrapTable keyField='id' data={ products } columns={ columns } filter={ filterFactory() } />
|
||||
`;
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
filter={ filterFactory() }
|
||||
/>
|
||||
<Code>{ sourceCode }</Code>
|
||||
</div>
|
||||
);
|
||||
128
packages/react-bootstrap-table2-example/examples/column-filter/custom-filter.js
vendored
Normal file
128
packages/react-bootstrap-table2-example/examples/column-filter/custom-filter.js
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
/* eslint no-return-assign: 0 */
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import BootstrapTable from 'react-bootstrap-table-next';
|
||||
import filterFactory, { textFilter, customFilter } from 'react-bootstrap-table2-filter';
|
||||
import Code from 'components/common/code-block';
|
||||
import { productsGenerator } from 'utils/common';
|
||||
|
||||
const products = productsGenerator(8);
|
||||
|
||||
class PriceFilter extends React.Component {
|
||||
static propTypes = {
|
||||
column: PropTypes.object.isRequired,
|
||||
onFilter: PropTypes.func.isRequired
|
||||
}
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.filter = this.filter.bind(this);
|
||||
this.getValue = this.getValue.bind(this);
|
||||
}
|
||||
getValue() {
|
||||
return this.input.value;
|
||||
}
|
||||
filter() {
|
||||
this.props.onFilter(this.getValue());
|
||||
}
|
||||
render() {
|
||||
return [
|
||||
<input
|
||||
key="input"
|
||||
ref={ node => this.input = node }
|
||||
type="text"
|
||||
placeholder="Input price"
|
||||
/>,
|
||||
<button
|
||||
key="submit"
|
||||
className="btn btn-warning"
|
||||
onClick={ this.filter }
|
||||
>
|
||||
{ `Find ${this.props.column.text}` }
|
||||
</button>
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
const columns = [{
|
||||
dataField: 'id',
|
||||
text: 'Product ID'
|
||||
}, {
|
||||
dataField: 'name',
|
||||
text: 'Product Name',
|
||||
filter: textFilter()
|
||||
}, {
|
||||
dataField: 'price',
|
||||
text: 'Product Price',
|
||||
filter: customFilter(),
|
||||
filterRenderer: (onFilter, column) =>
|
||||
<PriceFilter onFilter={ onFilter } column={ column } />
|
||||
}];
|
||||
|
||||
const sourceCode = `\
|
||||
import BootstrapTable from 'react-bootstrap-table-next';
|
||||
import filterFactory, { textFilter, customFilter } from 'react-bootstrap-table2-filter';
|
||||
|
||||
class PriceFilter extends React.Component {
|
||||
static propTypes = {
|
||||
column: PropTypes.object.isRequired,
|
||||
onFilter: PropTypes.func.isRequired
|
||||
}
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.filter = this.filter.bind(this);
|
||||
this.getValue = this.getValue.bind(this);
|
||||
}
|
||||
getValue() {
|
||||
return this.input.value;
|
||||
}
|
||||
filter() {
|
||||
this.props.onFilter(this.getValue());
|
||||
}
|
||||
render() {
|
||||
return [
|
||||
<input
|
||||
key="input"
|
||||
ref={ node => this.input = node }
|
||||
type="text"
|
||||
placeholder="Input price"
|
||||
/>,
|
||||
<button
|
||||
key="submit"
|
||||
className="btn btn-warning"
|
||||
onClick={ this.filter }
|
||||
>
|
||||
{ \`Filter $\{this.props.column.text}\` }
|
||||
</button>
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
const columns = [{
|
||||
dataField: 'id',
|
||||
text: 'Product ID'
|
||||
}, {
|
||||
dataField: 'name',
|
||||
text: 'Product Name',
|
||||
filter: textFilter()
|
||||
}, {
|
||||
dataField: 'price',
|
||||
text: 'Product Price',
|
||||
filter: customFilter(),
|
||||
filterRenderer: (onFilter, column) =>
|
||||
<PriceFilter onFilter={ onFilter } column={ column } />
|
||||
}];
|
||||
|
||||
<BootstrapTable keyField='id' data={ products } columns={ columns } filter={ filterFactory() } />
|
||||
`;
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
filter={ filterFactory() }
|
||||
/>
|
||||
<Code>{ sourceCode }</Code>
|
||||
</div>
|
||||
);
|
||||
52
packages/react-bootstrap-table2-example/examples/header-columns/header-class-table.js
vendored
Normal file
52
packages/react-bootstrap-table2-example/examples/header-columns/header-class-table.js
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
|
||||
import BootstrapTable from 'react-bootstrap-table-next';
|
||||
import Code from 'components/common/code-block';
|
||||
import { productsGenerator } from 'utils/common';
|
||||
|
||||
const products = productsGenerator();
|
||||
|
||||
const columns = [{
|
||||
dataField: 'id',
|
||||
text: 'Product ID'
|
||||
}, {
|
||||
dataField: 'name',
|
||||
text: 'Product Name'
|
||||
}, {
|
||||
dataField: 'price',
|
||||
text: 'Product Price'
|
||||
}];
|
||||
|
||||
const sourceCode = `\
|
||||
import BootstrapTable from 'react-bootstrap-table-next';
|
||||
|
||||
const columns = [{
|
||||
dataField: 'id',
|
||||
text: 'Product ID'
|
||||
}, {
|
||||
dataField: 'name',
|
||||
text: 'Product Name'
|
||||
}, {
|
||||
dataField: 'price',
|
||||
text: 'Product Price'
|
||||
}];
|
||||
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
headerClasses="header-class"
|
||||
/>
|
||||
`;
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
headerClasses="header-class"
|
||||
/>
|
||||
<Code>{ sourceCode }</Code>
|
||||
</div>
|
||||
);
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-bootstrap-table2-example",
|
||||
"version": "0.1.9",
|
||||
"version": "0.1.11",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"private": true,
|
||||
|
||||
@@ -33,6 +33,7 @@ import HeaderColumnEventTable from 'examples/header-columns/column-event-table';
|
||||
import HeaderColumnClassTable from 'examples/header-columns/column-class-table';
|
||||
import HeaderColumnStyleTable from 'examples/header-columns/column-style-table';
|
||||
import HeaderColumnAttrsTable from 'examples/header-columns/column-attrs-table';
|
||||
import HeaderClassTable from 'examples/header-columns/header-class-table';
|
||||
|
||||
// column filter
|
||||
import TextFilter from 'examples/column-filter/text-filter';
|
||||
@@ -55,6 +56,8 @@ import ProgrammaticallyTextFilter from 'examples/column-filter/programmatically-
|
||||
import ProgrammaticallySelectFilter from 'examples/column-filter/programmatically-select-filter';
|
||||
import ProgrammaticallyNumberFilter from 'examples/column-filter/programmatically-number-filter';
|
||||
import ProgrammaticallyDateFilter from 'examples/column-filter/programmatically-date-filter';
|
||||
import CustomFilter from 'examples/column-filter/custom-filter';
|
||||
import AdvanceCustomFilter from 'examples/column-filter/advance-custom-filter';
|
||||
|
||||
// work on rows
|
||||
import RowStyleTable from 'examples/rows/row-style';
|
||||
@@ -163,7 +166,8 @@ storiesOf('Work on Header Columns', module)
|
||||
.add('Column Event', () => <HeaderColumnEventTable />)
|
||||
.add('Customize Column Class', () => <HeaderColumnClassTable />)
|
||||
.add('Customize Column Style', () => <HeaderColumnStyleTable />)
|
||||
.add('Customize Column HTML attribute', () => <HeaderColumnAttrsTable />);
|
||||
.add('Customize Column HTML attribute', () => <HeaderColumnAttrsTable />)
|
||||
.add('Header Class', () => <HeaderClassTable />);
|
||||
|
||||
storiesOf('Column Filter', module)
|
||||
.add('Text Filter', () => <TextFilter />)
|
||||
@@ -186,7 +190,9 @@ storiesOf('Column Filter', module)
|
||||
.add('Programmatically Text Filter', () => <ProgrammaticallyTextFilter />)
|
||||
.add('Programmatically Select Filter', () => <ProgrammaticallySelectFilter />)
|
||||
.add('Programmatically Number Filter', () => <ProgrammaticallyNumberFilter />)
|
||||
.add('Programmatically Date Filter', () => <ProgrammaticallyDateFilter />);
|
||||
.add('Programmatically Date Filter', () => <ProgrammaticallyDateFilter />)
|
||||
.add('Custom Filter', () => <CustomFilter />)
|
||||
.add('Advance Custom Filter', () => <AdvanceCustomFilter />);
|
||||
|
||||
storiesOf('Work on Rows', module)
|
||||
.add('Customize Row Style', () => <RowStyleTable />)
|
||||
|
||||
@@ -10,3 +10,7 @@
|
||||
.demo-row-odd {
|
||||
background-color: $green-lighten-4;
|
||||
}
|
||||
|
||||
.header-class {
|
||||
background-color: $green-lighten-4;
|
||||
}
|
||||
@@ -20,6 +20,7 @@ You can get all types of filters via import and these filters are a factory func
|
||||
* SelectFilter
|
||||
* NumberFilter
|
||||
* DateFilter
|
||||
* CustomFilter
|
||||
* **Coming soon!**
|
||||
|
||||
## Add CSS
|
||||
@@ -190,3 +191,53 @@ const dateFilter = dateFilter({
|
||||
|
||||
// omit...
|
||||
```
|
||||
|
||||
## Custom Filter
|
||||
|
||||
```js
|
||||
import filterFactory, { customFilter } from 'react-bootstrap-table2-filter';
|
||||
|
||||
const columns = [..., {
|
||||
dataField: 'date',
|
||||
text: 'Product Name',
|
||||
filter: customFilter(),
|
||||
filterRenderer: (onFilter, column) => .....
|
||||
}];
|
||||
|
||||
<BootstrapTable keyField='id' data={ products } columns={ columns } filter={ filterFactory() } />
|
||||
```
|
||||
|
||||
In custom filter case, you are suppose to finish following two steps:
|
||||
1. Call `customFilter` and pass to `column.filter`
|
||||
2. Give `column.filterRenderer` as a callback function and return your custom filter element.
|
||||
|
||||
### column.filterRenderer
|
||||
|
||||
This function will pass two argument to you:
|
||||
1. `onFilter`: call it to trigger filter when you need.
|
||||
2. `column`: Just the column object!
|
||||
|
||||
In the end, please remember to return your custom filter element!
|
||||
|
||||
### customFilter
|
||||
|
||||
`customFilter` function just same as `textFilter`, `selectFilter` etc, it is for customization reason. However, in the custom filter case, there's only one props is valid: `type`
|
||||
|
||||
|
||||
```js
|
||||
import filterFactory, { FILTER_TYPES } from 'react-bootstrap-table2-filter';
|
||||
|
||||
const customFilter = customFilter({
|
||||
type: FILTER_TYPES.NUMBER, // default is FILTER_TYPES.TEXT
|
||||
})
|
||||
```
|
||||
|
||||
`type` is a way to ask `react-bootstrap-table` to filter you data as number, select, date or normal text.
|
||||
|
||||
### FILTER_TYPES
|
||||
|
||||
Following properties is valid in `FILTER_TYPES`:
|
||||
* TEXT
|
||||
* SELECT
|
||||
* NUMBER
|
||||
* DATE
|
||||
@@ -4,12 +4,15 @@ import NumberFilter from './src/components/number';
|
||||
import DateFilter from './src/components/date';
|
||||
import wrapperFactory from './src/wrapper';
|
||||
import * as Comparison from './src/comparison';
|
||||
import { FILTER_TYPE } from './src/const';
|
||||
|
||||
export default (options = {}) => ({
|
||||
wrapperFactory,
|
||||
options
|
||||
});
|
||||
|
||||
export const FILTER_TYPES = FILTER_TYPE;
|
||||
|
||||
export const Comparator = Comparison;
|
||||
|
||||
export const textFilter = (props = {}) => ({
|
||||
@@ -31,3 +34,7 @@ export const dateFilter = (props = {}) => ({
|
||||
Filter: DateFilter,
|
||||
props
|
||||
});
|
||||
|
||||
export const customFilter = (props = {}) => ({
|
||||
props
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-bootstrap-table2-filter",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"description": "it's a column filter addon for react-bootstrap-table2",
|
||||
"main": "./lib/index.js",
|
||||
"repository": {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* eslint react/require-default-props: 0 */
|
||||
/* eslint jsx-a11y/no-static-element-interactions: 0 */
|
||||
/* eslint no-return-assign: 0 */
|
||||
/* eslint prefer-template: 0 */
|
||||
import React, { Component } from 'react';
|
||||
@@ -121,7 +122,11 @@ class DateFilter extends Component {
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className={ `filter date-filter ${className}` } style={ style }>
|
||||
<div
|
||||
onClick={ e => e.stopPropagation() }
|
||||
className={ `filter date-filter ${className}` }
|
||||
style={ style }
|
||||
>
|
||||
<select
|
||||
ref={ n => this.dateFilterComparator = n }
|
||||
style={ comparatorStyle }
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint jsx-a11y/no-static-element-interactions: 0 */
|
||||
/* eslint react/require-default-props: 0 */
|
||||
/* eslint no-return-assign: 0 */
|
||||
|
||||
@@ -167,7 +168,11 @@ class NumberFilter extends Component {
|
||||
`;
|
||||
|
||||
return (
|
||||
<div className={ `filter number-filter ${className}` } style={ style }>
|
||||
<div
|
||||
onClick={ e => e.stopPropagation() }
|
||||
className={ `filter number-filter ${className}` }
|
||||
style={ style }
|
||||
>
|
||||
<select
|
||||
ref={ n => this.numberFilterComparator = n }
|
||||
style={ comparatorStyle }
|
||||
|
||||
@@ -116,6 +116,7 @@ class SelectFilter extends Component {
|
||||
style={ style }
|
||||
className={ selectClass }
|
||||
onChange={ this.filter }
|
||||
onClick={ e => e.stopPropagation() }
|
||||
defaultValue={ defaultValue !== undefined ? defaultValue : '' }
|
||||
>
|
||||
{ this.getOptions() }
|
||||
|
||||
@@ -20,6 +20,7 @@ export default (Base, {
|
||||
super(props);
|
||||
this.state = { currFilters: {}, isDataChanged: props.isDataChanged || false };
|
||||
this.onFilter = this.onFilter.bind(this);
|
||||
this.onExternalFilter = this.onExternalFilter.bind(this);
|
||||
}
|
||||
|
||||
componentWillReceiveProps({ isDataChanged, store, columns }) {
|
||||
@@ -78,12 +79,19 @@ export default (Base, {
|
||||
};
|
||||
}
|
||||
|
||||
onExternalFilter(column, filterType) {
|
||||
return (value) => {
|
||||
this.onFilter(column, filterType)(value);
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Base
|
||||
{ ...this.props }
|
||||
data={ this.props.store.data }
|
||||
onFilter={ this.onFilter }
|
||||
onExternalFilter={ this.onExternalFilter }
|
||||
isDataChanged={ this.state.isDataChanged }
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-bootstrap-table2-paginator",
|
||||
"version": "0.1.4",
|
||||
"version": "0.1.6",
|
||||
"description": "it's the pagination addon for react-bootstrap-table2",
|
||||
"main": "./lib/index.js",
|
||||
"repository": {
|
||||
|
||||
@@ -1,12 +1,39 @@
|
||||
/* eslint no-param-reassign: 0 */
|
||||
|
||||
const getNormalizedPage = (
|
||||
page,
|
||||
pageStartIndex
|
||||
) => {
|
||||
const offset = Math.abs(1 - pageStartIndex);
|
||||
return page + offset;
|
||||
};
|
||||
|
||||
const endIndex = (
|
||||
page,
|
||||
sizePerPage,
|
||||
pageStartIndex
|
||||
) => (getNormalizedPage(page, pageStartIndex) * sizePerPage) - 1;
|
||||
|
||||
const startIndex = (
|
||||
end,
|
||||
sizePerPage,
|
||||
) => end - (sizePerPage - 1);
|
||||
|
||||
export const alignPage = (store, pageStartIndex, sizePerPage) => {
|
||||
const end = endIndex(store.page, sizePerPage, pageStartIndex);
|
||||
const dataSize = store.data.length;
|
||||
|
||||
if (end - 1 > dataSize) {
|
||||
return pageStartIndex;
|
||||
}
|
||||
return store.page;
|
||||
};
|
||||
|
||||
export const getByCurrPage = (store, pageStartIndex) => {
|
||||
const dataSize = store.data.length;
|
||||
if (!dataSize) return [];
|
||||
const getNormalizedPage = () => {
|
||||
const offset = Math.abs(1 - pageStartIndex);
|
||||
return store.page + offset;
|
||||
};
|
||||
const end = (getNormalizedPage() * store.sizePerPage) - 1;
|
||||
const start = end - (store.sizePerPage - 1);
|
||||
const end = endIndex(store.page, store.sizePerPage, pageStartIndex);
|
||||
const start = startIndex(end, store.sizePerPage);
|
||||
|
||||
const result = [];
|
||||
for (let i = start; i <= end; i += 1) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
||||
|
||||
import Const from './const';
|
||||
import Pagination from './pagination';
|
||||
import { getByCurrPage } from './page';
|
||||
import { getByCurrPage, alignPage } from './page';
|
||||
|
||||
export default (Base, {
|
||||
remoteResolver
|
||||
@@ -49,16 +49,23 @@ export default (Base, {
|
||||
componentWillReceiveProps(nextProps) {
|
||||
let needNewState = false;
|
||||
let { currPage, currSizePerPage } = this.state;
|
||||
const { page, sizePerPage, pageStartIndex, onPageChange } = nextProps.pagination.options;
|
||||
const { page, sizePerPage, onPageChange } = nextProps.pagination.options;
|
||||
|
||||
const pageStartIndex = typeof nextProps.pagination.options.pageStartIndex !== 'undefined' ?
|
||||
nextProps.pagination.options.pageStartIndex : Const.PAGE_START_INDEX;
|
||||
|
||||
if (typeof page !== 'undefined' && currPage !== page) { // user defined page
|
||||
currPage = page;
|
||||
needNewState = true;
|
||||
} else if (nextProps.isDataChanged) { // user didn't defined page but data change
|
||||
currPage = typeof pageStartIndex !== 'undefined' ? pageStartIndex : Const.PAGE_START_INDEX;
|
||||
} else if (nextProps.isDataChanged) {
|
||||
currPage = alignPage(this.props.store, pageStartIndex, currSizePerPage);
|
||||
needNewState = true;
|
||||
}
|
||||
|
||||
if (typeof currPage === 'undefined') {
|
||||
currPage = pageStartIndex;
|
||||
}
|
||||
|
||||
if (typeof sizePerPage !== 'undefined') {
|
||||
currSizePerPage = sizePerPage;
|
||||
needNewState = true;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Store from 'react-bootstrap-table-next/src/store';
|
||||
import { getByCurrPage } from '../src/page';
|
||||
import { getByCurrPage, alignPage } from '../src/page';
|
||||
|
||||
describe('Page Functions', () => {
|
||||
let data;
|
||||
@@ -48,4 +48,40 @@ describe('Page Functions', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('alignPage', () => {
|
||||
const pageStartIndex = 1;
|
||||
const sizePerPage = 10;
|
||||
describe('if the length of store.data is less than the end page index', () => {
|
||||
beforeEach(() => {
|
||||
data = [];
|
||||
for (let i = 0; i < 15; i += 1) {
|
||||
data.push({ id: i, name: `test_name${i}` });
|
||||
}
|
||||
store = new Store('id');
|
||||
store.data = data;
|
||||
store.page = 2;
|
||||
});
|
||||
|
||||
it('should return pageStartIndex argument', () => {
|
||||
expect(alignPage(store, pageStartIndex, sizePerPage)).toEqual(pageStartIndex);
|
||||
});
|
||||
});
|
||||
|
||||
describe('if the length of store.data is large than the end page index', () => {
|
||||
beforeEach(() => {
|
||||
data = [];
|
||||
for (let i = 0; i < 30; i += 1) {
|
||||
data.push({ id: i, name: `test_name${i}` });
|
||||
}
|
||||
store = new Store('id');
|
||||
store.data = data;
|
||||
store.page = 2;
|
||||
});
|
||||
|
||||
it('should return current page', () => {
|
||||
expect(alignPage(store, pageStartIndex, sizePerPage)).toEqual(store.page);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -176,22 +176,6 @@ describe('Wrapper', () => {
|
||||
expect(props.store.page).toEqual(instance.state.currPage);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when nextProps.isDataChanged is true and options.pageStartIndex is existing', () => {
|
||||
beforeEach(() => {
|
||||
nextProps.isDataChanged = true;
|
||||
nextProps.pagination.options.pageStartIndex = 0;
|
||||
instance.componentWillReceiveProps(nextProps);
|
||||
});
|
||||
|
||||
it('should setting currPage state correctly', () => {
|
||||
expect(instance.state.currPage).toBe(nextProps.pagination.options.pageStartIndex);
|
||||
});
|
||||
|
||||
it('should saving store.page correctly', () => {
|
||||
expect(props.store.page).toEqual(instance.state.currPage);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-bootstrap-table-next",
|
||||
"version": "0.1.13",
|
||||
"version": "0.1.15",
|
||||
"description": "Next generation of react-bootstrap-table",
|
||||
"main": "./lib/index.js",
|
||||
"repository": {
|
||||
|
||||
@@ -86,10 +86,12 @@ class BootstrapTable extends PropsBaseResolver(Component) {
|
||||
{ tableCaption }
|
||||
<Header
|
||||
columns={ columns }
|
||||
className={ this.props.headerClasses }
|
||||
sortField={ store.sortField }
|
||||
sortOrder={ store.sortOrder }
|
||||
onSort={ this.props.onSort }
|
||||
onFilter={ this.props.onFilter }
|
||||
onExternalFilter={ this.props.onExternalFilter }
|
||||
selectRow={ headerCellSelectionInfo }
|
||||
/>
|
||||
<Body
|
||||
@@ -154,6 +156,7 @@ BootstrapTable.propTypes = {
|
||||
rowStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
|
||||
rowEvents: PropTypes.object,
|
||||
rowClasses: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
||||
headerClasses: PropTypes.string,
|
||||
defaultSorted: PropTypes.arrayOf(PropTypes.shape({
|
||||
dataField: PropTypes.string.isRequired,
|
||||
order: PropTypes.oneOf([Const.SORT_DESC, Const.SORT_ASC]).isRequired
|
||||
@@ -162,7 +165,8 @@ BootstrapTable.propTypes = {
|
||||
overlay: PropTypes.func,
|
||||
onTableChange: PropTypes.func,
|
||||
onSort: PropTypes.func,
|
||||
onFilter: PropTypes.func
|
||||
onFilter: PropTypes.func,
|
||||
onExternalFilter: PropTypes.func
|
||||
};
|
||||
|
||||
BootstrapTable.defaultProps = {
|
||||
|
||||
@@ -17,13 +17,15 @@ const HeaderCell = (props) => {
|
||||
sorting,
|
||||
sortOrder,
|
||||
isLastSorting,
|
||||
onFilter
|
||||
onFilter,
|
||||
onExternalFilter
|
||||
} = props;
|
||||
|
||||
const {
|
||||
text,
|
||||
sort,
|
||||
filter,
|
||||
filterRenderer,
|
||||
headerTitle,
|
||||
headerAlign,
|
||||
headerFormatter,
|
||||
@@ -89,7 +91,11 @@ const HeaderCell = (props) => {
|
||||
|
||||
if (cellClasses) cellAttrs.className = cs(cellAttrs.className, cellClasses);
|
||||
if (!_.isEmptyObject(cellStyle)) cellAttrs.style = cellStyle;
|
||||
if (filter) {
|
||||
|
||||
if (filterRenderer) {
|
||||
const onCustomFilter = onExternalFilter(column, filter.props.type);
|
||||
filterElm = filterRenderer(onCustomFilter, column);
|
||||
} else if (filter) {
|
||||
filterElm = <filter.Filter { ...filter.props } onFilter={ onFilter } column={ column } />;
|
||||
}
|
||||
|
||||
@@ -136,6 +142,7 @@ HeaderCell.propTypes = {
|
||||
editorRenderer: PropTypes.func,
|
||||
validator: PropTypes.func,
|
||||
filter: PropTypes.object,
|
||||
filterRenderer: PropTypes.func,
|
||||
filterValue: PropTypes.func
|
||||
}).isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
@@ -143,7 +150,8 @@ HeaderCell.propTypes = {
|
||||
sorting: PropTypes.bool,
|
||||
sortOrder: PropTypes.oneOf([Const.SORT_ASC, Const.SORT_DESC]),
|
||||
isLastSorting: PropTypes.bool,
|
||||
onFilter: PropTypes.func
|
||||
onFilter: PropTypes.func,
|
||||
onExternalFilter: PropTypes.func
|
||||
};
|
||||
|
||||
export default HeaderCell;
|
||||
|
||||
11
packages/react-bootstrap-table2/src/header.js
vendored
11
packages/react-bootstrap-table2/src/header.js
vendored
@@ -10,17 +10,19 @@ const Header = (props) => {
|
||||
const { ROW_SELECT_DISABLED } = Const;
|
||||
|
||||
const {
|
||||
className,
|
||||
columns,
|
||||
onSort,
|
||||
onFilter,
|
||||
sortField,
|
||||
sortOrder,
|
||||
selectRow
|
||||
selectRow,
|
||||
onExternalFilter
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<thead>
|
||||
<tr>
|
||||
<tr className={ className }>
|
||||
{
|
||||
(selectRow.mode !== ROW_SELECT_DISABLED && !selectRow.hideSelectColumn)
|
||||
? <SelectionHeaderCell { ...selectRow } /> : null
|
||||
@@ -39,6 +41,7 @@ const Header = (props) => {
|
||||
onSort={ onSort }
|
||||
sorting={ currSort }
|
||||
onFilter={ onFilter }
|
||||
onExternalFilter={ onExternalFilter }
|
||||
sortOrder={ sortOrder }
|
||||
isLastSorting={ isLastSorting }
|
||||
/>);
|
||||
@@ -57,7 +60,9 @@ Header.propTypes = {
|
||||
onFilter: PropTypes.func,
|
||||
sortField: PropTypes.string,
|
||||
sortOrder: PropTypes.string,
|
||||
selectRow: PropTypes.object
|
||||
selectRow: PropTypes.object,
|
||||
onExternalFilter: PropTypes.func,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
export default Header;
|
||||
|
||||
@@ -669,4 +669,74 @@ describe('HeaderCell', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when column.filter is defined', () => {
|
||||
const onFilter = jest.fn();
|
||||
const filterProps = { a: 123 };
|
||||
const Filter = () => <div>test</div>;
|
||||
let column;
|
||||
|
||||
beforeEach(() => {
|
||||
onFilter.mockClear();
|
||||
column = {
|
||||
dataField: 'id',
|
||||
text: 'ID',
|
||||
filter: {
|
||||
props: filterProps,
|
||||
Filter
|
||||
}
|
||||
};
|
||||
wrapper = shallow(<HeaderCell column={ column } index={ index } onFilter={ onFilter } />);
|
||||
});
|
||||
|
||||
it('should render successfully', () => {
|
||||
expect(wrapper.length).toBe(1);
|
||||
expect(wrapper.find('th').length).toBe(1);
|
||||
});
|
||||
|
||||
it('should render filter correctly', () => {
|
||||
expect(wrapper.find(Filter).length).toBe(1);
|
||||
expect(wrapper.find(Filter).props()).toEqual({
|
||||
column,
|
||||
onFilter,
|
||||
...filterProps
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when column.filter and column.filterRenderer is defined', () => {
|
||||
const onExternalFilter = jest.fn();
|
||||
const filterProps = { a: 123 };
|
||||
const Filter = () => <div>test</div>;
|
||||
const filterRenderer = jest.fn().mockReturnValue(<Filter />);
|
||||
let column;
|
||||
|
||||
beforeEach(() => {
|
||||
onExternalFilter.mockClear();
|
||||
filterRenderer.mockClear();
|
||||
column = {
|
||||
dataField: 'id',
|
||||
text: 'ID',
|
||||
filter: {
|
||||
props: filterProps
|
||||
},
|
||||
filterRenderer
|
||||
};
|
||||
wrapper = shallow(
|
||||
<HeaderCell column={ column } index={ index } onExternalFilter={ onExternalFilter } />);
|
||||
});
|
||||
|
||||
it('should render successfully', () => {
|
||||
expect(wrapper.length).toBe(1);
|
||||
expect(wrapper.find('th').length).toBe(1);
|
||||
});
|
||||
|
||||
it('should render filter correctly', () => {
|
||||
expect(wrapper.find(Filter).length).toBe(1);
|
||||
});
|
||||
|
||||
it('should call filterRenderer function correctly', () => {
|
||||
expect(filterRenderer).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,6 +29,25 @@ describe('Header', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('className prop is exists', () => {
|
||||
const className = 'test-class';
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(
|
||||
<Header
|
||||
{ ...mockHeaderResolvedProps }
|
||||
columns={ columns }
|
||||
className={ className }
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
it('should render successfully', () => {
|
||||
expect(wrapper.length).toBe(1);
|
||||
expect(wrapper.find(`.${className}`).length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('header with columns enable sort', () => {
|
||||
const sortField = columns[1].dataField;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user