Compare commits

..

38 Commits

Author SHA1 Message Date
AllenFang
8e142de332 Publish
- react-bootstrap-table2-example@1.0.17
 - react-bootstrap-table2-filter@1.1.4
 - react-bootstrap-table2-toolkit@1.2.2
 - react-bootstrap-table-next@2.1.2
2019-02-16 17:11:10 +08:00
Allen
322605f14e Merge pull request #798 from react-bootstrap-table/develop
20190216 release
2019-02-16 17:09:40 +08:00
Allen
3156e01dd6 fix #788 (#797) 2019-02-16 16:26:26 +08:00
Allen
052284a163 fix #791 (#796) 2019-02-16 16:18:33 +08:00
AllenFang
3cd8efffb9 Publish
- react-bootstrap-table2-example@1.0.16
 - react-bootstrap-table2-filter@1.1.3
 - react-bootstrap-table2-paginator@2.0.2
 - react-bootstrap-table2-toolkit@1.2.1
 - react-bootstrap-table-next@2.1.1
2019-02-09 21:07:26 +08:00
Allen
447d69cae5 Merge pull request #790 from react-bootstrap-table/develop
20190209 release
2019-02-09 21:04:28 +08:00
AllenFang
cacc28e1bc fix test cases 2019-02-09 20:28:28 +08:00
AllenFang
d7f84a9da5 fix remote filter/search broken when pagination enabled 2019-02-09 18:01:53 +08:00
AllenFang
63c2630f46 fix same issue #778, but for search 2019-02-07 15:44:15 +08:00
AllenFang
903dd2e5c8 rename listenerForPagination -> dataChangeListener 2019-02-07 15:34:03 +08:00
AllenFang
964faa53e3 fix wrong storyb code 2019-02-07 13:56:42 +08:00
AllenFang
8fb5364cc2 add story for #445 2019-02-06 22:56:53 +08:00
AllenFang
8b89b3de0e fix #445 2019-02-06 22:56:31 +08:00
AllenFang
4506a3dea2 add story for #778 2019-02-06 18:06:08 +08:00
AllenFang
ecea3efdaa fix #778 2019-02-06 18:05:53 +08:00
AllenFang
8bef7eb348 fix #672 2019-02-06 15:15:26 +08:00
AllenFang
408a004f58 Publish
- react-bootstrap-table2-example@1.0.15
 - react-bootstrap-table2-filter@1.1.2
 - react-bootstrap-table2-toolkit@1.2.0
 - react-bootstrap-table-next@2.1.0
2019-01-20 22:50:11 +08:00
Allen
bf46dfa026 Merge pull request #766 from react-bootstrap-table/develop
20190120 release
2019-01-20 22:47:34 +08:00
AllenFang
f1b39e3dd6 patch docs for clear search button 2019-01-20 17:20:41 +08:00
AllenFang
029cd3ab6f add story for #751 2019-01-20 17:16:44 +08:00
AllenFang
1c68892a7b fix #751 2019-01-20 17:15:54 +08:00
AllenFang
1bf3fa50db patch docs for table footer 2019-01-20 15:40:51 +08:00
AllenFang
9988e790c1 Merge branch 'osiux-table-footer' into develop 2019-01-20 14:39:26 +08:00
AllenFang
bb071c98f9 fix typo 2019-01-20 14:19:12 +08:00
AllenFang
9c5d8aac62 Merge branch 'table-footer' of https://github.com/osiux/react-bootstrap-table2 into osiux-table-footer 2019-01-20 14:11:13 +08:00
AllenFang
7c79b64985 enhance clear filter story 2019-01-20 14:07:05 +08:00
Martins Linde
16f89989f0 Allow null date when clearing date filter with getFilter function (#759)
* fix date filter's getFilter function to accept empty value for clearing

* syntax improvements from ESlint
2019-01-20 14:03:24 +08:00
Eduardo Reveles
58cfdacfd1 fix error 2019-01-16 00:11:27 -06:00
Eduardo Reveles
78d5164056 add more examples to storybook 2019-01-15 21:40:01 -06:00
Eduardo Reveles
bd2ce5abf0 Add table footer 2019-01-15 21:40:01 -06:00
AllenFang
416fcf08d4 Publish
- react-bootstrap-table2-example@1.0.14
 - react-bootstrap-table2-filter@1.1.1
 - react-bootstrap-table2-paginator@2.0.1
 - react-bootstrap-table2-toolkit@1.1.2
 - react-bootstrap-table-next@2.0.1
2019-01-06 17:06:20 +08:00
Allen
0c650c0682 Merge pull request #747 from react-bootstrap-table/develop
20190106 release
2019-01-06 17:03:34 +08:00
Allen
7d30804da9 fix #636 (#746) 2019-01-06 16:25:02 +08:00
Allen
782c630e58 fix #734, fix #620 (#745) 2019-01-06 14:47:22 +08:00
Allen
22cc79961f fix #715 (#743) 2019-01-05 20:45:18 +08:00
Allen
a83b3d0d78 fix #729 (#742) 2019-01-05 17:00:52 +08:00
Allen
340ddb8722 fix #719 (#741) 2019-01-05 16:09:12 +08:00
AllenFang
32e455e65f add remote standalone pagination story 2019-01-05 14:37:07 +08:00
65 changed files with 2913 additions and 222 deletions

View File

@@ -30,6 +30,14 @@ Available properties in a column object:
* [headerAttrs](#headerAttrs)
* [headerSortingClasses](#headerSortingClasses)
* [headerSortingStyle](#headerSortingStyle)
* [footer](#footer)
* [footerFormatter](#footerFormatter)
* [footerClasses](#footerClasses)
* [footerStyle](#footerStyle)
* [footerTitle](#footerTitle)
* [footerEvents](#footerEvents)
* [footerAlign](#footerAlign)
* [footerAttrs](#footerAttrs)
* [editable](#editable)
* [validator](#validator)
* [editCellStyle](#editCellStyle)
@@ -302,7 +310,7 @@ A new `Object` will be the result of element headerStyle.
A new `String` will be the result of element title.
## <a name='headerTitle'>column.headerTitle - [Bool | Function]</a>
`headerTitle` is only for the title on header column, default is disable. The usage almost same as [`column.title`](#title),
Configure the title on header column, default is disable. The usage almost same as [`column.title`](#title),
```js
{
@@ -518,6 +526,162 @@ const sortingHeaderStyle = {
};
```
### <a name='footer'>footer - [String | Function]</a>
Give a string to render the string value on the footer column.
```js
const columns = [{
dataField: 'id',
text: 'Product ID',
footerAlign: 'center',
footer: 'Footer 1'
}, .....];
```
This prop also accept a function:
```js
{
dataField: 'price',
text: 'Product Price',
footer: column => column.reduce((acc, item) => acc + item, 0)
}
```
## <a name='footerFormatter'>column.footerFormatter - [Function]</a>
`footerFormatter` allow you to customize the table footer column and only accept a callback function which take two arguments and a JSX/String are expected for return.
* `column`
* `columnIndex`
## <a name='footerClasses'>column.footerClasses - [String | Function]</a>
It's similar to [`column.classes`](#classes), `footerClasses` is available to have customized class on table footer column:
```js
{
// omit...
footerClasses: 'id-custom-cell'
}
```
Furthermore, it also accept a callback function which takes 2 arguments and a `String` is expect to return:
```js
{
footerClasses: function callback(column, colIndex) { ... }
}
```
**Parameters**
* `column`: The value of current column.
* `colIndex`: The index of the current `column` being processed in `BootstrapTable`.
## <a name='footerStyle'>column.footerStyle - [Object | Function]</a>
Customized the inline-style on table footer column:
```js
{
// omit...
footerStyle: { backgroundColor: 'green' }
}
```
Moreover, it also accept a callback function which takes **2** arguments and an `Object` is expect to return:
```js
{
footerStyle: function callback(column, colIndex) { ... }
}
```
**Parameters**
* `column`: The value of current column.
* `colIndex`: The index of the current `column` being processed in `BootstrapTable`.
## <a name='footerTitle'>column.footerTitle - [Bool | Function]</a>
Configure the title on footer column, default is disable. The usage is almost same as [`column.title`](#title),
```js
{
// omit...
footerTitle: true
}
```
It's also available to custom via a callback function:
```js
{
footerTitle: function callback(column, colIndex) { ... }
}
```
**Parameters**
* `column`: The value of current column.
* `colIndex`: The index of the current `column` being processed in `BootstrapTable`.
## <a name='footerEvents'>column.footerEvents - [Object]</a>
`footerEvents` same as [`column.events`](#events) but it is for footer column:
```js
{
// omit...
footerEvents: {
onClick: e => { ... }
}
}
```
## <a name='footerAlign'>column.footerAlign - [String | Function]</a>
It's almost same as [`column.align`](#align), but it's for the [CSS text-align](https://www.w3schools.com/cssref/pr_text_text-align.asp) on footer column.
```js
{
// omit...
footerAlign: 'center'
}
```
Also, you can custom the align by a callback function:
```js
{
// omit...
footerAlign: (column, colIndex) => {
// column is an object and perform itself
// return custom title here
}
}
```
**Parameters**
* `column`: The value of current column.
* `colIndex`: The index of the current `column` being processed in `BootstrapTable`.
## <a name='footerAttrs'>column.footerAttrs - [Object | Function]</a>
`footerAttrs` is similar to [`column.attrs`](#attrs) but it works for footer column.
```js
{
// omit...
footerAttrs: {
title: 'bar',
'data-test': 'foo'
}
}
```
Additionally, customize the header attributes by a **2** arguments callback function:
```js
{
// omit...
footerAttrs: (column, colIndex) => ({
// return customized HTML attribute here
})
}
```
**Parameters**
* `column`: The value of current column.
* `colIndex`: The index of the current `column` being processed in `BootstrapTable`.
## <a name='editable'>column.editable - [Bool | Function]</a>
`column.editable` default is true, means every column is editable if you configure [`cellEdit`](./README.md#cellEdit). But you can disable some columns editable via setting `false`.

View File

@@ -118,7 +118,7 @@ Remember to install [`react-bootstrap-table2-paginator`](https://www.npmjs.com/p
- [x] Custom search component and position
- [x] Custom search value
- [ ] Clear search
- [x] Clear search
- [ ] Multiple search
- [ ] Strict search

View File

@@ -15,6 +15,7 @@
* [showExpandColumn](#showExpandColumn)
* [onlyOneExpanding](#onlyOneExpanding)
* [expandByColumnOnly](#expandByColumnOnly)
* [expandColumnPosition](#expandColumnPosition)
* [expandColumnRenderer](#expandColumnRenderer)
* [expandHeaderColumnRenderer](#expandHeaderColumnRenderer)
@@ -153,3 +154,14 @@ const expandRow = {
expandByColumnOnly: true
};
```
### <a name='expandColumnPosition'>expandRow.expandColumnPosition - [String]</a>
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'
};
```

View File

@@ -1,13 +1,14 @@
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, { textFilter } from 'react-bootstrap-table2-filter';
import filterFactory, { textFilter, dateFilter } from 'react-bootstrap-table2-filter';
import Code from 'components/common/code-block';
import { productsGenerator } from 'utils/common';
import { stockGenerator } from 'utils/common';
const products = productsGenerator(8);
const products = stockGenerator(8);
let nameFilter;
let priceFilter;
let stockDateFilter;
const columns = [{
dataField: 'id',
@@ -22,25 +23,36 @@ const columns = [{
})
}, {
dataField: 'price',
text: 'Product Price',
text: 'Price',
filter: textFilter({
getFilter: (filter) => {
priceFilter = filter;
}
})
}, {
dataField: 'inStockDate',
text: 'InStock Date',
formatter: cell => cell.toString(),
filter: dateFilter({
getFilter: (filter) => {
stockDateFilter = filter;
}
})
}];
const handleClick = () => {
nameFilter('');
priceFilter('');
stockDateFilter();
};
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, { textFilter } from 'react-bootstrap-table2-filter';
import filterFactory, { textFilter, dateFilter } from 'react-bootstrap-table2-filter';
let nameFilter;
let priceFilter;
let stockDateFilter;
const columns = [{
dataField: 'id',
@@ -55,17 +67,27 @@ const columns = [{
})
}, {
dataField: 'price',
text: 'Product Price',
text: 'Price',
filter: textFilter({
getFilter: (filter) => {
priceFilter = filter;
}
})
}, {
dataField: 'inStockDate',
text: 'InStock Date',
formatter: cell => cell.toString(),
filter: dateFilter({
getFilter: (filter) => {
stockDateFilter = filter;
}
})
}];
const handleClick = () => {
nameFilter('');
priceFilter('');
stockDateFilter();
};
export default () => (

View File

@@ -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 (
<div>
<BootstrapTable
keyField="id"
data={ products }
columns={ columns }
filter={ filterFactory() }
/>
</div>
);
}
}
`;
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 (
<div>
<h2>Implement a eq filter on product price column</h2>
<BootstrapTable
keyField="id"
data={ products }
columns={ columns }
filter={ filterFactory() }
/>
<Code>{ sourceCode }</Code>
</div>
);
}
}

View File

@@ -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}\`)
})
}];
<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>
);

View File

@@ -0,0 +1,70 @@
/* eslint react/prop-types: 0 */
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, { textFilter } from 'react-bootstrap-table2-filter';
import { productsGenerator } from 'utils/common';
const ProductList = (props) => {
const columns = [
{
dataField: 'id',
text: 'Product ID'
},
{
dataField: 'name',
text: 'Product Name',
filter: textFilter({
defaultValue: '1'
})
},
{
dataField: 'price',
text: 'Product Price',
filter: textFilter()
}
];
return (
<div style={ { paddingTop: '20px' } }>
<h1 className="h2">Products</h1>
<BootstrapTable
keyField="id"
data={ props.products }
columns={ columns }
filter={ filterFactory() }
/>
</div>
);
};
export default class DataContainer extends React.Component {
state = {
products: productsGenerator(3)
};
loadData = () => {
this.setState({
products: productsGenerator(14)
});
}
render() {
return (
<div>
<button
onClick={ this.loadData }
style={ {
fontSize: '20px',
position: 'absolute',
left: '200px',
top: '40px'
} }
>
Load Data
</button>
<ProductList products={ this.state.products } />
</div>
);
}
}

View File

@@ -0,0 +1,80 @@
/* eslint react/prop-types: 0 */
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit';
import { productsGenerator } from 'utils/common';
const { SearchBar } = Search;
const ProductList = (props) => {
const columns = [
{
dataField: 'id',
text: 'Product ID'
},
{
dataField: 'name',
text: 'Product Name'
},
{
dataField: 'price',
text: 'Product Price'
}
];
return (
<div style={ { paddingTop: '20px' } }>
<h1 className="h2">Products</h1>
<ToolkitProvider
keyField="id"
data={ props.products }
columns={ columns }
search={ { defaultSearch: '2101' } }
>
{
toolkitprops => (
<div>
<SearchBar { ...toolkitprops.searchProps } />
<BootstrapTable
striped
hover
{ ...toolkitprops.baseProps }
/>
</div>
)
}
</ToolkitProvider>
</div>
);
};
export default class DataContainer extends React.Component {
state = {
products: productsGenerator(3)
};
loadData = () => {
this.setState({
products: productsGenerator(14)
});
}
render() {
return (
<div>
<button
onClick={ this.loadData }
style={ {
fontSize: '20px',
position: 'absolute',
left: '200px',
top: '40px'
} }
>
Load Data
</button>
<ProductList products={ this.state.products } />
</div>
);
}
}

View File

@@ -0,0 +1,68 @@
/* eslint react/prop-types: 0 */
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, { textFilter } from 'react-bootstrap-table2-filter';
import { productsGenerator } from 'utils/common';
const ProductList = (props) => {
const columns = [
{
dataField: 'id',
text: 'Product ID'
},
{
dataField: 'name',
text: 'Product Name',
filter: textFilter()
},
{
dataField: 'price',
text: 'Product Price',
filter: textFilter()
}
];
return (
<div style={ { paddingTop: '20px' } }>
<h1 className="h2">Products</h1>
<BootstrapTable
keyField="id"
data={ props.products }
columns={ columns }
filter={ filterFactory() }
/>
</div>
);
};
export default class DataContainer extends React.Component {
state = {
products: []
};
loadData = () => {
this.setState({
products: productsGenerator()
});
}
render() {
return (
<div>
<button
onClick={ this.loadData }
style={ {
fontSize: '20px',
position: 'absolute',
left: '200px',
top: '40px'
} }
>
Load Data
</button>
<ProductList products={ this.state.products } />
</div>
);
}
}

View File

@@ -0,0 +1,72 @@
/* eslint react/prop-types: 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 { productsGenerator } from 'utils/common';
const ProductList = (props) => {
const columns = [
{
dataField: 'id',
text: 'Product ID'
},
{
dataField: 'name',
text: 'Product Name',
filter: textFilter({
defaultValue: '6'
})
},
{
dataField: 'price',
text: 'Product Price',
filter: textFilter()
}
];
return (
<div style={ { paddingTop: '20px' } }>
<h1 className="h2">Products</h1>
<BootstrapTable
keyField="id"
data={ props.products }
columns={ columns }
filter={ filterFactory() }
pagination={ paginationFactory() }
/>
</div>
);
};
export default class DataContainer extends React.Component {
state = {
products: productsGenerator(60)
};
loadData = () => {
this.setState({
products: productsGenerator(14)
});
}
render() {
return (
<div>
<button
onClick={ this.loadData }
style={ {
fontSize: '20px',
position: 'absolute',
left: '200px',
top: '40px'
} }
>
Load Data
</button>
<ProductList products={ this.state.products } />
</div>
);
}
}

View File

@@ -0,0 +1,80 @@
/* eslint react/prop-types: 0 */
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit';
import { productsGenerator } from 'utils/common';
const { SearchBar } = Search;
const ProductList = (props) => {
const columns = [
{
dataField: 'id',
text: 'Product ID'
},
{
dataField: 'name',
text: 'Product Name'
},
{
dataField: 'price',
text: 'Product Price'
}
];
return (
<div style={ { paddingTop: '20px' } }>
<h1 className="h2">Products</h1>
<ToolkitProvider
keyField="id"
data={ props.products }
columns={ columns }
search
>
{
toolkitprops => (
<div>
<SearchBar { ...toolkitprops.searchProps } />
<BootstrapTable
striped
hover
{ ...toolkitprops.baseProps }
/>
</div>
)
}
</ToolkitProvider>
</div>
);
};
export default class DataContainer extends React.Component {
state = {
products: []
};
loadData = () => {
this.setState({
products: productsGenerator()
});
}
render() {
return (
<div>
<button
onClick={ this.loadData }
style={ {
fontSize: '20px',
position: 'absolute',
left: '200px',
top: '40px'
} }
>
Load Data
</button>
<ProductList products={ this.state.products } />
</div>
);
}
}

View File

@@ -0,0 +1,53 @@
/* eslint no-unused-vars: 0 */
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',
footerAlign: 'center',
footer: 'Footer 1'
}, {
dataField: 'name',
text: 'Product Name',
footerAlign: (column, colIndex) => 'right',
footer: 'Footer 2'
}, {
dataField: 'price',
text: 'Product Price',
footer: 'Footer 3'
}];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
const columns = [{
dataField: 'id',
text: 'Product ID',
footerAlign: 'center',
footer: 'Footer 1'
}, {
dataField: 'name',
text: 'Product Name',
footerAlign: (column, colIndex) => 'right',
footer: 'Footer 2'
}, {
dataField: 'price',
text: 'Product Price',
footer: 'Footer 3'
}];
<BootstrapTable keyField='id' data={ products } columns={ columns } />
`;
export default () => (
<div>
<BootstrapTable keyField="id" data={ products } columns={ columns } />
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -0,0 +1,54 @@
/* eslint no-unused-vars: 0 */
/* eslint no-alert: 0 */
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',
footer: 'Footer 1',
footerAttrs: { title: 'ID footer column' }
}, {
dataField: 'name',
text: 'Product Name',
footer: 'Footer 2',
footerAttrs: (column, colIndex) => ({ 'data-test': `customized data ${colIndex}` })
}, {
dataField: 'price',
text: 'Product Price',
footer: 'Footer 3'
}];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
const columns = [{
dataField: 'id',
text: 'Product ID',
footer: 'Footer 1',
footerAttrs: { title: 'ID footer column' }
}, {
dataField: 'name',
text: 'Product Name',
footer: 'Footer 2',
footerAttrs: (column, colIndex) => ({ 'data-test': \`customized data \${colIndex}\` })
}, {
dataField: 'price',
text: 'Product Price',
footer: 'Footer 3'
}];
<BootstrapTable keyField='id' data={ products } columns={ columns } />
`;
export default () => (
<div>
<BootstrapTable keyField="id" data={ products } columns={ columns } />
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -0,0 +1,60 @@
/* eslint no-unused-vars: 0 */
/* eslint no-alert: 0 */
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',
footer: 'Footer 1'
}, {
dataField: 'name',
text: 'Product Name',
footer: 'Footer 2',
footerClasses: 'demo-row-odd'
}, {
dataField: 'price',
text: 'Product Price',
footer: 'Footer 3',
footerClasses: (column, colIndex) => {
if (colIndex % 2 === 0) return 'demo-row-even';
return 'demo-row-odd';
}
}];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
const columns = [{
dataField: 'id',
text: 'Product ID',
footer: 'Footer 1'
}, {
dataField: 'name',
text: 'Product Name',
footer: 'Footer 2',
footerClasses: 'demo-row-odd'
}, {
dataField: 'price',
text: 'Product Price',
footer: 'Footer 3',
footerClasses: (column, colIndex) => {
if (colIndex % 2 === 0) return 'demo-row-even';
return 'demo-row-odd';
}
}];
<BootstrapTable keyField='id' data={ products } columns={ columns } />
`;
export default () => (
<div>
<BootstrapTable keyField="id" data={ products } columns={ columns } />
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -0,0 +1,56 @@
/* eslint no-unused-vars: 0 */
/* eslint no-alert: 0 */
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',
footerEvents: {
onClick: () => alert('Click on Product ID footer column')
},
footer: 'Footer 1'
}, {
dataField: 'name',
text: 'Product Name',
footer: 'Footer 2'
}, {
dataField: 'price',
text: 'Product Price',
footer: 'Footer 3'
}];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
const columns = [{
dataField: 'id',
text: 'Product ID',
footerEvents: {
onClick: () => alert('Click on Product ID footer column')
},
footer: 'Footer 1'
}, {
dataField: 'name',
text: 'Product Name',
footer: 'Footer 2'
}, {
dataField: 'price',
text: 'Product Price',
footer: 'Footer 3'
}];
<BootstrapTable keyField='id' data={ products } columns={ columns } />
`;
export default () => (
<div>
<BootstrapTable keyField="id" data={ products } columns={ columns } />
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -0,0 +1,67 @@
/* eslint no-unused-vars: 0 */
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();
function priceFormatter(column, colIndex) {
return (
<h5>
<strong>$$ {column.text} $$</strong>
</h5>
);
}
const columns = [
{
dataField: 'id',
text: 'Product ID',
footer: 'Footer 1'
},
{
dataField: 'name',
text: 'Product Name',
footer: 'Footer 2'
},
{
dataField: 'price',
text: 'Product Price',
footer: 'Footer 3',
footerFormatter: priceFormatter
}
];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
function priceFormatter(column, colIndex) {
return (
<h5><strong>$$ { column.text } $$</strong></h5>
);
}
const columns = [
// omit...
{
dataField: 'price',
text: 'Product Price',
footer: 'Footer 3',
footerFormatter: priceFormatter
}];
<BootstrapTable
keyField="id"
data={ products }
columns={ columns }
/>
`;
export default () => (
<div>
<BootstrapTable keyField="id" data={ products } columns={ columns } />
<Code>{sourceCode}</Code>
</div>
);

View File

@@ -0,0 +1,76 @@
/* eslint no-unused-vars: 0 */
/* eslint no-alert: 0 */
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',
footer: 'Footer 1'
}, {
dataField: 'name',
text: 'Product Name',
footer: 'Footer 2',
footerStyle: {
backgroundColor: '#c8e6c9'
}
}, {
dataField: 'price',
text: 'Product Price',
footer: 'Footer 3',
footerStyle: (column, colIndex) => {
if (colIndex % 2 === 0) {
return {
backgroundColor: '#81c784'
};
}
return {
backgroundColor: '#c8e6c9'
};
}
}];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
const columns = [{
dataField: 'id',
text: 'Product ID',
footer: 'Footer 1'
}, {
dataField: 'name',
text: 'Product Name',
footer: 'Footer 2',
footerStyle: {
backgroundColor: '#c8e6c9'
}
}, {
dataField: 'price',
text: 'Product Price',
footer: 'Footer 3',
footerStyle: (column, colIndex) => {
if (colIndex % 2 === 0) {
return {
backgroundColor: '#81c784'
};
}
return {
backgroundColor: '#c8e6c9'
};
}
}];
<BootstrapTable keyField='id' data={ products } columns={ columns } />
`;
export default () => (
<div>
<BootstrapTable keyField="id" data={ products } columns={ columns } />
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -0,0 +1,53 @@
/* eslint no-unused-vars: 0 */
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',
footerTitle: true,
footer: 'Footer 1'
}, {
dataField: 'name',
text: 'Product Name',
footerTitle: (column, colIndex) => `this is custom title for ${column.text}`,
footer: 'Footer 2'
}, {
dataField: 'price',
text: 'Product Price',
footer: 'Footer 3'
}];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
const columns = [{
dataField: 'id',
text: 'Product ID',
footerTitle: true,
footer: 'Footer 1'
}, {
dataField: 'name',
text: 'Product Name',
footerTitle: (column, colIndex) => \`this is custom title for \${column.text}\`,
footer: 'Footer 2'
}, {
dataField: 'price',
text: 'Product Price',
footer: 'Footer 3'
}];
<BootstrapTable keyField='id' data={ products } columns={ columns } />
`;
export default () => (
<div>
<BootstrapTable keyField="id" data={ products } columns={ columns } />
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -0,0 +1,61 @@
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',
footer: 'Footer 1'
},
{
dataField: 'name',
text: 'Product Name',
footer: 'Footer 2'
},
{
dataField: 'price',
text: 'Product Price',
footer: 'Footer 3'
}
];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
const columns = [
{
dataField: 'id',
text: 'Product ID',
footer: 'Footer 1'
},
{
dataField: 'name',
text: 'Product Name',
footer: 'Footer 2'
},
{
dataField: 'price',
text: 'Product Price',
footer: 'Footer 3'
}
];
<BootstrapTable
keyField="id"
data={ products }
columns={ columns }
footerClasses="footer-class"
/>
`;
export default () => (
<div>
<BootstrapTable keyField="id" data={ products } columns={ columns } footerClasses="footer-class" />
<Code>{sourceCode}</Code>
</div>
);

View File

@@ -0,0 +1,52 @@
/* eslint no-unused-vars: 0 */
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',
footer: 'Footer 1'
},
{
dataField: 'name',
text: 'Product Name',
footer: 'Footer 2'
},
{
dataField: 'price',
text: 'Product Price',
footer: columnData => columnData.reduce((acc, item) => acc + item, 0)
}
];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
const columns = [
// omit...
{
dataField: 'price',
text: 'Product Price',
footer: columnData => columnData.reduce((acc, item) => acc + item, 0)
}];
<BootstrapTable
keyField="id"
data={ products }
columns={ columns }
/>
`;
export default () => (
<div>
<BootstrapTable keyField="id" data={ products } columns={ columns } />
<Code>{sourceCode}</Code>
</div>
);

View File

@@ -0,0 +1,52 @@
/* eslint no-unused-vars: 0 */
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',
footer: 'Footer 1'
},
{
dataField: 'name',
text: 'Product Name',
footer: 'Footer 2'
},
{
dataField: 'price',
text: 'Product Price',
footer: 'Footer 3'
}
];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
const columns = [
// omit...
{
dataField: 'price',
text: 'Product Price',
footer: 'Footer 3'
}];
<BootstrapTable
keyField="id"
data={ products }
columns={ columns }
/>
`;
export default () => (
<div>
<BootstrapTable keyField="id" data={ products } columns={ columns } />
<Code>{sourceCode}</Code>
</div>
);

View File

@@ -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 }) => (
<div>
<PaginationListStandalone { ...paginationProps } />
<div>
<div>
<BootstrapTable
striped
hover
keyField="id"
data={ products }
columns={ columns }
filter={ filterFactory() }
cellEdit={ cellEditFactory() }
{ ...paginationTableProps }
/>
</div>
</div>
<PaginationListStandalone { ...paginationProps } />
</div>
);
return (
<div>
<h2>PaginationProvider will care the data size change. You dont do anything</h2>
<PaginationProvider
pagination={
paginationFactory(options)
}
>
{ contentTable }
</PaginationProvider>
<Code>{ sourceCode }</Code>
</div >
);
}
}
`;
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 }) => (
<div>
<PaginationListStandalone { ...paginationProps } />
<ToolkitProvider
keyField="id"
columns={ columns }
data={ products }
search
>
{
toolkitprops => (
<div>
<SearchBar { ...toolkitprops.searchProps } />
<BootstrapTable
striped
hover
{ ...toolkitprops.baseProps }
{ ...paginationTableProps }
/>
</div>
)
}
</ToolkitProvider>
<PaginationListStandalone { ...paginationProps } />
</div>
);
return (
<div>
<h2>PaginationProvider will care the data size change. You dont do anything</h2>
<PaginationProvider
pagination={
paginationFactory(options)
}
>
{ contentTable }
</PaginationProvider>
<Code>{ sourceCode }</Code>
</div >
);
}
}

View File

@@ -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 }) => (
<div>
<PaginationListStandalone { ...paginationProps } />
<div>
<div>
<BootstrapTable
striped
hover
keyField="id"
data={ products }
columns={ columns }
filter={ filterFactory() }
cellEdit={ cellEditFactory() }
{ ...paginationTableProps }
/>
</div>
</div>
<PaginationListStandalone { ...paginationProps } />
</div>
);
return (
<div>
<h2>PaginationProvider will care the data size change. You dont do anything</h2>
<PaginationProvider
pagination={
paginationFactory(options)
}
>
{ contentTable }
</PaginationProvider>
<Code>{ sourceCode }</Code>
</div >
);
}
}
`;
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 }) => (
<div>
<PaginationListStandalone { ...paginationProps } />
<div>
<div>
<BootstrapTable
striped
hover
keyField="id"
data={ products }
columns={ columns }
filter={ filterFactory() }
{ ...paginationTableProps }
/>
</div>
</div>
<PaginationListStandalone { ...paginationProps } />
</div>
);
return (
<div>
<h2>PaginationProvider will care the data size change. You dont do anything</h2>
<PaginationProvider
pagination={
paginationFactory(options)
}
>
{ contentTable }
</PaginationProvider>
<Code>{ sourceCode }</Code>
</div >
);
}
}

View File

@@ -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 }) => (
<div>
<PaginationProvider
pagination={
paginationFactory({
custom: true,
page,
sizePerPage,
totalSize
})
}
>
{
({
paginationProps,
paginationTableProps
}) => (
<div>
<div>
<p>Current Page: { paginationProps.page }</p>
<p>Current SizePerPage: { paginationProps.sizePerPage }</p>
</div>
<div>
<PaginationListStandalone
{ ...paginationProps }
/>
</div>
<BootstrapTable
remote
keyField="id"
data={ data }
columns={ columns }
onTableChange={ onTableChange }
{ ...paginationTableProps }
/>
</div>
)
}
</PaginationProvider>
</div>
);
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 (
<RemotePagination
data={ data }
page={ page }
sizePerPage={ sizePerPage }
totalSize={ products.length }
onTableChange={ this.handleTableChange }
/>
);
}
}
`;
const RemotePagination = ({ data, page, sizePerPage, onTableChange, totalSize }) => (
<div>
<PaginationProvider
pagination={
paginationFactory({
custom: true,
page,
sizePerPage,
totalSize
})
}
>
{
({
paginationProps,
paginationTableProps
}) => (
<div>
<div>
<p>Current Page: { paginationProps.page }</p>
<p>Current SizePerPage: { paginationProps.sizePerPage }</p>
</div>
<div>
<PaginationListStandalone
{ ...paginationProps }
/>
</div>
<BootstrapTable
remote
keyField="id"
data={ data }
columns={ columns }
onTableChange={ onTableChange }
{ ...paginationTableProps }
/>
</div>
)
}
</PaginationProvider>
<Code>{ sourceCode }</Code>
</div>
);
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 (
<RemotePagination
data={ data }
page={ page }
sizePerPage={ sizePerPage }
totalSize={ products.length }
onTableChange={ this.handleTableChange }
/>
);
}
}
export default Container;

View File

@@ -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 => (
<div>
<p>{ `This Expand row is belong to rowKey ${row.id}` }</p>
<p>You can render anything here, also you can add additional data on every row object</p>
<p>expandRow.renderer callback will pass the origin row object to you</p>
</div>
),
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 => (
<div>
<p>{ \`This Expand row is belong to rowKey $\{row.id}\` }</p>
<p>You can render anything here, also you can add additional data on every row object</p>
<p>expandRow.renderer callback will pass the origin row object to you</p>
</div>
),
showExpandColumn: true,
expandColumnPosition: 'right'
};
<BootstrapTable
keyField='id'
data={ products }
columns={ columns }
expandRow={ expandRow }
/>
`;
export default () => (
<div>
<BootstrapTable
keyField="id"
data={ products }
columns={ columns }
expandRow={ expandRow }
/>
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -0,0 +1,86 @@
/* eslint react/prop-types: 0 */
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit';
import Code from 'components/common/code-block';
import { productsGenerator } from 'utils/common';
const { SearchBar, ClearSearchButton } = Search;
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';
import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit';
const { SearchBar, ClearSearchButton } = Search;
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'price',
text: 'Product Price'
}];
<ToolkitProvider
keyField="id"
data={ products }
columns={ columns }
search
>
{
props => (
<div>
<h3>Input something at below input field:</h3>
<SearchBar { ...props.searchProps } />
<ClearSearchButton { ...props.searchProps } />
<hr />
<BootstrapTable
{ ...props.baseProps }
/>
</div>
)
}
</ToolkitProvider>
`;
export default () => (
<div>
<ToolkitProvider
keyField="id"
data={ products }
columns={ columns }
search
>
{
props => (
<div>
<h3>Input something at below input field:</h3>
<SearchBar { ...props.searchProps } />
<ClearSearchButton { ...props.searchProps } />
<hr />
<BootstrapTable
{ ...props.baseProps }
/>
</div>
)
}
</ToolkitProvider>
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -1,6 +1,6 @@
{
"name": "react-bootstrap-table2-example",
"version": "1.0.13",
"version": "1.0.17",
"description": "",
"main": "index.js",
"private": true,

View File

@@ -70,6 +70,7 @@ export const stockGenerator = (quantity = 5) =>
Array.from({ length: quantity }, (value, index) => ({
id: index,
name: `Todo item ${index}`,
price: Math.floor((Math.random() * 2) + 1),
inStockDate:
new Date(startDate.getTime() + Math.random() * (endDate.getTime() - startDate.getTime()))
}));

View File

@@ -45,6 +45,18 @@ 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';
// footer
import SimpleFooter from 'examples/footer/simple-footer';
import FunctionFooter from 'examples/footer/function-footer';
import FooterClassTable from 'examples/footer/footer-class-table';
import FooterColumnFormatTable from 'examples/footer/column-format-table';
import FooterColumnAlignTable from 'examples/footer/column-align-table';
import FooterColumnTitleTable from 'examples/footer/column-title-table.js';
import FooterColumnEventsTable from 'examples/footer/column-event-table.js';
import FooterColumnClassTable from 'examples/footer/column-class-table.js';
import FooterColumnStyleTable from 'examples/footer/column-style-table.js';
import FooterColumnAttrsTable from 'examples/footer/column-attrs-table.js';
// column filter
import TextFilter from 'examples/column-filter/text-filter';
import TextFilterWithDefaultValue from 'examples/column-filter/text-filter-default-value';
@@ -74,6 +86,8 @@ 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';
import CustomFilterLogic from 'examples/column-filter/custom-filter-logic';
// work on rows
import RowStyleTable from 'examples/rows/row-style';
@@ -141,6 +155,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
@@ -154,9 +169,13 @@ 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';
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';
import ClearSearchButton from 'examples/search/clear-search-button';
import DefaultSearch from 'examples/search/default-search';
import DefaultCustomSearch from 'examples/search/default-custom-search';
import FullyCustomSearch from 'examples/search/fully-custom-search';
@@ -186,6 +205,13 @@ import RemoteSearch from 'examples/remote/remote-search';
import RemoteCellEdit from 'examples/remote/remote-celledit';
import RemoteAll from 'examples/remote/remote-all';
// data
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';
import LoadDataWithDefaultSearch from 'examples/data/load-data-on-the-fly-with-default-search';
import LoadDataWithPaginationAndFilter from 'examples/data/load-data-on-the-fly-with-pagination-and-filter';
// css style
import 'stories/stylesheet/tomorrow.min.css';
import 'stories/stylesheet/storybook.scss';
@@ -196,8 +222,7 @@ import '../../react-bootstrap-table2-filter/style/react-bootstrap-table2-filter.
// import bootstrap style by given version
import bootstrapStyle, { BOOTSTRAP_VERSION } from './bootstrap-style';
storiesOf('Welcome', module)
.add('react bootstrap table 2 ', () => <Welcome />);
storiesOf('Welcome', module).add('react bootstrap table 2 ', () => <Welcome />);
storiesOf('Basic Table', module)
.addDecorator(bootstrapStyle())
@@ -273,7 +298,9 @@ storiesOf('Column Filter', module)
.add('Custom Filter', () => <CustomFilter />)
.add('Advance Custom Filter', () => <AdvanceCustomFilter />)
.add('Preserved Option Order on Select Filter', () => <SelectFilterWithPreservedOptionsOrder />)
.add('Clear All Filters', () => <ClearAllFilters />);
.add('Clear All Filters', () => <ClearAllFilters />)
.add('Filter Hooks', () => <FilterHooks />)
.add('Implement custom filter logic', () => <CustomFilterLogic />);
storiesOf('Work on Rows', module)
.addDecorator(bootstrapStyle())
@@ -282,6 +309,19 @@ storiesOf('Work on Rows', module)
.add('Hide Rows', () => <RowHiddenTable />)
.add('Row Event', () => <RowEventTable />);
storiesOf('Footer', module)
.addDecorator(bootstrapStyle())
.add('Simple Footer', () => <SimpleFooter />)
.add('Function Footer', () => <FunctionFooter />)
.add('Column Formatter', () => <FooterColumnFormatTable />)
.add('Column Align', () => <FooterColumnAlignTable />)
.add('Column Title', () => <FooterColumnTitleTable />)
.add('Column Events', () => <FooterColumnEventsTable />)
.add('Customize Column Class', () => <FooterColumnClassTable />)
.add('Customize Column Style', () => <FooterColumnStyleTable />)
.add('Customize Column HTML attribute', () => <FooterColumnAttrsTable />)
.add('Footer Class', () => <FooterClassTable />);
storiesOf('Sort Table', module)
.addDecorator(bootstrapStyle())
.add('Enable Sort', () => <EnableSortTable />)
@@ -346,6 +386,7 @@ storiesOf('Row Expand', module)
.add('Only Expand by Indicator', () => <OnlyExpandByColumn />)
.add('Expand Only One Row at The Same Time', () => <ExpandOnlyOne />)
.add('Custom Expand Indicator', () => <CustomExpandColumn />)
.add('Expand Column Position', () => <ExpandColumnPosition />)
.add('Expand Hooks', () => <ExpandHooks />);
storiesOf('Pagination', module)
@@ -359,11 +400,15 @@ storiesOf('Pagination', module)
.add('Custom SizePerPage', () => <CustomSizePerPageTable />)
.add('Standalone Pagination List', () => <StandalonePaginationList />)
.add('Standalone SizePerPage Dropdown', () => <StandaloneSizePerPage />)
.add('Fully Custom Pagination', () => <FullyCustomPaginationTable />);
.add('Fully Custom Pagination', () => <FullyCustomPaginationTable />)
.add('Remote Fully Custom Pagination', () => <RemoteStandalonePaginationTable />)
.add('Custom Pagination with Filter', () => <CustomePaginationWithFilter />)
.add('Custom Pagination with Search', () => <CustomePaginationWithSearch />);
storiesOf('Table Search', module)
.addDecorator(bootstrapStyle())
.add('Basic Search Table', () => <SearchTable />)
.add('Clear Search Button', () => <ClearSearchButton />)
.add('Default Search Table', () => <DefaultSearch />)
.add('Default Custom Search', () => <DefaultCustomSearch />)
.add('Fully Custom Search', () => <FullyCustomSearch />)
@@ -395,3 +440,11 @@ storiesOf('Remote', module)
.add('Remote Search', () => <RemoteSearch />)
.add('Remote Cell Editing', () => <RemoteCellEdit />)
.add('Remote All', () => <RemoteAll />);
storiesOf('Data', module)
.addDecorator(bootstrapStyle())
.add('Load data with Filter', () => <LoadDataWithFilter />)
.add('Load data with Default Filter', () => <LoadDataWithDefaultFilter />)
.add('Load data with Search', () => <LoadDataWithSearch />)
.add('Load data with Default Search', () => <LoadDataWithDefaultSearch />)
.add('Load data with Filter and Pagination', () => <LoadDataWithPaginationAndFilter />);

View File

@@ -13,4 +13,8 @@
.header-class {
background-color: $green-lighten-4;
}
.footer-class {
background-color: $green-lighten-4;
}

View File

@@ -1,6 +1,6 @@
{
"name": "react-bootstrap-table2-filter",
"version": "1.1.0",
"version": "1.1.4",
"description": "it's a column filter addon for react-bootstrap-table2",
"main": "./lib/index.js",
"repository": {

View File

@@ -42,10 +42,11 @@ class DateFilter extends Component {
// export onFilter function to allow users to access
if (getFilter) {
getFilter((filterVal) => {
this.dateFilterComparator.value = filterVal.comparator;
this.inputDate.value = dateParser(filterVal.date);
const nullableFilterVal = filterVal || { date: null, comparator: null };
this.dateFilterComparator.value = nullableFilterVal.comparator;
this.inputDate.value = nullableFilterVal.date ? dateParser(nullableFilterVal.date) : null;
this.applyFilter(filterVal.date, filterVal.comparator);
this.applyFilter(nullableFilterVal.date, nullableFilterVal.comparator);
});
}
}

View File

@@ -17,14 +17,19 @@ 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) {
super(props);
this.currFilters = {};
this.onFilter = this.onFilter.bind(this);
this.doFilter = this.doFilter.bind(this);
this.onExternalFilter = this.onExternalFilter.bind(this);
this.state = {
data: props.data
};
}
componentDidMount() {
@@ -33,6 +38,16 @@ export default (
}
}
componentWillReceiveProps(nextProps) {
let nextData = nextProps.data;
if (!isRemoteFiltering() && !_.isEqual(nextProps.data, this.props.data)) {
nextData = this.doFilter(nextProps);
}
this.setState({
data: nextData
});
}
onFilter(column, filterType, initialize = false) {
return (filterVal) => {
// watch out here if migration to context API, #334
@@ -64,7 +79,13 @@ export default (
return;
}
this.forceUpdate();
let result;
if (filter.props.onFilter) {
result = filter.props.onFilter(filterVal);
}
result = this.doFilter(this.props, result);
this.setState({ data: result });
};
}
@@ -74,14 +95,21 @@ export default (
};
}
render() {
let { data } = this.props;
if (!isRemoteFiltering()) {
data = filters(data, this.props.columns, _)(this.currFilters);
doFilter(props, customResult) {
let result = customResult;
const { dataChangeListener, data, columns } = props;
result = result || filters(data, columns, _)(this.currFilters);
if (dataChangeListener) {
dataChangeListener.emit('filterChanged', result.length);
}
return result;
}
render() {
return (
<FilterContext.Provider value={ {
data,
data: this.state.data,
onFilter: this.onFilter,
onExternalFilter: this.onExternalFilter
} }

View File

@@ -44,7 +44,9 @@ describe('FilterContext', () => {
const handleFilterChange = jest.fn();
function shallowContext(
enableRemote = false
enableRemote = false,
tableColumns = columns,
dataChangeListener,
) {
mockBase.mockReset();
handleFilterChange.mockReset();
@@ -56,8 +58,9 @@ describe('FilterContext', () => {
return (
<FilterContext.Provider
columns={ columns }
columns={ tableColumns }
data={ data }
dataChangeListener={ dataChangeListener }
>
<FilterContext.Consumer>
{
@@ -225,6 +228,84 @@ 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('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());

View File

@@ -1,6 +1,6 @@
{
"name": "react-bootstrap-table2-paginator",
"version": "2.0.0",
"version": "2.0.2",
"description": "it's the pagination addon for react-bootstrap-table2",
"main": "./lib/index.js",
"repository": {

View File

@@ -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

View File

@@ -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
}
} }
>

View File

@@ -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)
}
});
});

View File

@@ -95,8 +95,34 @@ If you want to search on the formatted data, you are supposed to enable this pro
</ToolkitProvider>
```
### Clear Search Button
We have a built-in clear search function which allow user clear search status via clicking button:
```js
import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit';
const { SearchBar, ClearSearchButton } = Search;
<ToolkitProvider
keyField="id"
data={ products }
columns={ columns }
search
>
{
props => (
<div>
<SearchBar { ...props.searchProps } />
<ClearSearchButton { ...props.searchProps } />
....
</div>
)
}
</ToolkitProvider>
```
## Export CSV
There are two step to enable the export CSV functionality:
There are two steps to enable the export CSV functionality:
1. Give `exportCSV` prop as `true` on `ToolkitProvider`.
2. Render `ExportCSVButton` with `csvProps`. The position of `ExportCSVButton` is depends on you.

View File

@@ -46,6 +46,7 @@ class ToolkitProvider extends statelessDrcorator(React.Component) {
searchText: typeof props.search === 'object' ? (props.search.defaultSearch || '') : ''
};
this._ = null;
this.onClear = this.onClear.bind(this);
this.onSearch = this.onSearch.bind(this);
this.setDependencyModules = this.setDependencyModules.bind(this);
}
@@ -56,6 +57,10 @@ class ToolkitProvider extends statelessDrcorator(React.Component) {
}
}
onClear() {
this.setState({ searchText: '' });
}
/**
*
* @param {*} _
@@ -87,7 +92,8 @@ class ToolkitProvider extends statelessDrcorator(React.Component) {
<ToolkitContext.Provider value={ {
searchProps: {
searchText: this.state.searchText,
onSearch: this.onSearch
onSearch: this.onSearch,
onClear: this.onClear
},
csvProps: {
onExport: this.handleExportCSV

View File

@@ -1,6 +1,6 @@
{
"name": "react-bootstrap-table2-toolkit",
"version": "1.1.1",
"version": "1.2.2",
"description": "The toolkit for react-bootstrap-table2",
"main": "./lib/index.js",
"repository": {

View File

@@ -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);

View File

@@ -26,33 +26,51 @@ const handleDebounce = (func, wait, immediate) => {
};
};
const SearchBar = ({
delay,
onSearch,
className,
style,
placeholder,
searchText,
...rest
}) => {
let input;
const debounceCallback = handleDebounce(() => {
onSearch(input.value);
}, delay);
class SearchBar extends React.Component {
constructor(props) {
super(props);
this.state = {
value: props.searchText
};
}
return (
<input
ref={ n => input = n }
type="text"
style={ style }
onKeyUp={ () => debounceCallback() }
className={ `form-control ${className}` }
defaultValue={ searchText }
placeholder={ placeholder || SearchBar.defaultProps.placeholder }
{ ...rest }
/>
);
};
componentWillReceiveProps(nextProps) {
this.setState({ value: nextProps.searchText });
}
onChangeValue = (e) => {
this.setState({ value: e.target.value });
}
onKeyup = () => {
const { delay, onSearch } = this.props;
const debounceCallback = handleDebounce(() => {
onSearch(this.input.value);
}, delay);
debounceCallback();
}
render() {
const {
className,
style,
placeholder
} = this.props;
return (
<input
ref={ n => 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 }
/>
);
}
}
SearchBar.propTypes = {
onSearch: PropTypes.func.isRequired,

View File

@@ -0,0 +1,20 @@
import React from 'react';
import PropTypes from 'prop-types';
const ClearButton = ({
onClear,
text
}) => (
<button className="btn btn-default" onClick={ onClear }>{ text }</button>
);
ClearButton.propTypes = {
onClear: PropTypes.func.isRequired,
text: PropTypes.string
};
ClearButton.defaultProps = {
text: 'Clear'
};
export default ClearButton;

View File

@@ -1,6 +1,8 @@
/* eslint react/prop-types: 0 */
/* eslint react/require-default-props: 0 */
/* eslint no-continue: 0 */
/* eslint no-lonely-if: 0 */
/* eslint class-methods-use-this: 0 */
import React from 'react';
import PropTypes from 'prop-types';
@@ -17,36 +19,55 @@ 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);
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);
this.triggerListener(result);
this.setState({
data: result
});
}
} else {
if (isRemoteSearch()) {
this.setState({ data: nextProps.data });
} else if (!_.isEqual(nextProps.data, this.props.data)) {
const result = this.search(nextProps);
this.triggerListener(result);
this.setState({
data: result
});
}
}
}
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(props) {
const { data, columns } = props;
const searchText = props.searchText.toLowerCase();
return data.filter((row, ridx) => {
for (let cidx = 0; cidx < columns.length; cidx += 1) {
const column = columns[cidx];
@@ -69,9 +90,8 @@ export default (options = {
}
render() {
const data = this.search();
return (
<SearchContext.Provider value={ { data } }>
<SearchContext.Provider value={ { data: this.state.data } }>
{ this.props.children }
</SearchContext.Provider>
);

View File

@@ -1,3 +1,4 @@
import SearchBar from './SearchBar';
import ClearSearchButton from './clear-button';
export default { SearchBar };
export default { SearchBar, ClearSearchButton };

View File

@@ -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;
}
};

View File

@@ -1,6 +1,6 @@
{
"name": "react-bootstrap-table-next",
"version": "2.0.0",
"version": "2.1.2",
"description": "Next generation of react-bootstrap-table",
"main": "./lib/index.js",
"repository": {

View File

@@ -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 = <RowSection content={ indication } colSpan={ visibleColumnSize } />;
} 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 <RowComponent { ...baseRowProps } />;
return <this.RowComponent { ...baseRowProps } />;
});
}

View File

@@ -7,20 +7,19 @@ import cs from 'classnames';
import Header from './header';
import Caption from './caption';
import Body from './body';
import Footer from './footer';
import PropsBaseResolver from './props-resolver';
import Const from './const';
import _ from './utils';
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();
}
@@ -66,9 +65,11 @@ class BootstrapTable extends PropsBaseResolver(Component) {
'table-striped': striped,
'table-hover': hover,
'table-bordered': bordered,
[(bootstrap4 ? 'table-sm' : 'table-condensed')]: condensed
[bootstrap4 ? 'table-sm' : 'table-condensed']: condensed
}, classes);
const hasFooter = _.filter(columns, col => _.has(col, 'footer')).length > 0;
const tableCaption = (caption && <Caption>{ caption }</Caption>);
return (
@@ -101,6 +102,15 @@ class BootstrapTable extends PropsBaseResolver(Component) {
rowClasses={ rowClasses }
rowEvents={ rowEvents }
/>
{hasFooter && (
<Footer
data={ this.getData() }
columns={ columns }
selectRow={ selectRow }
expandRow={ expandRow }
className={ this.props.footerClasses }
/>
)}
</table>
</div>
);
@@ -161,12 +171,17 @@ 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,
rowClasses: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
headerClasses: PropTypes.string,
footerClasses: PropTypes.string,
defaultSorted: PropTypes.arrayOf(PropTypes.shape({
dataField: PropTypes.string.isRequired,
order: PropTypes.oneOf([Const.SORT_DESC, Const.SORT_ASC]).isRequired

View File

@@ -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'
};

View File

@@ -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);
@@ -208,6 +217,7 @@ const withContext = Base =>
ref={ n => this.searchContext = n }
data={ rootProps.getData(filterProps) }
searchText={ this.props.search.searchText }
dataChangeListener={ this.props.dataChangeListener }
>
<this.SearchContext.Consumer>
{
@@ -228,6 +238,7 @@ const withContext = Base =>
{ ...baseProps }
ref={ n => this.filterContext = n }
data={ rootProps.getData() }
dataChangeListener={ this.props.dataChangeListener }
>
<this.FilterContext.Consumer>
{
@@ -255,9 +266,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();

View File

@@ -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) {

View File

@@ -0,0 +1,64 @@
/* eslint react/require-default-props: 0 */
import React from 'react';
import cs from 'classnames';
import PropTypes from 'prop-types';
import _ from './utils';
const FooterCell = (props) => {
const { index, column, columnData } = props;
const {
footer,
footerTitle,
footerAlign,
footerFormatter,
footerEvents,
footerClasses,
footerStyle,
footerAttrs
} = column;
const cellAttrs = {
...(_.isFunction(footerAttrs) ? footerAttrs(column, index) : footerAttrs),
...footerEvents
};
let text = '';
if (_.isString(footer)) {
text = footer;
} else if (_.isFunction(footer)) {
text = footer(columnData, column, index);
}
let cellStyle = {};
const cellClasses = _.isFunction(footerClasses) ? footerClasses(column, index) : footerClasses;
if (footerStyle) {
cellStyle = _.isFunction(footerStyle) ? footerStyle(column, index) : footerStyle;
cellStyle = cellStyle ? { ...cellStyle } : cellStyle;
}
if (footerTitle) {
cellAttrs.title = _.isFunction(footerTitle) ? footerTitle(column, index) : text;
}
if (footerAlign) {
cellStyle.textAlign = _.isFunction(footerAlign) ? footerAlign(column, index) : footerAlign;
}
if (cellClasses) cellAttrs.className = cs(cellAttrs.className, cellClasses);
if (!_.isEmptyObject(cellStyle)) cellAttrs.style = cellStyle;
const children = footerFormatter ? footerFormatter(column, index) : text;
return React.createElement('th', cellAttrs, children);
};
FooterCell.propTypes = {
columnData: PropTypes.array,
index: PropTypes.number,
column: PropTypes.object
};
export default FooterCell;

View File

@@ -0,0 +1,64 @@
/* eslint react/require-default-props: 0 */
import React from 'react';
import PropTypes from 'prop-types';
import Const from './const';
import FooterCell from './footer-cell';
import _ from './utils';
const Footer = (props) => {
const { data, className, columns, selectRow, expandRow } = props;
const SelectionFooterCellComp = () => <th />;
const ExpansionFooterCellComp = () => <th />;
const isRenderExpandColumnInLeft = (
expandColumnPosition = Const.INDICATOR_POSITION_LEFT
) => expandColumnPosition === Const.INDICATOR_POSITION_LEFT;
const childrens = columns.map((column, i) => {
if (column.footer === undefined || column.footer === null || column.hidden) {
return false;
}
const columnData = _.pluck(data, column.dataField);
return (
<FooterCell
index={ i }
key={ column.dataField }
column={ column }
columnData={ columnData }
/>
);
});
if (selectRow && selectRow.hideSelectColumn !== true) {
childrens.unshift(<SelectionFooterCellComp key="selection" />);
}
if (expandRow.showExpandColumn) {
if (isRenderExpandColumnInLeft(expandRow.expandColumnPosition)) {
childrens.unshift(<ExpansionFooterCellComp key="expansion" />);
} else {
childrens.push(<ExpansionFooterCellComp key="expansion" />);
}
}
return (
<tfoot>
<tr className={ className }>
{ childrens }
</tr>
</tfoot>
);
};
Footer.propTypes = {
data: PropTypes.array,
className: PropTypes.string,
columns: PropTypes.array,
selectRow: PropTypes.object,
expandRow: PropTypes.object
};
export default Footer;

View File

@@ -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 (
<HeaderCell
index={ i }
key={ column.dataField }
column={ column }
onSort={ onSort }
sorting={ currSort }
onFilter={ onFilter }
onExternalFilter={ onExternalFilter }
sortOrder={ sortOrder }
isLastSorting={ isLastSorting }
/>);
}
return false;
})
];
if (!selectRow.hideSelectColumn) {
childrens.unshift(<SelectionHeaderCellComp key="selection" />);
}
if (expandRow.showExpandColumn) {
if (isRenderExpandColumnInLeft(expandRow.expandColumnPosition)) {
childrens.unshift(<ExpansionHeaderCellComp key="expansion" />);
} else {
childrens.push(<ExpansionHeaderCellComp key="expansion" />);
}
}
return (
<thead>
<tr className={ className }>
<ExpansionHeaderCellComp />
{
!selectRow.hideSelectColumn ?
<SelectionHeaderCellComp /> : null
}
{
columns.map((column, i) => {
if (!column.hidden) {
const currSort = column.dataField === sortField;
const isLastSorting = column.dataField === sortField;
return (
<HeaderCell
index={ i }
key={ column.dataField }
column={ column }
onSort={ onSort }
sorting={ currSort }
onFilter={ onFilter }
onExternalFilter={ onExternalFilter }
sortOrder={ sortOrder }
isLastSorting={ isLastSorting }
/>);
}
return false;
})
}
{ childrens }
</tr>
</thead>
);

View File

@@ -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 = [(
<RowPureContent
key="row"
row={ row }
columns={ columns }
keyField={ keyField }
rowIndex={ rowIndex }
shouldUpdate={ this.shouldUpdateRowContent }
tabIndexStart={ tabIndexCell ? tabIndexStart : -1 }
{ ...rest }
/>
)];
if (!hideSelectColumn) {
childrens.unshift((
<SelectionCell
{ ...selectRow }
key="selection-cell"
rowKey={ key }
rowIndex={ rowIndex }
selected={ selected }
disabled={ !selectable }
tabIndex={ tabIndexCell ? tabIndexStart++ : -1 }
/>
));
}
if (showExpandColumn) {
const expandCell = (
<ExpandCell
{ ...expandRow }
key="expand-cell"
rowKey={ key }
rowIndex={ rowIndex }
expanded={ expanded }
expandable={ expandable }
tabIndex={ tabIndexCell ? tabIndexStart++ : -1 }
/>
);
if (this.isRenderExpandColumnInLeft(expandColumnPosition)) {
childrens.unshift(expandCell);
} else {
childrens.push(expandCell);
}
}
return (
<tr
style={ style }
className={ className }
{ ...newAttrs }
>
{
showExpandColumn ? (
<ExpandCell
{ ...expandRow }
rowKey={ key }
rowIndex={ rowIndex }
expanded={ expanded }
expandable={ expandable }
tabIndex={ tabIndexCell ? tabIndexStart++ : -1 }
/>
) : null
}
{
!hideSelectColumn
? (
<SelectionCell
{ ...selectRow }
rowKey={ key }
rowIndex={ rowIndex }
selected={ selected }
disabled={ !selectable }
tabIndex={ tabIndexCell ? tabIndexStart++ : -1 }
/>
)
: null
}
<RowPureContent
row={ row }
columns={ columns }
keyField={ keyField }
rowIndex={ rowIndex }
shouldUpdate={ this.shouldUpdateRowContent }
tabIndexStart={ tabIndexCell ? tabIndexStart : -1 }
{ ...rest }
/>
{ childrens }
</tr>
);
}

View File

@@ -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 ||

View File

@@ -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);

View File

@@ -60,26 +60,6 @@ describe('BootstrapTable', () => {
});
});
describe('when props.registerExposedAPI is defined', () => {
const registerExposedAPI = jest.fn();
beforeEach(() => {
registerExposedAPI.mockClear();
wrapper = shallow(
<BootstrapTable
keyField="id"
columns={ columns }
data={ data }
registerExposedAPI={ registerExposedAPI }
/>
);
});
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';

View File

@@ -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(
<BootstrapTable
keyField={ keyField }
data={ data }
columns={ columns }
pagination={ paginator }
registerExposedAPI={ registerExposedAPI }
/>
);
wrapper.render();
});
it('should call props.registerExposedAPI correctly', () => {
expect(registerExposedAPI).toHaveBeenCalledTimes(1);
});
});
});

View File

@@ -0,0 +1,124 @@
/* eslint no-unused-vars: 0 */
import 'jsdom-global/register';
import React from 'react';
import { shallow, render } from 'enzyme';
import Const from '../src/const';
import Footer from '../src/footer';
import FooterCell from '../src/footer-cell';
describe('Footer', () => {
let wrapper;
const columns = [
{
dataField: 'id',
text: 'ID',
footer: 'Footer 1'
},
{
dataField: 'name',
text: 'Name',
footer: (columnData, column) => 'Footer 2'
}
];
const data = [
{
id: 1,
name: 'A'
},
{
id: 2,
name: 'B'
}
];
const selectRow = {
mode: Const.ROW_SELECT_DISABLED,
selected: [],
hideSelectColumn: true
};
const expandRow = {
renderer: undefined,
expanded: [],
nonExpandable: []
};
const keyField = 'id';
describe('simplest footer', () => {
beforeEach(() => {
wrapper = shallow(
<Footer
data={ data }
columns={ columns }
selectRow={ selectRow }
expandRow={ expandRow }
/>
);
});
it('should render successfully', () => {
expect(wrapper.length).toBe(1);
expect(wrapper.find('tr').length).toBe(1);
expect(wrapper.find(FooterCell).length).toBe(columns.length);
});
});
describe('className prop is exists', () => {
const className = 'test-class';
beforeEach(() => {
wrapper = shallow(
<Footer
data={ data }
columns={ columns }
className={ className }
selectRow={ selectRow }
expandRow={ expandRow }
/>
);
});
it('should render successfully', () => {
expect(wrapper.length).toBe(1);
expect(wrapper.find(`.${className}`).length).toBe(1);
});
});
describe('when selecrRow prop is enable', () => {
beforeEach(() => {
wrapper = render(
<Footer
data={ data }
columns={ columns }
selectRow={ { ...selectRow, mode: 'radio', hideSelectColumn: false } }
expandRow={ expandRow }
/>
);
});
it('should render successfully', () => {
expect(wrapper.length).toBe(1);
expect(wrapper.find('th').length).toBe(columns.length + 1);
});
});
describe('when expandRow prop is enable', () => {
beforeEach(() => {
wrapper = render(
<Footer
data={ data }
columns={ columns }
selectRow={ selectRow }
expandRow={ { expandRow, showExpandColumn: true } }
/>
);
});
it('should render successfully', () => {
expect(wrapper.length).toBe(1);
expect(wrapper.find('th').length).toBe(columns.length + 1);
});
});
});

View File

@@ -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(
<ExpansionContext.Provider
data={ data }
keyField={ keyField }
expandRow={ expandRow }
>
<Header
{ ...mockHeaderResolvedProps }
columns={ columns }
expandRow={ expandRow }
/>
</ExpansionContext.Provider>
);
});
it('should render expansion column correctly', () => {
const header = wrapper.find(Header).children();
expect(header.children().children().last().find(ExpandHeaderCell)).toHaveLength(1);
});
});
});
});

View File

@@ -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(
<ExpansionContext.Provider data={ data } keyField={ keyField } expandRow={ expandRow }>
<RowAggregatorWithExpansion { ...getBaseProps() } />
</ExpansionContext.Provider>
);
});
it('should render expansion column correctly', () => {
rowAggregator = wrapper.find(RowAggregator);
expect(rowAggregator).toHaveLength(1);
expect(rowAggregator.children().children().last().type()).toEqual(ExpandCell);
});
});
});
describe('createClickEventHandler', () => {

View File

@@ -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(<DummyComponent { ...props } />);
});
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(<DummyComponent { ...props } />);
});
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(<DummyComponent { ...props } />);
});
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(() => {