Compare commits

..

56 Commits

Author SHA1 Message Date
AllenFang
6cac7f6dc8 Publish
- react-bootstrap-table2-example@1.0.19
 - react-bootstrap-table2-filter@1.1.5
 - react-bootstrap-table2-toolkit@1.3.1
 - react-bootstrap-table-next@3.0.0
2019-02-24 17:01:52 +08:00
Allen
da5b93c3cf Merge pull request #818 from react-bootstrap-table/develop
20190224 release
2019-02-24 16:59:43 +08:00
AllenFang
3ffccce1fe fix #817 2019-02-24 16:20:44 +08:00
AllenFang
09f21e8130 allow to custom className on clear search and export csv button 2019-02-24 16:11:38 +08:00
AllenFang
bf0c5c43a2 fix #735 2019-02-24 15:50:13 +08:00
AllenFang
c01f45a719 fix #789 2019-02-24 14:56:27 +08:00
AllenFang
d26c13b9be fix #815 2019-02-24 14:11:05 +08:00
AllenFang
8e940112f5 fix #811 2019-02-23 16:13:40 +08:00
AllenFang
6070d150a9 Publish
- react-bootstrap-table2-example@1.0.18
 - react-bootstrap-table2-paginator@2.0.3
 - react-bootstrap-table2-toolkit@1.3.0
 - react-bootstrap-table-next@2.2.0
2019-02-20 23:08:26 +08:00
Allen
dab6f1b206 Merge pull request #810 from react-bootstrap-table/develop
20190220 release
2019-02-20 23:05:03 +08:00
Allen
2a497194e7 Merge pull request #801 from react-bootstrap-table/enhance/795
Implement PaginationTotalStandalone
2019-02-17 17:41:57 +08:00
AllenFang
f5f17897fd add story for PaginationTotalStandalone 2019-02-17 17:29:56 +08:00
AllenFang
2932b8a1b8 fix #795 2019-02-17 17:29:56 +08:00
AllenFang
93103e5ca0 exclude examples from coverage 2019-02-17 17:17:56 +08:00
AllenFang
a7c2a49182 fix #793 2019-02-17 15:25:32 +08:00
Allen
cd27ff98ff Merge pull request #800 from react-bootstrap-table/feat/723
Implement Column Toggle(723)
2019-02-17 15:24:17 +08:00
AllenFang
4d815894e6 add stories for #723 2019-02-17 14:55:44 +08:00
AllenFang
d9ff201373 fix #723 2019-02-17 14:55:17 +08:00
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
97 changed files with 4282 additions and 466 deletions

View File

@@ -30,6 +30,14 @@ Available properties in a column object:
* [headerAttrs](#headerAttrs) * [headerAttrs](#headerAttrs)
* [headerSortingClasses](#headerSortingClasses) * [headerSortingClasses](#headerSortingClasses)
* [headerSortingStyle](#headerSortingStyle) * [headerSortingStyle](#headerSortingStyle)
* [footer](#footer)
* [footerFormatter](#footerFormatter)
* [footerClasses](#footerClasses)
* [footerStyle](#footerStyle)
* [footerTitle](#footerTitle)
* [footerEvents](#footerEvents)
* [footerAlign](#footerAlign)
* [footerAttrs](#footerAttrs)
* [editable](#editable) * [editable](#editable)
* [validator](#validator) * [validator](#validator)
* [editCellStyle](#editCellStyle) * [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 new `String` will be the result of element title.
## <a name='headerTitle'>column.headerTitle - [Bool | Function]</a> ## <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 ```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> ## <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`. `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

@@ -23,7 +23,7 @@ Currently, **I still can't implement all the mainly features in legacy `react-bo
* [`react-bootstrap-table2-overlay`](https://www.npmjs.com/package/react-bootstrap-table2-overlay) * [`react-bootstrap-table2-overlay`](https://www.npmjs.com/package/react-bootstrap-table2-overlay)
* Overlay/Loading Addons * Overlay/Loading Addons
* [`react-bootstrap-table2-toolkit`](https://www.npmjs.com/package/react-bootstrap-table2-toolkit) * [`react-bootstrap-table2-toolkit`](https://www.npmjs.com/package/react-bootstrap-table2-toolkit)
* Table Toolkits, like search, csv etc. * Table Toolkits, like search, csv, column toggle etc.
This can help your application with less bundled size and also help `react-bootstrap-table2` have clean design to avoid handling to much logic in kernel module(SRP). Hence, which means you probably need to install above addons when you need specific features. This can help your application with less bundled size and also help `react-bootstrap-table2` have clean design to avoid handling to much logic in kernel module(SRP). Hence, which means you probably need to install above addons when you need specific features.
@@ -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 component and position
- [x] Custom search value - [x] Custom search value
- [ ] Clear search - [x] Clear search
- [ ] Multiple search - [ ] Multiple search
- [ ] Strict search - [ ] Strict search

View File

@@ -15,6 +15,7 @@
* [showExpandColumn](#showExpandColumn) * [showExpandColumn](#showExpandColumn)
* [onlyOneExpanding](#onlyOneExpanding) * [onlyOneExpanding](#onlyOneExpanding)
* [expandByColumnOnly](#expandByColumnOnly) * [expandByColumnOnly](#expandByColumnOnly)
* [expandColumnPosition](#expandColumnPosition)
* [expandColumnRenderer](#expandColumnRenderer) * [expandColumnRenderer](#expandColumnRenderer)
* [expandHeaderColumnRenderer](#expandHeaderColumnRenderer) * [expandHeaderColumnRenderer](#expandHeaderColumnRenderer)
@@ -153,3 +154,14 @@ const expandRow = {
expandByColumnOnly: true 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

@@ -176,7 +176,7 @@ Provide a callback function which allow you to custom the checkbox/radio box. Th
```js ```js
const selectRow = { const selectRow = {
mode: 'checkbox', mode: 'checkbox',
selectionRenderer: ({ mode, checked, disabled }) => ( selectionRenderer: ({ mode, checked, disabled, rowIndex }) => (
// .... // ....
) )
}; };

View File

@@ -89,7 +89,8 @@
"jest": { "jest": {
"collectCoverageFrom": [ "collectCoverageFrom": [
"packages/*/src/**/*.js", "packages/*/src/**/*.js",
"packages/*/index.js" "packages/*/index.js",
"!packages/react-bootstrap-table2-example/**/*.js"
], ],
"roots": [ "roots": [
"<rootDir>/packages" "<rootDir>/packages"

View File

@@ -0,0 +1,81 @@
/* eslint react/prop-types: 0 */
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import ToolkitProvider, { ColumnToggle } from 'react-bootstrap-table2-toolkit';
import Code from 'components/common/code-block';
import { productsGenerator } from 'utils/common';
const { ToggleList } = ColumnToggle;
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, { ColumnToggle } from 'react-bootstrap-table2-toolkit';
const { ToggleList } = ColumnToggle;
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'price',
text: 'Product Price'
}];
<ToolkitProvider
keyField="id"
data={ products }
columns={ columns }
columnToggle
>
{
props => (
<div>
<ToggleList { ...props.columnToggleProps } />
<hr />
<BootstrapTable
{ ...props.baseProps }
/>
</div>
)
}
</ToolkitProvider>
`;
export default () => (
<div>
<ToolkitProvider
keyField="id"
data={ products }
columns={ columns }
columnToggle
>
{
props => (
<div>
<ToggleList { ...props.columnToggleProps } />
<hr />
<BootstrapTable
{ ...props.baseProps }
/>
</div>
)
}
</ToolkitProvider>
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -0,0 +1,90 @@
/* eslint react/prop-types: 0 */
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import ToolkitProvider, { Search, CSVExport } from 'react-bootstrap-table2-toolkit';
import Code from 'components/common/code-block';
import { productsGenerator } from 'utils/common';
const { SearchBar, ClearSearchButton } = Search;
const { ExportCSVButton } = CSVExport;
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, CSVExport } from 'react-bootstrap-table2-toolkit';
const { SearchBar, ClearSearchButton } = Search;
const { ExportCSVButton } = CSVExport;
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 }
/>
<ExportCSVButton { ...props.csvProps }>Export CSV!!</ExportCSVButton>
</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 }
/>
<ExportCSVButton { ...props.csvProps }>Export CSV!!</ExportCSVButton>
</div>
)
}
</ToolkitProvider>
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -18,6 +18,11 @@ class QualityRanger extends React.Component {
static defaultProps = { static defaultProps = {
value: 0 value: 0
} }
componentDidMount() {
this.range.focus();
}
getValue() { getValue() {
return parseInt(this.range.value, 10); return parseInt(this.range.value, 10);
} }

View File

@@ -1,13 +1,14 @@
import React from 'react'; import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next'; 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 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 nameFilter;
let priceFilter; let priceFilter;
let stockDateFilter;
const columns = [{ const columns = [{
dataField: 'id', dataField: 'id',
@@ -22,25 +23,36 @@ const columns = [{
}) })
}, { }, {
dataField: 'price', dataField: 'price',
text: 'Product Price', text: 'Price',
filter: textFilter({ filter: textFilter({
getFilter: (filter) => { getFilter: (filter) => {
priceFilter = filter; priceFilter = filter;
} }
}) })
}, {
dataField: 'inStockDate',
text: 'InStock Date',
formatter: cell => cell.toString(),
filter: dateFilter({
getFilter: (filter) => {
stockDateFilter = filter;
}
})
}]; }];
const handleClick = () => { const handleClick = () => {
nameFilter(''); nameFilter('');
priceFilter(''); priceFilter('');
stockDateFilter();
}; };
const sourceCode = `\ const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next'; 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 nameFilter;
let priceFilter; let priceFilter;
let stockDateFilter;
const columns = [{ const columns = [{
dataField: 'id', dataField: 'id',
@@ -55,17 +67,27 @@ const columns = [{
}) })
}, { }, {
dataField: 'price', dataField: 'price',
text: 'Product Price', text: 'Price',
filter: textFilter({ filter: textFilter({
getFilter: (filter) => { getFilter: (filter) => {
priceFilter = filter; priceFilter = filter;
} }
}) })
}, {
dataField: 'inStockDate',
text: 'InStock Date',
formatter: cell => cell.toString(),
filter: dateFilter({
getFilter: (filter) => {
stockDateFilter = filter;
}
})
}]; }];
const handleClick = () => { const handleClick = () => {
nameFilter(''); nameFilter('');
priceFilter(''); priceFilter('');
stockDateFilter();
}; };
export default () => ( 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,135 @@
/* eslint react/prop-types: 0 */
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import ToolkitProvider from 'react-bootstrap-table2-toolkit';
import Code from 'components/common/code-block';
import { productsGenerator } from 'utils/common';
const products = productsGenerator();
const columnsdt = [{
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 from 'react-bootstrap-table2-toolkit';
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'price',
text: 'Product Price'
}];
const CustomToggleList = ({
columns,
onColumnToggle,
toggles
}) => (
<div className="btn-group btn-group-toggle btn-group-vertical" data-toggle="buttons">
{
columns
.map(column => ({
...column,
toggle: toggles[column.dataField]
}))
.map(column => (
<button
type="button"
key={ column.dataField }
className={ \`btn btn-warning \${column.toggle ? 'active' : ''}\` }
data-toggle="button"
aria-pressed={ column.toggle ? 'true' : 'false' }
onClick={ () => onColumnToggle(column.dataField) }
>
{ column.text }
</button>
))
}
</div>
);
<ToolkitProvider
keyField="id"
data={ products }
columns={ columnsdt }
columnToggle
>
{
props => (
<div>
<CustomToggleList { ...props.columnToggleProps } />
<hr />
<BootstrapTable
{ ...props.baseProps }
/>
</div>
)
}
</ToolkitProvider>
`;
const CustomToggleList = ({
columns,
onColumnToggle,
toggles
}) => (
<div className="btn-group btn-group-toggle btn-group-vertical" data-toggle="buttons">
{
columns
.map(column => ({
...column,
toggle: toggles[column.dataField]
}))
.map(column => (
<button
type="button"
key={ column.dataField }
className={ `btn btn-warning ${column.toggle ? 'active' : ''}` }
data-toggle="button"
aria-pressed={ column.toggle ? 'true' : 'false' }
onClick={ () => onColumnToggle(column.dataField) }
>
{ column.text }
</button>
))
}
</div>
);
export default () => (
<div>
<ToolkitProvider
keyField="id"
data={ products }
columns={ columnsdt }
columnToggle
>
{
props => (
<div>
<CustomToggleList { ...props.columnToggleProps } />
<hr />
<BootstrapTable
{ ...props.baseProps }
/>
</div>
)
}
</ToolkitProvider>
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -0,0 +1,81 @@
/* eslint react/prop-types: 0 */
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import ToolkitProvider, { ColumnToggle } from 'react-bootstrap-table2-toolkit';
import Code from 'components/common/code-block';
import { productsGenerator } from 'utils/common';
const { ToggleList } = ColumnToggle;
const products = productsGenerator();
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name',
hidden: true
}, {
dataField: 'price',
text: 'Product Price',
hidden: true
}];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
import ToolkitProvider, { ColumnToggle } from 'react-bootstrap-table2-toolkit';
const { ToggleList } = ColumnToggle;
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name',
hidden: true
}, {
dataField: 'price',
text: 'Product Price',
hidden: true
}];
<ToolkitProvider
keyField="id"
data={ products }
columns={ columns }
columnToggle
>
{
props => (
<div>
<ToggleList { ...props.columnToggleProps } />
<hr />
<BootstrapTable { ...props.baseProps } />
</div>
)
}
</ToolkitProvider>
`;
export default () => (
<div>
<ToolkitProvider
keyField="id"
data={ products }
columns={ columns }
columnToggle
>
{
props => (
<div>
<ToggleList { ...props.columnToggleProps } />
<hr />
<BootstrapTable { ...props.baseProps } />
</div>
)
}
</ToolkitProvider>
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -0,0 +1,81 @@
/* eslint react/prop-types: 0 */
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import ToolkitProvider, { ColumnToggle } from 'react-bootstrap-table2-toolkit';
import Code from 'components/common/code-block';
import { productsGenerator } from 'utils/common';
const { ToggleList } = ColumnToggle;
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, { ColumnToggle } from 'react-bootstrap-table2-toolkit';
const { ToggleList } = ColumnToggle;
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'price',
text: 'Product Price'
}];
<ToolkitProvider
keyField="id"
data={ products }
columns={ columns }
columnToggle
>
{
props => (
<div>
<ToggleList { ...props.columnToggleProps } />
<hr />
<BootstrapTable
{ ...props.baseProps }
/>
</div>
)
}
</ToolkitProvider>
`;
export default () => (
<div>
<ToolkitProvider
keyField="id"
data={ products }
columns={ columns }
columnToggle
>
{
props => (
<div>
<ToggleList { ...props.columnToggleProps } />
<hr />
<BootstrapTable
{ ...props.baseProps }
/>
</div>
)
}
</ToolkitProvider>
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -0,0 +1,91 @@
/* eslint react/prop-types: 0 */
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import ToolkitProvider, { ColumnToggle } from 'react-bootstrap-table2-toolkit';
import Code from 'components/common/code-block';
import { productsGenerator } from 'utils/common';
const { ToggleList } = ColumnToggle;
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, { ColumnToggle } from 'react-bootstrap-table2-toolkit';
const { ToggleList } = ColumnToggle;
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'price',
text: 'Product Price'
}];
<ToolkitProvider
keyField="id"
data={ products }
columns={ columns }
columnToggle
>
{
props => (
<div>
<ToggleList
contextual="success"
className="list-custom-class"
btnClassName="list-btn-custom-class"
{ ...props.columnToggleProps }
/>
<hr />
<BootstrapTable
{ ...props.baseProps }
/>
</div>
)
}
</ToolkitProvider>
`;
export default () => (
<div>
<ToolkitProvider
keyField="id"
data={ products }
columns={ columns }
columnToggle
>
{
props => (
<div>
<ToggleList
contextual="success"
className="list-custom-class"
btnClassName="list-btn-custom-class"
{ ...props.columnToggleProps }
/>
<hr />
<BootstrapTable
{ ...props.baseProps }
/>
</div>
)
}
</ToolkitProvider>
<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,107 @@
/* eslint react/prefer-stateless-function: 0 */
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import paginationFactory, { PaginationProvider, PaginationTotalStandalone, 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, PaginationTotalStandalone, PaginationListStandalone } from 'react-bootstrap-table2-paginator';
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'price',
text: 'Product Price'
}];
const options = {
custom: true,
totalSize: products.length
};
<PaginationProvider
pagination={ paginationFactory(options) }
>
{
({
paginationProps,
paginationTableProps
}) => (
<div>
<PaginationTotalStandalone
{ ...paginationProps }
/>
<PaginationListStandalone
{ ...paginationProps }
/>
<BootstrapTable
keyField="id"
data={ products }
columns={ columns }
{ ...paginationTableProps }
/>
</div>
)
}
</PaginationProvider>
`;
const options = {
custom: true,
totalSize: products.length
};
export default class StandalonePaginationList extends React.Component {
render() {
return (
<div>
<PaginationProvider
pagination={ paginationFactory(options) }
>
{
({
paginationProps,
paginationTableProps
}) => (
<div>
<PaginationTotalStandalone
{ ...paginationProps }
/>
<PaginationListStandalone
{ ...paginationProps }
/>
<BootstrapTable
keyField="id"
data={ products }
columns={ columns }
{ ...paginationTableProps }
/>
</div>
)
}
</PaginationProvider>
<Code>{ sourceCode }</Code>
</div>
);
}
}

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", "name": "react-bootstrap-table2-example",
"version": "1.0.13", "version": "1.0.19",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"private": true, "private": true,

View File

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

View File

@@ -20,6 +20,8 @@ import TabIndexCellTable from 'examples/basic/tabindex-column';
import Bootstrap4DefaultSortTable from 'examples/bootstrap4/sort'; import Bootstrap4DefaultSortTable from 'examples/bootstrap4/sort';
import Bootstrap4RowSelectionTable from 'examples/bootstrap4/row-selection'; import Bootstrap4RowSelectionTable from 'examples/bootstrap4/row-selection';
import Bootstrap4PaginationTable from 'examples/bootstrap4/pagination'; import Bootstrap4PaginationTable from 'examples/bootstrap4/pagination';
import Bootstrap4ColumnToggleTable from 'examples/bootstrap4/column-toggle';
import ToolkitsTable from 'examples/bootstrap4/toolkits';
// work on columns // work on columns
import NestedDataTable from 'examples/columns/nested-data-table'; import NestedDataTable from 'examples/columns/nested-data-table';
@@ -45,6 +47,18 @@ import HeaderColumnStyleTable from 'examples/header-columns/column-style-table';
import HeaderColumnAttrsTable from 'examples/header-columns/column-attrs-table'; import HeaderColumnAttrsTable from 'examples/header-columns/column-attrs-table';
import HeaderClassTable from 'examples/header-columns/header-class-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 // column filter
import TextFilter from 'examples/column-filter/text-filter'; import TextFilter from 'examples/column-filter/text-filter';
import TextFilterWithDefaultValue from 'examples/column-filter/text-filter-default-value'; import TextFilterWithDefaultValue from 'examples/column-filter/text-filter-default-value';
@@ -74,6 +88,8 @@ import ProgrammaticallyMultiSelectFilter from 'examples/column-filter/programmat
import CustomFilter from 'examples/column-filter/custom-filter'; import CustomFilter from 'examples/column-filter/custom-filter';
import AdvanceCustomFilter from 'examples/column-filter/advance-custom-filter'; import AdvanceCustomFilter from 'examples/column-filter/advance-custom-filter';
import ClearAllFilters from 'examples/column-filter/clear-all-filters'; 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 // work on rows
import RowStyleTable from 'examples/rows/row-style'; import RowStyleTable from 'examples/rows/row-style';
@@ -141,6 +157,7 @@ import ExpandColumn from 'examples/row-expand/expand-column';
import OnlyExpandByColumn from 'examples/row-expand/expand-by-column-only.js'; import OnlyExpandByColumn from 'examples/row-expand/expand-by-column-only.js';
import ExpandOnlyOne from 'examples/row-expand/expand-only-one'; import ExpandOnlyOne from 'examples/row-expand/expand-only-one';
import CustomExpandColumn from 'examples/row-expand/custom-expand-column'; 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'; import ExpandHooks from 'examples/row-expand/expand-hooks';
// pagination // pagination
@@ -153,10 +170,15 @@ import CustomSizePerPageTable from 'examples/pagination/custom-size-per-page';
import CustomPageListTable from 'examples/pagination/custom-page-list'; import CustomPageListTable from 'examples/pagination/custom-page-list';
import StandalonePaginationList from 'examples/pagination/standalone-pagination-list'; import StandalonePaginationList from 'examples/pagination/standalone-pagination-list';
import StandaloneSizePerPage from 'examples/pagination/standalone-size-per-page'; import StandaloneSizePerPage from 'examples/pagination/standalone-size-per-page';
import StandalonePaginationTotal from 'examples/pagination/standalone-pagination-total';
import FullyCustomPaginationTable from 'examples/pagination/fully-custom-pagination'; 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 // search
import SearchTable from 'examples/search'; import SearchTable from 'examples/search';
import ClearSearchButton from 'examples/search/clear-search-button';
import DefaultSearch from 'examples/search/default-search'; import DefaultSearch from 'examples/search/default-search';
import DefaultCustomSearch from 'examples/search/default-custom-search'; import DefaultCustomSearch from 'examples/search/default-custom-search';
import FullyCustomSearch from 'examples/search/fully-custom-search'; import FullyCustomSearch from 'examples/search/fully-custom-search';
@@ -174,6 +196,12 @@ import CustomCSVButton from 'examples/csv/custom-csv-button';
import ExportCustomData from 'examples/csv/export-custom-data'; import ExportCustomData from 'examples/csv/export-custom-data';
import CustomCSV from 'examples/csv/custom-csv'; import CustomCSV from 'examples/csv/custom-csv';
// Column toggle
import BasicColumnToggle from 'examples/column-toggle';
import DefaultVisibility from 'examples/column-toggle/default-visibility';
import StylingColumnToggle from 'examples/column-toggle/styling-toggle-list';
import CustomToggleList from 'examples/column-toggle/custom-toggle-list';
// loading overlay // loading overlay
import EmptyTableOverlay from 'examples/loading-overlay/empty-table-overlay'; import EmptyTableOverlay from 'examples/loading-overlay/empty-table-overlay';
import TableOverlay from 'examples/loading-overlay/table-overlay'; import TableOverlay from 'examples/loading-overlay/table-overlay';
@@ -186,6 +214,13 @@ import RemoteSearch from 'examples/remote/remote-search';
import RemoteCellEdit from 'examples/remote/remote-celledit'; import RemoteCellEdit from 'examples/remote/remote-celledit';
import RemoteAll from 'examples/remote/remote-all'; 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 // css style
import 'stories/stylesheet/tomorrow.min.css'; import 'stories/stylesheet/tomorrow.min.css';
import 'stories/stylesheet/storybook.scss'; import 'stories/stylesheet/storybook.scss';
@@ -196,8 +231,7 @@ import '../../react-bootstrap-table2-filter/style/react-bootstrap-table2-filter.
// import bootstrap style by given version // import bootstrap style by given version
import bootstrapStyle, { BOOTSTRAP_VERSION } from './bootstrap-style'; import bootstrapStyle, { BOOTSTRAP_VERSION } from './bootstrap-style';
storiesOf('Welcome', module) storiesOf('Welcome', module).add('react bootstrap table 2 ', () => <Welcome />);
.add('react bootstrap table 2 ', () => <Welcome />);
storiesOf('Basic Table', module) storiesOf('Basic Table', module)
.addDecorator(bootstrapStyle()) .addDecorator(bootstrapStyle())
@@ -215,7 +249,9 @@ storiesOf('Bootstrap 4', module)
.addDecorator(bootstrapStyle(BOOTSTRAP_VERSION.FOUR)) .addDecorator(bootstrapStyle(BOOTSTRAP_VERSION.FOUR))
.add('Sort table with bootstrap 4', () => <Bootstrap4DefaultSortTable />) .add('Sort table with bootstrap 4', () => <Bootstrap4DefaultSortTable />)
.add('Row selection table with bootstrap 4', () => <Bootstrap4RowSelectionTable />) .add('Row selection table with bootstrap 4', () => <Bootstrap4RowSelectionTable />)
.add('Pagination table with bootstrap 4', () => <Bootstrap4PaginationTable />); .add('Pagination table with bootstrap 4', () => <Bootstrap4PaginationTable />)
.add('Column Toggle with bootstrap 4', () => <Bootstrap4ColumnToggleTable />)
.add('toolkits Table bootstrap 4', () => <ToolkitsTable />);
storiesOf('Work on Columns', module) storiesOf('Work on Columns', module)
.addDecorator(bootstrapStyle()) .addDecorator(bootstrapStyle())
@@ -273,7 +309,9 @@ storiesOf('Column Filter', module)
.add('Custom Filter', () => <CustomFilter />) .add('Custom Filter', () => <CustomFilter />)
.add('Advance Custom Filter', () => <AdvanceCustomFilter />) .add('Advance Custom Filter', () => <AdvanceCustomFilter />)
.add('Preserved Option Order on Select Filter', () => <SelectFilterWithPreservedOptionsOrder />) .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) storiesOf('Work on Rows', module)
.addDecorator(bootstrapStyle()) .addDecorator(bootstrapStyle())
@@ -282,6 +320,19 @@ storiesOf('Work on Rows', module)
.add('Hide Rows', () => <RowHiddenTable />) .add('Hide Rows', () => <RowHiddenTable />)
.add('Row Event', () => <RowEventTable />); .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) storiesOf('Sort Table', module)
.addDecorator(bootstrapStyle()) .addDecorator(bootstrapStyle())
.add('Enable Sort', () => <EnableSortTable />) .add('Enable Sort', () => <EnableSortTable />)
@@ -346,6 +397,7 @@ storiesOf('Row Expand', module)
.add('Only Expand by Indicator', () => <OnlyExpandByColumn />) .add('Only Expand by Indicator', () => <OnlyExpandByColumn />)
.add('Expand Only One Row at The Same Time', () => <ExpandOnlyOne />) .add('Expand Only One Row at The Same Time', () => <ExpandOnlyOne />)
.add('Custom Expand Indicator', () => <CustomExpandColumn />) .add('Custom Expand Indicator', () => <CustomExpandColumn />)
.add('Expand Column Position', () => <ExpandColumnPosition />)
.add('Expand Hooks', () => <ExpandHooks />); .add('Expand Hooks', () => <ExpandHooks />);
storiesOf('Pagination', module) storiesOf('Pagination', module)
@@ -359,17 +411,29 @@ storiesOf('Pagination', module)
.add('Custom SizePerPage', () => <CustomSizePerPageTable />) .add('Custom SizePerPage', () => <CustomSizePerPageTable />)
.add('Standalone Pagination List', () => <StandalonePaginationList />) .add('Standalone Pagination List', () => <StandalonePaginationList />)
.add('Standalone SizePerPage Dropdown', () => <StandaloneSizePerPage />) .add('Standalone SizePerPage Dropdown', () => <StandaloneSizePerPage />)
.add('Fully Custom Pagination', () => <FullyCustomPaginationTable />); .add('Standalone Pagination Total', () => <StandalonePaginationTotal />)
.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) storiesOf('Table Search', module)
.addDecorator(bootstrapStyle()) .addDecorator(bootstrapStyle())
.add('Basic Search Table', () => <SearchTable />) .add('Basic Search Table', () => <SearchTable />)
.add('Clear Search Button', () => <ClearSearchButton />)
.add('Default Search Table', () => <DefaultSearch />) .add('Default Search Table', () => <DefaultSearch />)
.add('Default Custom Search', () => <DefaultCustomSearch />) .add('Default Custom Search', () => <DefaultCustomSearch />)
.add('Fully Custom Search', () => <FullyCustomSearch />) .add('Fully Custom Search', () => <FullyCustomSearch />)
.add('Search Fromatted Value', () => <SearchFormattedData />) .add('Search Fromatted Value', () => <SearchFormattedData />)
.add('Custom Search Value', () => <CustomSearchValue />); .add('Custom Search Value', () => <CustomSearchValue />);
storiesOf('Column Toggle', module)
.addDecorator(bootstrapStyle())
.add('Basic Column Toggle', () => <BasicColumnToggle />)
.add('Default Visibility', () => <DefaultVisibility />)
.add('Styling Column Toggle', () => <StylingColumnToggle />)
.add('Custom Column Toggle', () => <CustomToggleList />);
storiesOf('Export CSV', module) storiesOf('Export CSV', module)
.addDecorator(bootstrapStyle()) .addDecorator(bootstrapStyle())
.add('Basic Export CSV', () => <ExportCSV />) .add('Basic Export CSV', () => <ExportCSV />)
@@ -395,3 +459,11 @@ storiesOf('Remote', module)
.add('Remote Search', () => <RemoteSearch />) .add('Remote Search', () => <RemoteSearch />)
.add('Remote Cell Editing', () => <RemoteCellEdit />) .add('Remote Cell Editing', () => <RemoteCellEdit />)
.add('Remote All', () => <RemoteAll />); .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

@@ -14,3 +14,7 @@
.header-class { .header-class {
background-color: $green-lighten-4; background-color: $green-lighten-4;
} }
.footer-class {
background-color: $green-lighten-4;
}

View File

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

View File

@@ -42,10 +42,11 @@ class DateFilter extends Component {
// export onFilter function to allow users to access // export onFilter function to allow users to access
if (getFilter) { if (getFilter) {
getFilter((filterVal) => { getFilter((filterVal) => {
this.dateFilterComparator.value = filterVal.comparator; const nullableFilterVal = filterVal || { date: null, comparator: null };
this.inputDate.value = dateParser(filterVal.date); 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 { class FilterProvider extends React.Component {
static propTypes = { static propTypes = {
data: PropTypes.array.isRequired, data: PropTypes.array.isRequired,
columns: PropTypes.array.isRequired columns: PropTypes.array.isRequired,
dataChangeListener: PropTypes.object
} }
constructor(props) { constructor(props) {
super(props); super(props);
this.currFilters = {}; this.currFilters = {};
this.onFilter = this.onFilter.bind(this); this.onFilter = this.onFilter.bind(this);
this.doFilter = this.doFilter.bind(this);
this.onExternalFilter = this.onExternalFilter.bind(this); this.onExternalFilter = this.onExternalFilter.bind(this);
this.state = {
data: props.data
};
} }
componentDidMount() { componentDidMount() {
@@ -33,6 +38,16 @@ export default (
} }
} }
componentWillReceiveProps(nextProps) {
let nextData = nextProps.data;
if (!isRemoteFiltering() && !_.isEqual(nextProps.data, this.state.data)) {
nextData = this.doFilter(nextProps);
}
this.setState({
data: nextData
});
}
onFilter(column, filterType, initialize = false) { onFilter(column, filterType, initialize = false) {
return (filterVal) => { return (filterVal) => {
// watch out here if migration to context API, #334 // watch out here if migration to context API, #334
@@ -64,7 +79,13 @@ export default (
return; 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() { doFilter(props, customResult) {
let { data } = this.props; let result = customResult;
if (!isRemoteFiltering()) {
data = filters(data, this.props.columns, _)(this.currFilters); const { dataChangeListener, data, columns } = props;
result = result || filters(data, columns, _)(this.currFilters);
if (dataChangeListener) {
dataChangeListener.emit('filterChanged', result.length);
} }
return result;
}
render() {
return ( return (
<FilterContext.Provider value={ { <FilterContext.Provider value={ {
data, data: this.state.data,
onFilter: this.onFilter, onFilter: this.onFilter,
onExternalFilter: this.onExternalFilter onExternalFilter: this.onExternalFilter
} } } }

View File

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

View File

@@ -69,23 +69,23 @@ Sometime, you may feel above props is not satisfied with your requirement, don't
* [sizePerPageOptionRenderer](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/pagination-props.html#paginationsizeperpageoptionrenderer-function) * [sizePerPageOptionRenderer](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/pagination-props.html#paginationsizeperpageoptionrenderer-function)
* [paginationTotalRenderer](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/pagination-props.html#paginationpaginationtotalrenderer-function) * [paginationTotalRenderer](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/pagination-props.html#paginationpaginationtotalrenderer-function)
### Professional ### Fully Customization
If you want to customize the pagination component completely, you may get interesting on following solution: If you want to customize the pagination component completely, you may get interesting on following solutions:
* Standalone * Standalone
* Non-standalone * Non-standalone
`react-bootstrap-table2-paginator` have a `PaginationProvider` which is a react context and you will be easier to customize the pagination components under the scope of `PaginationProvider`. Let's introduce it step by step: `react-bootstrap-table2-paginator` have a `PaginationProvider` which is a react context and that will be easier to customize the pagination components under the scope of `PaginationProvider`. Let's introduce it step by step:
#### Import PaginationProvider #### 1. Import PaginationProvider
```js ```js
import paginationFactory, { PaginationProvider } from 'react-bootstrap-table2-paginator'; import paginationFactory, { PaginationProvider } from 'react-bootstrap-table2-paginator';
``` ```
#### Declare custom and totalSize in pagination option: #### 2. Declare custom and totalSize in pagination option:
```js ```js
const paginationOption = { const paginationOption = {
@@ -94,7 +94,7 @@ const paginationOption = {
}; };
``` ```
#### Render PaginationProvider #### 3. Render PaginationProvider
```js ```js
<PaginationProvider <PaginationProvider
@@ -139,18 +139,24 @@ So far, your customization pagination is supposed to look like it:
</PaginationProvider> </PaginationProvider>
``` ```
Now, you have to choose, your built-in standalne components or you customize all of them by yourself: Now, you have to choose which solution you like: standalone or non-standalone ?
#### Use Standalone Component #### 4.1 Use Standalone Component
`react-bootstrap-table2-paginator` provider two standalone components: `react-bootstrap-table2-paginator` provider three standalone components:
* Size Per Page Dropdwn Standalone * Size Per Page Dropdwn Standalone
* Pagination List Standalone * Pagination List Standalone
* Pagination Total Standalone
When render each standalone, you just need to pass the `paginationProps` props to standalone component: When render each standalone, you just need to pass the `paginationProps` props to standalone component:
```js ```js
import paginationFactory, { PaginationProvider, PaginationListStandalone, SizePerPageDropdownStandalone } from 'react-bootstrap-table2-paginator'; import paginationFactory, {
PaginationProvider,
PaginationListStandalone,
SizePerPageDropdownStandalone,
PaginationTotalStandalone
} from 'react-bootstrap-table2-paginator';
<PaginationProvider <PaginationProvider
pagination={ paginationFactory(options) } pagination={ paginationFactory(options) }
@@ -164,6 +170,9 @@ import paginationFactory, { PaginationProvider, PaginationListStandalone, SizePe
<SizePerPageDropdownStandalone <SizePerPageDropdownStandalone
{ ...paginationProps } { ...paginationProps }
/> />
<PaginationTotalStandalone
{ ...paginationProps }
/>
<BootstrapTable <BootstrapTable
keyField="id" keyField="id"
data={ products } data={ products }
@@ -181,7 +190,7 @@ import paginationFactory, { PaginationProvider, PaginationListStandalone, SizePe
That's it!! The benifit for using standalone is you can much easier to render the standalone component in any posistion. In the future, we will implement more featue like applying `style`, `className` etc on standalone components. That's it!! The benifit for using standalone is you can much easier to render the standalone component in any posistion. In the future, we will implement more featue like applying `style`, `className` etc on standalone components.
#### Customization Everything #### 4.2 Customization Everything
If you choose to custom the pagination component by yourself, the `paginationProps` will be important for you. Becasue you have to know for example how to change page or what's the current page etc. Hence, following is all the props in `paginationProps` object: If you choose to custom the pagination component by yourself, the `paginationProps` will be important for you. Becasue you have to know for example how to change page or what's the current page etc. Hence, following is all the props in `paginationProps` object:

View File

@@ -4,6 +4,7 @@ import createBaseContext from './src/state-context';
import createDataContext from './src/data-context'; import createDataContext from './src/data-context';
import PaginationListStandalone from './src/pagination-list-standalone'; import PaginationListStandalone from './src/pagination-list-standalone';
import SizePerPageDropdownStandalone from './src/size-per-page-dropdown-standalone'; import SizePerPageDropdownStandalone from './src/size-per-page-dropdown-standalone';
import PaginationTotalStandalone from './src/pagination-total-standalone';
export default (options = {}) => ({ export default (options = {}) => ({
createContext: createDataContext, createContext: createDataContext,
@@ -23,4 +24,4 @@ CustomizableProvider.propTypes = {
}; };
export const PaginationProvider = CustomizableProvider; export const PaginationProvider = CustomizableProvider;
export { PaginationListStandalone, SizePerPageDropdownStandalone }; export { PaginationListStandalone, SizePerPageDropdownStandalone, PaginationTotalStandalone };

View File

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

View File

@@ -135,8 +135,8 @@ export default ExtendBase =>
calculateSizePerPageStatus() { calculateSizePerPageStatus() {
const { sizePerPageList } = this.props; const { sizePerPageList } = this.props;
return sizePerPageList.map((_sizePerPage) => { return sizePerPageList.map((_sizePerPage) => {
const pageText = _sizePerPage.text || _sizePerPage; const pageText = typeof _sizePerPage.text !== 'undefined' ? _sizePerPage.text : _sizePerPage;
const pageNumber = _sizePerPage.value || _sizePerPage; const pageNumber = typeof _sizePerPage.value !== 'undefined' ? _sizePerPage.value : _sizePerPage;
return { return {
text: `${pageText}`, text: `${pageText}`,
page: pageNumber page: pageNumber

View File

@@ -0,0 +1,24 @@
/* eslint react/prop-types: 0 */
import React, { Component } from 'react';
import pageResolver from './page-resolver';
import PaginationTotal from './pagination-total';
const paginationTotalAdapter = WrappedComponent =>
class PaginationTotalAdapter extends pageResolver(Component) {
render() {
const [from, to] = this.calculateFromTo();
return (
<WrappedComponent
from={ from }
to={ to }
dataSize={ this.props.dataSize }
paginationTotalRenderer={ this.props.paginationTotalRenderer }
/>
);
}
};
export const PaginationTotalWithAdapter = paginationTotalAdapter(PaginationTotal);
export default paginationTotalAdapter;

View File

@@ -0,0 +1,11 @@
import React from 'react';
import PaginationTotal from './pagination-total';
import standaloneAdapter from './standalone-adapter';
import paginationTotalAdapter from './pagination-total-adapter';
const PaginationTotalStandalone = props => (
<PaginationTotal { ...props } />
);
export default
standaloneAdapter(paginationTotalAdapter(PaginationTotalStandalone));

View File

@@ -1,16 +1,26 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
const PaginationTotal = props => ( const PaginationTotal = (props) => {
if (props.paginationTotalRenderer) {
return props.paginationTotalRenderer(props.from, props.to, props.dataSize);
}
return (
<span className="react-bootstrap-table-pagination-total"> <span className="react-bootstrap-table-pagination-total">
&nbsp;Showing rows { props.from } to&nbsp;{ props.to } of&nbsp;{ props.dataSize } &nbsp;Showing rows { props.from } to&nbsp;{ props.to } of&nbsp;{ props.dataSize }
</span> </span>
); );
};
PaginationTotal.propTypes = { PaginationTotal.propTypes = {
from: PropTypes.number.isRequired, from: PropTypes.number.isRequired,
to: PropTypes.number.isRequired, to: PropTypes.number.isRequired,
dataSize: PropTypes.number.isRequired dataSize: PropTypes.number.isRequired,
paginationTotalRenderer: PropTypes.func
};
PaginationTotal.defaultProps = {
paginationTotalRenderer: undefined
}; };
export default PaginationTotal; export default PaginationTotal;

View File

@@ -5,30 +5,16 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import pageResolver from './page-resolver'; import pageResolver from './page-resolver';
import paginationHandler from './pagination-handler'; import paginationHandler from './pagination-handler';
import { SizePerPageDropdownAdapter } from './size-per-page-dropdown-adapter'; import { SizePerPageDropdownWithAdapter } from './size-per-page-dropdown-adapter';
import { PaginationListWithAdapter } from './pagination-list-adapter'; import { PaginationListWithAdapter } from './pagination-list-adapter';
import PaginationTotal from './pagination-total'; import { PaginationTotalWithAdapter } from './pagination-total-adapter';
import Const from './const'; import Const from './const';
class Pagination extends pageResolver(Component) { class Pagination extends pageResolver(Component) {
defaultTotal = (from, to, size) => (
<PaginationTotal
from={ from }
to={ to }
dataSize={ size }
/>
);
setTotal = (from, to, size, total) => {
if (total && (typeof total === 'function')) {
return total(from, to, size);
}
return this.defaultTotal(from, to, size);
};
render() { render() {
const { const {
currPage,
pageStartIndex,
showTotal, showTotal,
dataSize, dataSize,
pageListRenderer, pageListRenderer,
@@ -48,7 +34,6 @@ class Pagination extends pageResolver(Component) {
} = this.props; } = this.props;
const pages = this.calculatePageStatus(this.calculatePages(totalPages, lastPage), lastPage); const pages = this.calculatePageStatus(this.calculatePages(totalPages, lastPage), lastPage);
const [from, to] = this.calculateFromTo();
const pageListClass = cs( const pageListClass = cs(
'react-bootstrap-table-pagination-list', 'react-bootstrap-table-pagination-list',
'col-md-6 col-xs-6 col-sm-6 col-lg-6', { 'col-md-6 col-xs-6 col-sm-6 col-lg-6', {
@@ -57,7 +42,7 @@ class Pagination extends pageResolver(Component) {
return ( return (
<div className="row react-bootstrap-table-pagination"> <div className="row react-bootstrap-table-pagination">
<div className="col-md-6 col-xs-6 col-sm-6 col-lg-6"> <div className="col-md-6 col-xs-6 col-sm-6 col-lg-6">
<SizePerPageDropdownAdapter <SizePerPageDropdownWithAdapter
sizePerPageList={ sizePerPageList } sizePerPageList={ sizePerPageList }
currSizePerPage={ currSizePerPage } currSizePerPage={ currSizePerPage }
hideSizePerPage={ hideSizePerPage } hideSizePerPage={ hideSizePerPage }
@@ -67,12 +52,13 @@ class Pagination extends pageResolver(Component) {
/> />
{ {
showTotal ? showTotal ?
this.setTotal( <PaginationTotalWithAdapter
from, currPage={ currPage }
to, currSizePerPage={ currSizePerPage }
dataSize, pageStartIndex={ pageStartIndex }
paginationTotalRenderer dataSize={ dataSize }
) : null paginationTotalRenderer={ paginationTotalRenderer }
/> : null
} }
</div> </div>
{ {
@@ -83,6 +69,9 @@ class Pagination extends pageResolver(Component) {
<div className={ pageListClass }> <div className={ pageListClass }>
<PaginationListWithAdapter <PaginationListWithAdapter
{ ...rest } { ...rest }
currPage={ currPage }
currSizePerPage={ currSizePerPage }
pageStartIndex={ pageStartIndex }
lastPage={ lastPage } lastPage={ lastPage }
totalPages={ totalPages } totalPages={ totalPages }
pageButtonRenderer={ pageButtonRenderer } pageButtonRenderer={ pageButtonRenderer }

View File

@@ -63,5 +63,5 @@ const sizePerPageDropdownAdapter = WrappedComponent =>
}; };
export const SizePerPageDropdownAdapter = sizePerPageDropdownAdapter(SizePerPageDropDown); export const SizePerPageDropdownWithAdapter = sizePerPageDropdownAdapter(SizePerPageDropDown);
export default sizePerPageDropdownAdapter; export default sizePerPageDropdownAdapter;

View File

@@ -2,7 +2,9 @@
/* eslint react/require-default-props: 0 */ /* eslint react/require-default-props: 0 */
/* eslint no-lonely-if: 0 */ /* eslint no-lonely-if: 0 */
import React from 'react'; import React from 'react';
import EventEmitter from 'events';
import Const from './const'; import Const from './const';
import { alignPage } from './page';
const StateContext = React.createContext(); const StateContext = React.createContext();
@@ -10,6 +12,7 @@ class StateProvider extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.handleChangePage = this.handleChangePage.bind(this); this.handleChangePage = this.handleChangePage.bind(this);
this.handleDataSizeChange = this.handleDataSizeChange.bind(this);
this.handleChangeSizePerPage = this.handleChangeSizePerPage.bind(this); this.handleChangeSizePerPage = this.handleChangeSizePerPage.bind(this);
let currPage; let currPage;
@@ -36,7 +39,10 @@ class StateProvider extends React.Component {
} }
this.currPage = currPage; this.currPage = currPage;
this.dataSize = options.totalSize;
this.currSizePerPage = currSizePerPage; this.currSizePerPage = currSizePerPage;
this.dataChangeListener = new EventEmitter();
this.dataChangeListener.on('filterChanged', this.handleDataSizeChange);
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
@@ -46,12 +52,13 @@ class StateProvider extends React.Component {
if (this.isRemotePagination() || custom) { if (this.isRemotePagination() || custom) {
this.currPage = nextProps.pagination.options.page; this.currPage = nextProps.pagination.options.page;
this.currSizePerPage = nextProps.pagination.options.sizePerPage; this.currSizePerPage = nextProps.pagination.options.sizePerPage;
this.dataSize = nextProps.pagination.options.totalSize;
} }
} }
getPaginationProps = () => { getPaginationProps = () => {
const { pagination: { options }, bootstrap4 } = this.props; const { pagination: { options }, bootstrap4 } = this.props;
const { currPage, currSizePerPage } = this; const { currPage, currSizePerPage, dataSize } = this;
const withFirstAndLast = typeof options.withFirstAndLast === 'undefined' ? const withFirstAndLast = typeof options.withFirstAndLast === 'undefined' ?
Const.With_FIRST_AND_LAST : options.withFirstAndLast; Const.With_FIRST_AND_LAST : options.withFirstAndLast;
const alwaysShowAllBtns = typeof options.alwaysShowAllBtns === 'undefined' ? const alwaysShowAllBtns = typeof options.alwaysShowAllBtns === 'undefined' ?
@@ -72,7 +79,7 @@ class StateProvider extends React.Component {
hideSizePerPage, hideSizePerPage,
alwaysShowAllBtns, alwaysShowAllBtns,
withFirstAndLast, withFirstAndLast,
dataSize: options.totalSize, dataSize,
sizePerPageList: options.sizePerPageList || Const.SIZE_PER_PAGE_LIST, sizePerPageList: options.sizePerPageList || Const.SIZE_PER_PAGE_LIST,
paginationSize: options.paginationSize || Const.PAGINATION_SIZE, paginationSize: options.paginationSize || Const.PAGINATION_SIZE,
showTotal: options.showTotal, showTotal: options.showTotal,
@@ -106,6 +113,20 @@ class StateProvider extends React.Component {
return e.result; 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) { handleChangePage(currPage) {
const { currSizePerPage } = this; const { currSizePerPage } = this;
const { pagination: { options } } = this.props; const { pagination: { options } } = this.props;
@@ -153,7 +174,8 @@ class StateProvider extends React.Component {
paginationProps, paginationProps,
paginationTableProps: { paginationTableProps: {
pagination, pagination,
setPaginationRemoteEmitter: this.setPaginationRemoteEmitter setPaginationRemoteEmitter: this.setPaginationRemoteEmitter,
dataChangeListener: this.dataChangeListener
} }
} } } }
> >

View File

@@ -0,0 +1,34 @@
import React from 'react';
import { shallow } from 'enzyme';
import paginationTotalAdapter from '../src/pagination-total-adapter';
const MockComponent = () => null;
const PaginationTotalAdapter = paginationTotalAdapter(MockComponent);
describe('paginationTotalAdapter', () => {
let wrapper;
const props = {
dataSize: 20,
currPage: 1,
currSizePerPage: 10,
paginationTotalRenderer: jest.fn()
};
describe('render', () => {
beforeEach(() => {
wrapper = shallow(<PaginationTotalAdapter { ...props } />);
});
it('should render successfully', () => {
const mockComponent = wrapper.find(MockComponent);
expect(mockComponent).toHaveLength(1);
expect(mockComponent.props().from).toBeDefined();
expect(mockComponent.props().to).toBeDefined();
expect(mockComponent.props().dataSize).toEqual(props.dataSize);
expect(mockComponent.props().paginationTotalRenderer).toEqual(props.paginationTotalRenderer);
});
});
});

View File

@@ -1,11 +1,10 @@
import React from 'react'; import React from 'react';
import sinon from 'sinon'; import sinon from 'sinon';
import { shallow } from 'enzyme'; import { shallow, render } from 'enzyme';
import SizePerPageDropDown from '../src/size-per-page-dropdown'; import SizePerPageDropDown from '../src/size-per-page-dropdown';
import PaginationList from '../src/pagination-list'; import PaginationList from '../src/pagination-list';
import Pagination from '../src/pagination'; import Pagination from '../src/pagination';
import PaginationTotal from '../src/pagination-total';
describe('Pagination', () => { describe('Pagination', () => {
let wrapper; let wrapper;
@@ -107,28 +106,24 @@ describe('Pagination', () => {
describe('when props.showTotal is true', () => { describe('when props.showTotal is true', () => {
beforeEach(() => { beforeEach(() => {
const props = createMockProps({ showTotal: true }); const props = createMockProps({ showTotal: true });
wrapper = shallow(<Pagination { ...props } />); wrapper = render(<Pagination { ...props } />);
wrapper.render();
instance = wrapper.instance();
}); });
it('should render PaginationTotal correctly', () => { it('should render PaginationTotal correctly', () => {
expect(wrapper.dive().find(PaginationTotal)).toHaveLength(1); expect(wrapper.find('.react-bootstrap-table-pagination-total')).toHaveLength(1);
}); });
describe('if props.paginationTotalRenderer is defined', () => { describe('if props.paginationTotalRenderer is defined', () => {
let paginationTotalRenderer; let paginationTotalRenderer;
beforeEach(() => { beforeEach(() => {
paginationTotalRenderer = jest.fn(); paginationTotalRenderer = jest.fn(() => <div />);
const props = createMockProps({ showTotal: true, paginationTotalRenderer }); const props = createMockProps({ showTotal: true, paginationTotalRenderer });
wrapper = shallow(<Pagination { ...props } />); wrapper = render(<Pagination { ...props } />);
wrapper.render();
instance = wrapper.instance();
}); });
it('should not render PaginationTotal', () => { it('should not render PaginationTotal', () => {
expect(wrapper.dive().find(PaginationTotal)).toHaveLength(0); expect(wrapper.find('.react-bootstrap-table-pagination-total')).toHaveLength(0);
}); });
it('should call props.paginationTotalRenderer correctly', () => { it('should call props.paginationTotalRenderer correctly', () => {

View File

@@ -91,6 +91,10 @@ describe('PaginationStateContext', () => {
expect(wrapper.instance().currSizePerPage).toEqual(Const.SIZE_PER_PAGE_LIST[0]); 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', () => { it('should get correct pagination props', () => {
const instance = wrapper.instance(); const instance = wrapper.instance();
expect(wrapper.length).toBe(1); expect(wrapper.length).toBe(1);
@@ -102,7 +106,8 @@ describe('PaginationStateContext', () => {
createContext: expect.any(Function), createContext: expect.any(Function),
options: instance.getPaginationProps() options: instance.getPaginationProps()
}, },
setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter,
dataChangeListener: expect.any(Object)
} }
}); });
}); });
@@ -149,7 +154,7 @@ describe('PaginationStateContext', () => {
setRemotePaginationEmitter(instance, true); setRemotePaginationEmitter(instance, true);
nextProps = { nextProps = {
data, data,
pagination: { ...defaultPagination, options: { page: 3, sizePerPage: 5 } } pagination: { ...defaultPagination, options: { page: 3, sizePerPage: 5, totalSize: 50 } }
}; };
instance.componentWillReceiveProps(nextProps); instance.componentWillReceiveProps(nextProps);
}); });
@@ -157,6 +162,7 @@ describe('PaginationStateContext', () => {
it('should always reset currPage and currSizePerPage', () => { it('should always reset currPage and currSizePerPage', () => {
expect(instance.currPage).toEqual(nextProps.pagination.options.page); expect(instance.currPage).toEqual(nextProps.pagination.options.page);
expect(instance.currSizePerPage).toEqual(nextProps.pagination.options.sizePerPage); 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); setRemotePaginationEmitter(instance, true);
nextProps = { nextProps = {
data, data,
pagination: { ...defaultPagination, options: { page: 3, sizePerPage: 5, custom: true } } pagination: {
...defaultPagination,
options: { page: 3, sizePerPage: 5, custom: true, totalSize: 50 }
}
}; };
instance.componentWillReceiveProps(nextProps); instance.componentWillReceiveProps(nextProps);
}); });
@@ -178,10 +187,36 @@ describe('PaginationStateContext', () => {
it('should always reset currPage and currSizePerPage', () => { it('should always reset currPage and currSizePerPage', () => {
expect(instance.currPage).toEqual(nextProps.pagination.options.page); expect(instance.currPage).toEqual(nextProps.pagination.options.page);
expect(instance.currSizePerPage).toEqual(nextProps.pagination.options.sizePerPage); 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', () => { describe('handleChangePage', () => {
let instance; let instance;
const newPage = 3; const newPage = 3;
@@ -343,7 +378,8 @@ describe('PaginationStateContext', () => {
createContext: expect.any(Function), createContext: expect.any(Function),
options: instance.getPaginationProps() options: instance.getPaginationProps()
}, },
setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter,
dataChangeListener: expect.any(Object)
} }
}); });
}); });
@@ -374,7 +410,8 @@ describe('PaginationStateContext', () => {
createContext: expect.any(Function), createContext: expect.any(Function),
options: instance.getPaginationProps() options: instance.getPaginationProps()
}, },
setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter,
dataChangeListener: expect.any(Object)
} }
}); });
}); });
@@ -401,7 +438,8 @@ describe('PaginationStateContext', () => {
createContext: expect.any(Function), createContext: expect.any(Function),
options: instance.getPaginationProps() options: instance.getPaginationProps()
}, },
setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter,
dataChangeListener: expect.any(Object)
} }
}); });
}); });
@@ -428,7 +466,8 @@ describe('PaginationStateContext', () => {
createContext: expect.any(Function), createContext: expect.any(Function),
options: instance.getPaginationProps() options: instance.getPaginationProps()
}, },
setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter,
dataChangeListener: expect.any(Object)
} }
}); });
}); });
@@ -455,7 +494,8 @@ describe('PaginationStateContext', () => {
createContext: expect.any(Function), createContext: expect.any(Function),
options: instance.getPaginationProps() options: instance.getPaginationProps()
}, },
setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter,
dataChangeListener: expect.any(Object)
} }
}); });
}); });
@@ -482,7 +522,8 @@ describe('PaginationStateContext', () => {
createContext: expect.any(Function), createContext: expect.any(Function),
options: instance.getPaginationProps() options: instance.getPaginationProps()
}, },
setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter,
dataChangeListener: expect.any(Object)
} }
}); });
}); });
@@ -509,7 +550,8 @@ describe('PaginationStateContext', () => {
createContext: expect.any(Function), createContext: expect.any(Function),
options: instance.getPaginationProps() options: instance.getPaginationProps()
}, },
setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter,
dataChangeListener: expect.any(Object)
} }
}); });
}); });
@@ -536,7 +578,8 @@ describe('PaginationStateContext', () => {
createContext: expect.any(Function), createContext: expect.any(Function),
options: instance.getPaginationProps() options: instance.getPaginationProps()
}, },
setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter,
dataChangeListener: expect.any(Object)
} }
}); });
}); });
@@ -563,7 +606,8 @@ describe('PaginationStateContext', () => {
createContext: expect.any(Function), createContext: expect.any(Function),
options: instance.getPaginationProps() options: instance.getPaginationProps()
}, },
setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter,
dataChangeListener: expect.any(Object)
} }
}); });
}); });
@@ -590,7 +634,8 @@ describe('PaginationStateContext', () => {
createContext: expect.any(Function), createContext: expect.any(Function),
options: instance.getPaginationProps() options: instance.getPaginationProps()
}, },
setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter,
dataChangeListener: expect.any(Object)
} }
}); });
}); });
@@ -617,7 +662,8 @@ describe('PaginationStateContext', () => {
createContext: expect.any(Function), createContext: expect.any(Function),
options: instance.getPaginationProps() options: instance.getPaginationProps()
}, },
setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter,
dataChangeListener: expect.any(Object)
} }
}); });
}); });
@@ -644,7 +690,8 @@ describe('PaginationStateContext', () => {
createContext: expect.any(Function), createContext: expect.any(Function),
options: instance.getPaginationProps() options: instance.getPaginationProps()
}, },
setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter,
dataChangeListener: expect.any(Object)
} }
}); });
}); });
@@ -671,7 +718,8 @@ describe('PaginationStateContext', () => {
createContext: expect.any(Function), createContext: expect.any(Function),
options: instance.getPaginationProps() options: instance.getPaginationProps()
}, },
setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter,
dataChangeListener: expect.any(Object)
} }
}); });
}); });
@@ -698,7 +746,8 @@ describe('PaginationStateContext', () => {
createContext: expect.any(Function), createContext: expect.any(Function),
options: instance.getPaginationProps() options: instance.getPaginationProps()
}, },
setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter,
dataChangeListener: expect.any(Object)
} }
}); });
}); });
@@ -725,7 +774,8 @@ describe('PaginationStateContext', () => {
createContext: expect.any(Function), createContext: expect.any(Function),
options: instance.getPaginationProps() options: instance.getPaginationProps()
}, },
setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter,
dataChangeListener: expect.any(Object)
} }
}); });
}); });
@@ -752,7 +802,8 @@ describe('PaginationStateContext', () => {
createContext: expect.any(Function), createContext: expect.any(Function),
options: instance.getPaginationProps() options: instance.getPaginationProps()
}, },
setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter,
dataChangeListener: expect.any(Object)
} }
}); });
}); });
@@ -779,7 +830,8 @@ describe('PaginationStateContext', () => {
createContext: expect.any(Function), createContext: expect.any(Function),
options: instance.getPaginationProps() options: instance.getPaginationProps()
}, },
setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter,
dataChangeListener: expect.any(Object)
} }
}); });
}); });
@@ -806,7 +858,8 @@ describe('PaginationStateContext', () => {
createContext: expect.any(Function), createContext: expect.any(Function),
options: instance.getPaginationProps() options: instance.getPaginationProps()
}, },
setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter,
dataChangeListener: expect.any(Object)
} }
}); });
}); });
@@ -833,7 +886,8 @@ describe('PaginationStateContext', () => {
createContext: expect.any(Function), createContext: expect.any(Function),
options: instance.getPaginationProps() options: instance.getPaginationProps()
}, },
setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter setPaginationRemoteEmitter: instance.setPaginationRemoteEmitter,
dataChangeListener: expect.any(Object)
} }
}); });
}); });

View File

@@ -2,9 +2,15 @@
`react-bootstrap-table2` support some additional features in [`react-bootstrap-table2-toolkit`](https://github.com/react-bootstrap-table/react-bootstrap-table2/tree/develop/packages/react-bootstrap-table2-toolkit). `react-bootstrap-table2` support some additional features in [`react-bootstrap-table2-toolkit`](https://github.com/react-bootstrap-table/react-bootstrap-table2/tree/develop/packages/react-bootstrap-table2-toolkit).
In the future, this toolkit will support other feature like row delete, insert etc. Right now we only support Table Search and CSV export. In the future, this toolkit will support other feature like row delete, insert etc. Right now we only following features:
* Table Search
* Export CSV
* Column Toggle
**[Live Demo For Table Search](https://react-bootstrap-table.github.io/react-bootstrap-table2/storybook/index.html?selectedKind=Table%20Search)** **[Live Demo For Table Search](https://react-bootstrap-table.github.io/react-bootstrap-table2/storybook/index.html?selectedKind=Table%20Search)**
**[Live Demo For Export CSV](https://react-bootstrap-table.github.io/react-bootstrap-table2/storybook/index.html?selectedKind=Export%20CSV&selectedStory=Basic%20Export%20CSV)**
**[Live Demo For Column Toggle](https://react-bootstrap-table.github.io/react-bootstrap-table2/storybook/index.html?selectedKind=Column%20Toggle&selectedStory=Basic%20Column%20Toggle)**
**[API&Props Definitation](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/pagination-props.html)** **[API&Props Definitation](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/pagination-props.html)**
@@ -95,8 +101,34 @@ If you want to search on the formatted data, you are supposed to enable this pro
</ToolkitProvider> </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 ## 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`. 1. Give `exportCSV` prop as `true` on `ToolkitProvider`.
2. Render `ExportCSVButton` with `csvProps`. The position of `ExportCSVButton` is depends on you. 2. Render `ExportCSVButton` with `csvProps`. The position of `ExportCSVButton` is depends on you.
@@ -143,3 +175,35 @@ Default is `true`. `false` will only export current data which display on table.
#### onlyExportSelection - [bool] #### onlyExportSelection - [bool]
Default is `false`. `true` will only export the data which is selected. Default is `false`. `true` will only export the data which is selected.
## Column Toggle
Let's see how to render the column toggle in your react component:
```js
import BootstrapTable from 'react-bootstrap-table-next';
import ToolkitProvider, { ColumnToggle } from 'react-bootstrap-table2-toolkit';
<ToolkitProvider
keyField="id"
data={ products }
columns={ columns }
columnToggle
>
{
props => (
<div>
<ToggleList { ...props.columnToggleProps } />
<hr />
<BootstrapTable
{ ...props.baseProps }
/>
</div>
)
}
</ToolkitProvider>
```
> `columnToggleProps` props have enough information to let you custom the toggle list: [demo]([Live Demo For Export CSV](https://react-bootstrap-table.github.io/react-bootstrap-table2/storybook/index.html?selectedKind=Export%20CSV&selectedStory=Custom%20Column%20Toggle))
If you want to have default visibility on specified column, you can just give `true` or `false` on `column.hidden`.

View File

@@ -1,13 +1,14 @@
/* eslint no-param-reassign: 0 */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import statelessDrcorator from './statelessOp'; import statelessDecorator from './statelessOp';
import createContext from './src/search/context'; import createSearchContext from './src/search/context';
const ToolkitContext = React.createContext(); const ToolkitContext = React.createContext();
class ToolkitProvider extends statelessDrcorator(React.Component) { class ToolkitProvider extends statelessDecorator(React.Component) {
static propTypes = { static propTypes = {
keyField: PropTypes.string.isRequired, keyField: PropTypes.string.isRequired,
data: PropTypes.array.isRequired, data: PropTypes.array.isRequired,
@@ -42,12 +43,22 @@ class ToolkitProvider extends statelessDrcorator(React.Component) {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { const state = {};
searchText: typeof props.search === 'object' ? (props.search.defaultSearch || '') : ''
};
this._ = null; this._ = null;
this.onClear = this.onClear.bind(this);
this.onSearch = this.onSearch.bind(this); this.onSearch = this.onSearch.bind(this);
this.onColumnToggle = this.onColumnToggle.bind(this);
this.setDependencyModules = this.setDependencyModules.bind(this); this.setDependencyModules = this.setDependencyModules.bind(this);
if (props.columnToggle) {
state.columnToggle = props.columns
.reduce((obj, column) => {
obj[column.dataField] = !column.hidden;
return obj;
}, {});
}
state.searchText = typeof props.search === 'object' ? (props.search.defaultSearch || '') : '';
this.state = state;
} }
onSearch(searchText) { onSearch(searchText) {
@@ -56,6 +67,18 @@ class ToolkitProvider extends statelessDrcorator(React.Component) {
} }
} }
onClear() {
this.setState({ searchText: '' });
}
onColumnToggle(dataField) {
const { columnToggle } = this.state;
columnToggle[dataField] = !columnToggle[dataField];
this.setState(({
...this.state,
columnToggle
}));
}
/** /**
* *
* @param {*} _ * @param {*} _
@@ -79,19 +102,30 @@ class ToolkitProvider extends statelessDrcorator(React.Component) {
}; };
if (this.props.search) { if (this.props.search) {
baseProps.search = { baseProps.search = {
searchContext: createContext(this.props.search), searchContext: createSearchContext(this.props.search),
searchText: this.state.searchText searchText: this.state.searchText
}; };
} }
if (this.props.columnToggle) {
baseProps.columnToggle = {
toggles: this.state.columnToggle
};
}
return ( return (
<ToolkitContext.Provider value={ { <ToolkitContext.Provider value={ {
searchProps: { searchProps: {
searchText: this.state.searchText, searchText: this.state.searchText,
onSearch: this.onSearch onSearch: this.onSearch,
onClear: this.onClear
}, },
csvProps: { csvProps: {
onExport: this.handleExportCSV onExport: this.handleExportCSV
}, },
columnToggleProps: {
columns: this.props.columns,
toggles: this.state.columnToggle,
onColumnToggle: this.onColumnToggle
},
baseProps baseProps
} } } }
> >

View File

@@ -5,3 +5,4 @@ export default ToolkitProvider;
export const ToolkitContext = Context; export const ToolkitContext = Context;
export { default as Search } from './src/search'; export { default as Search } from './src/search';
export { default as CSVExport } from './src/csv'; export { default as CSVExport } from './src/csv';
export { default as ColumnToggle } from './src/column-toggle';

View File

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

View File

@@ -0,0 +1,3 @@
import ToggleList from './toggle-list';
export default { ToggleList };

View File

@@ -0,0 +1,50 @@
import React from 'react';
import PropTypes from 'prop-types';
const ToggleList = ({
columns,
onColumnToggle,
toggles,
contextual,
className,
btnClassName
}) => (
<div className={ `btn-group btn-group-toggle ${className}` } data-toggle="buttons">
{
columns
.map(column => ({
...column,
toggle: toggles[column.dataField]
}))
.map(column => (
<button
type="button"
key={ column.dataField }
className={ `${btnClassName} btn btn-${contextual} ${column.toggle ? 'active' : ''}` }
data-toggle="button"
aria-pressed={ column.toggle ? 'true' : 'false' }
onClick={ () => onColumnToggle(column.dataField) }
>
{ column.text }
</button>
))
}
</div>
);
ToggleList.propTypes = {
columns: PropTypes.array.isRequired,
toggles: PropTypes.object.isRequired,
onColumnToggle: PropTypes.func.isRequired,
btnClassName: PropTypes.string,
className: PropTypes.string,
contextual: PropTypes.string
};
ToggleList.defaultProps = {
btnClassName: '',
className: '',
contextual: 'primary'
};
export default ToggleList;

View File

@@ -5,12 +5,14 @@ const ExportCSVButton = (props) => {
const { const {
onExport, onExport,
children, children,
className,
...rest ...rest
} = props; } = props;
return ( return (
<button <button
type="button" type="button"
className={ `react-bs-table-csv-btn btn btn-default ${className}` }
onClick={ () => onExport() } onClick={ () => onExport() }
{ ...rest } { ...rest }
> >
@@ -26,7 +28,7 @@ ExportCSVButton.propTypes = {
style: PropTypes.object style: PropTypes.object
}; };
ExportCSVButton.defaultProps = { ExportCSVButton.defaultProps = {
className: 'react-bs-table-csv-btn btn btn-default', className: '',
style: {} style: {}
}; };

View File

@@ -25,13 +25,19 @@ export default Base =>
let data; let data;
if (typeof source !== 'undefined') { if (typeof source !== 'undefined') {
data = source; data = source;
} else if (options.exportAll) {
data = this.props.data;
} else { } else {
data = options.exportAll ? this.props.data : this.getData(); const payload = {};
this.tableExposedAPIEmitter.emit('get.table.data', payload);
data = payload.result;
} }
// filter data // filter data
if (options.onlyExportSelection) { 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)); data = data.filter(row => !!selections.find(sel => row[keyField] === sel));
} }
const content = transform(data, meta, this._.get, options); const content = transform(data, meta, this._.get, options);

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,4 @@
import SearchBar from './SearchBar'; 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 => export default Base =>
class StatelessOperation extends Operation.csvOperation(Base) { class StatelessOperation extends Operation.csvOperation(Base) {
registerExposedAPI = (...exposedFuncs) => { registerExposedAPI = (tableExposedAPIEmitter) => {
exposedFuncs.forEach((func) => { this.tableExposedAPIEmitter = tableExposedAPIEmitter;
this[func.name] = func;
});
} }
}; };

View File

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

View File

@@ -15,9 +15,36 @@ import withRowExpansion from './row-expand/row-consumer';
class Body extends React.Component { class Body extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
if (props.cellEdit.createContext) { const {
this.EditingCell = props.cellEdit.createEditingCell(_, props.cellEdit.options.onStartEdit); 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() { render() {
@@ -46,21 +73,12 @@ class Body extends React.Component {
} }
content = <RowSection content={ indication } colSpan={ visibleColumnSize } />; content = <RowSection content={ indication } colSpan={ visibleColumnSize } />;
} else { } else {
let RowComponent = SimpleRow;
const selectRowEnabled = selectRow.mode !== Const.ROW_SELECT_DISABLED; const selectRowEnabled = selectRow.mode !== Const.ROW_SELECT_DISABLED;
const expandRowEnabled = !!expandRow.renderer; const expandRowEnabled = !!expandRow.renderer;
const additionalRowProps = {}; const additionalRowProps = {};
if (expandRowEnabled) {
RowComponent = withRowExpansion(RowAggregator, visibleColumnSize);
}
if (selectRowEnabled) {
RowComponent = withRowSelection(expandRowEnabled ? RowComponent : RowAggregator);
}
if (cellEdit.createContext) { if (cellEdit.createContext) {
RowComponent = cellEdit.withRowLevelCellEdit(RowComponent, selectRowEnabled, keyField, _);
additionalRowProps.EditingCellComponent = this.EditingCell; additionalRowProps.EditingCellComponent = this.EditingCell;
} }
@@ -88,7 +106,7 @@ class Body extends React.Component {
baseRowProps.style = _.isFunction(rowStyle) ? rowStyle(row, index) : rowStyle; baseRowProps.style = _.isFunction(rowStyle) ? rowStyle(row, index) : rowStyle;
baseRowProps.className = (_.isFunction(rowClasses) ? rowClasses(row, index) : rowClasses); 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 Header from './header';
import Caption from './caption'; import Caption from './caption';
import Body from './body'; import Body from './body';
import Footer from './footer';
import PropsBaseResolver from './props-resolver'; import PropsBaseResolver from './props-resolver';
import Const from './const'; import Const from './const';
import _ from './utils';
class BootstrapTable extends PropsBaseResolver(Component) { class BootstrapTable extends PropsBaseResolver(Component) {
constructor(props) { constructor(props) {
super(props); super(props);
this.validateProps(); this.validateProps();
if (props.registerExposedAPI) {
props.registerExposedAPI(this.getData);
}
} }
// Exposed APIs // Exposed APIs
getData() { getData = () => {
return this.visibleRows(); return this.visibleRows();
} }
@@ -66,9 +65,11 @@ class BootstrapTable extends PropsBaseResolver(Component) {
'table-striped': striped, 'table-striped': striped,
'table-hover': hover, 'table-hover': hover,
'table-bordered': bordered, 'table-bordered': bordered,
[(bootstrap4 ? 'table-sm' : 'table-condensed')]: condensed [bootstrap4 ? 'table-sm' : 'table-condensed']: condensed
}, classes); }, classes);
const hasFooter = _.filter(columns, col => _.has(col, 'footer')).length > 0;
const tableCaption = (caption && <Caption>{ caption }</Caption>); const tableCaption = (caption && <Caption>{ caption }</Caption>);
return ( return (
@@ -101,6 +102,15 @@ class BootstrapTable extends PropsBaseResolver(Component) {
rowClasses={ rowClasses } rowClasses={ rowClasses }
rowEvents={ rowEvents } rowEvents={ rowEvents }
/> />
{hasFooter && (
<Footer
data={ this.getData() }
columns={ columns }
selectRow={ selectRow }
expandRow={ expandRow }
className={ this.props.footerClasses }
/>
)}
</table> </table>
</div> </div>
); );
@@ -161,12 +171,17 @@ BootstrapTable.propTypes = {
onlyOneExpanding: PropTypes.bool, onlyOneExpanding: PropTypes.bool,
expandByColumnOnly: PropTypes.bool, expandByColumnOnly: PropTypes.bool,
expandColumnRenderer: PropTypes.func, 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]), rowStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
rowEvents: PropTypes.object, rowEvents: PropTypes.object,
rowClasses: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), rowClasses: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
headerClasses: PropTypes.string, headerClasses: PropTypes.string,
footerClasses: PropTypes.string,
defaultSorted: PropTypes.arrayOf(PropTypes.shape({ defaultSorted: PropTypes.arrayOf(PropTypes.shape({
dataField: PropTypes.string.isRequired, dataField: PropTypes.string.isRequired,
order: PropTypes.oneOf([Const.SORT_DESC, Const.SORT_ASC]).isRequired order: PropTypes.oneOf([Const.SORT_DESC, Const.SORT_ASC]).isRequired

View File

@@ -23,7 +23,10 @@ class Cell extends eventDelegater(Component) {
if (shouldUpdate) return true; if (shouldUpdate) return true;
// if (nextProps.formatter)
shouldUpdate = shouldUpdate =
nextProps.column.formatter ? !_.isEqual(this.props.row, nextProps.row) : false ||
this.props.column.hidden !== nextProps.column.hidden || this.props.column.hidden !== nextProps.column.hidden ||
this.props.rowIndex !== nextProps.rowIndex || this.props.rowIndex !== nextProps.rowIndex ||
this.props.columnIndex !== nextProps.columnIndex || this.props.columnIndex !== nextProps.columnIndex ||

View File

@@ -6,5 +6,7 @@ export default {
ROW_SELECT_DISABLED: 'ROW_SELECT_DISABLED', ROW_SELECT_DISABLED: 'ROW_SELECT_DISABLED',
CHECKBOX_STATUS_CHECKED: 'checked', CHECKBOX_STATUS_CHECKED: 'checked',
CHECKBOX_STATUS_INDETERMINATE: 'indeterminate', CHECKBOX_STATUS_INDETERMINATE: 'indeterminate',
CHECKBOX_STATUS_UNCHECKED: 'unchecked' CHECKBOX_STATUS_UNCHECKED: 'unchecked',
INDICATOR_POSITION_LEFT: 'left',
INDICATOR_POSITION_RIGHT: 'right'
}; };

View File

@@ -0,0 +1,39 @@
/* eslint react/prop-types: 0 */
/* eslint react/prefer-stateless-function: 0 */
import React from 'react';
import PropTypes from 'prop-types';
export default () => {
const ColumnManagementContext = React.createContext();
class ColumnManagementProvider extends React.Component {
static propTypes = {
columns: PropTypes.array.isRequired,
toggles: PropTypes.object
}
static defaultProps = {
toggles: null
}
render() {
let toggleColumn;
const { columns, toggles } = this.props;
if (toggles) {
toggleColumn = columns.filter(column => toggles[column.dataField]);
} else {
toggleColumn = columns.filter(column => !column.hidden);
}
return (
<ColumnManagementContext.Provider value={ { columns: toggleColumn } }>
{ this.props.children }
</ColumnManagementContext.Provider>
);
}
}
return {
Provider: ColumnManagementProvider,
Consumer: ColumnManagementContext.Consumer
};
};

View File

@@ -1,8 +1,11 @@
/* eslint no-return-assign: 0 */ /* eslint no-return-assign: 0 */
/* eslint no-param-reassign: 0 */
/* eslint class-methods-use-this: 0 */ /* eslint class-methods-use-this: 0 */
import React, { Component } from 'react'; import React, { Component } from 'react';
import EventEmitter from 'events';
import _ from '../utils'; import _ from '../utils';
import createDataContext from './data-context'; import createDataContext from './data-context';
import createColumnMgtContext from './column-context';
import createSortContext from './sort-context'; import createSortContext from './sort-context';
import SelectionContext from './selection-context'; import SelectionContext from './selection-context';
import RowExpandContext from './row-expand-context'; import RowExpandContext from './row-expand-context';
@@ -16,11 +19,25 @@ const withContext = Base =>
super(props); super(props);
this.DataContext = createDataContext(); 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) { if (props.columns.filter(col => col.sort).length > 0) {
this.SortContext = createSortContext( this.SortContext = createSortContext(
dataOperator, this.isRemoteSort, this.handleRemoteSortChange); dataOperator, this.isRemoteSort, this.handleRemoteSortChange);
} }
if (
props.columnToggle ||
props.columns.filter(col => col.hidden).length > 0
) {
this.ColumnManagementContext = createColumnMgtContext();
}
if (props.selectRow) { if (props.selectRow) {
this.SelectionContext = SelectionContext; this.SelectionContext = SelectionContext;
} }
@@ -74,6 +91,7 @@ const withContext = Base =>
searchProps, searchProps,
sortProps, sortProps,
paginationProps, paginationProps,
columnToggleProps
) => ( ) => (
<Base <Base
ref={ n => this.table = n } ref={ n => this.table = n }
@@ -82,11 +100,40 @@ const withContext = Base =>
{ ...filterProps } { ...filterProps }
{ ...searchProps } { ...searchProps }
{ ...paginationProps } { ...paginationProps }
{ ...columnToggleProps }
data={ rootProps.getData(filterProps, searchProps, sortProps, paginationProps) } data={ rootProps.getData(filterProps, searchProps, sortProps, paginationProps) }
/> />
); );
} }
renderWithColumnManagementCtx(base, baseProps) {
return (
rootProps,
filterProps,
searchProps,
sortProps,
paginationProps
) => (
<this.ColumnManagementContext.Provider
{ ...baseProps }
toggles={ this.props.columnToggle ? this.props.columnToggle.toggles : null }
>
<this.ColumnManagementContext.Consumer>
{
columnToggleProps => base(
rootProps,
filterProps,
searchProps,
sortProps,
paginationProps,
columnToggleProps
)
}
</this.ColumnManagementContext.Consumer>
</this.ColumnManagementContext.Provider>
);
}
renderWithSelectionCtx(base, baseProps) { renderWithSelectionCtx(base, baseProps) {
return ( return (
rootProps, rootProps,
@@ -208,6 +255,7 @@ const withContext = Base =>
ref={ n => this.searchContext = n } ref={ n => this.searchContext = n }
data={ rootProps.getData(filterProps) } data={ rootProps.getData(filterProps) }
searchText={ this.props.search.searchText } searchText={ this.props.search.searchText }
dataChangeListener={ this.props.dataChangeListener }
> >
<this.SearchContext.Consumer> <this.SearchContext.Consumer>
{ {
@@ -228,6 +276,7 @@ const withContext = Base =>
{ ...baseProps } { ...baseProps }
ref={ n => this.filterContext = n } ref={ n => this.filterContext = n }
data={ rootProps.getData() } data={ rootProps.getData() }
dataChangeListener={ this.props.dataChangeListener }
> >
<this.FilterContext.Consumer> <this.FilterContext.Consumer>
{ {
@@ -245,6 +294,7 @@ const withContext = Base =>
return rootProps => ( return rootProps => (
<this.CellEditContext.Provider <this.CellEditContext.Provider
{ ...baseProps } { ...baseProps }
ref={ n => this.cellEditContext = n }
selectRow={ this.props.selectRow } selectRow={ this.props.selectRow }
cellEdit={ this.props.cellEdit } cellEdit={ this.props.cellEdit }
data={ rootProps.getData() } data={ rootProps.getData() }
@@ -255,12 +305,15 @@ const withContext = Base =>
} }
render() { render() {
const { keyField, columns, bootstrap4, registerExposedAPI } = this.props; const { keyField, columns, bootstrap4 } = this.props;
const baseProps = { keyField, columns }; const baseProps = { keyField, columns };
if (registerExposedAPI) baseProps.registerExposedAPI = registerExposedAPI;
let base = this.renderBase(); let base = this.renderBase();
if (this.ColumnManagementContext) {
base = this.renderWithColumnManagementCtx(base, baseProps);
}
if (this.SelectionContext) { if (this.SelectionContext) {
base = this.renderWithSelectionCtx(base, baseProps); base = this.renderWithSelectionCtx(base, baseProps);
} }

View File

@@ -16,32 +16,25 @@ class SelectionProvider extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
if (props.registerExposedAPI) { this.selected = props.selectRow.selected || [];
const getSelected = () => this.getSelected();
props.registerExposedAPI(getSelected);
} }
}
state = { selected: this.props.selectRow.selected || [] };
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if (nextProps.selectRow) { if (nextProps.selectRow) {
this.setState(() => ({ this.selected = nextProps.selectRow.selected || this.selected;
selected: nextProps.selectRow.selected || this.state.selected
}));
} }
} }
// exposed API // exposed API
getSelected() { getSelected() {
return this.state.selected; return this.selected;
} }
handleRowSelect = (rowKey, checked, rowIndex, e) => { handleRowSelect = (rowKey, checked, rowIndex, e) => {
const { data, keyField, selectRow: { mode, onSelect } } = this.props; const { data, keyField, selectRow: { mode, onSelect } } = this.props;
const { ROW_SELECT_SINGLE } = Const; const { ROW_SELECT_SINGLE } = Const;
let currSelected = [...this.state.selected]; let currSelected = [...this.selected];
let result = true; let result = true;
if (onSelect) { if (onSelect) {
@@ -49,7 +42,6 @@ class SelectionProvider extends React.Component {
result = onSelect(row, checked, rowIndex, e); result = onSelect(row, checked, rowIndex, e);
} }
this.setState(() => {
if (result === true || result === undefined) { if (result === true || result === undefined) {
if (mode === ROW_SELECT_SINGLE) { // when select mode is radio if (mode === ROW_SELECT_SINGLE) { // when select mode is radio
currSelected = [rowKey]; currSelected = [rowKey];
@@ -59,8 +51,8 @@ class SelectionProvider extends React.Component {
currSelected = currSelected.filter(value => value !== rowKey); currSelected = currSelected.filter(value => value !== rowKey);
} }
} }
return { selected: currSelected }; this.selected = currSelected;
}); this.forceUpdate();
} }
handleAllRowsSelect = (e, isUnSelect) => { handleAllRowsSelect = (e, isUnSelect) => {
@@ -72,7 +64,7 @@ class SelectionProvider extends React.Component {
nonSelectable nonSelectable
} }
} = this.props; } = this.props;
const { selected } = this.state; const { selected } = this;
let currSelected; let currSelected;
@@ -89,7 +81,7 @@ class SelectionProvider extends React.Component {
dataOperator.getSelectedRows( dataOperator.getSelectedRows(
data, data,
keyField, keyField,
isUnSelect ? this.state.selected : currSelected isUnSelect ? selected : currSelected
), ),
e e
); );
@@ -97,7 +89,8 @@ class SelectionProvider extends React.Component {
currSelected = result; currSelected = result;
} }
} }
this.setState(() => ({ selected: currSelected })); this.selected = currSelected;
this.forceUpdate();
} }
render() { render() {
@@ -107,7 +100,7 @@ class SelectionProvider extends React.Component {
} = getSelectionSummary( } = getSelectionSummary(
this.props.data, this.props.data,
this.props.keyField, this.props.keyField,
this.state.selected this.selected
); );
let checkedStatus; let checkedStatus;
@@ -121,7 +114,7 @@ class SelectionProvider extends React.Component {
<SelectionContext.Provider <SelectionContext.Provider
value={ { value={ {
...this.props.selectRow, ...this.props.selectRow,
selected: this.state.selected, selected: this.selected,
onRowSelect: this.handleRowSelect, onRowSelect: this.handleRowSelect,
onAllRowsSelect: this.handleAllRowsSelect, onAllRowsSelect: this.handleAllRowsSelect,
allRowsSelected, allRowsSelected,

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) {
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 ExpandHeaderCell from './row-expand/expand-header-cell';
import withHeaderSelection from './row-selection/selection-header-cell-consumer'; import withHeaderSelection from './row-selection/selection-header-cell-consumer';
import withHeaderExpansion from './row-expand/expand-header-cell-consumer'; import withHeaderExpansion from './row-expand/expand-header-cell-consumer';
import Const from './const';
const Header = (props) => { const Header = (props) => {
const { const {
@@ -32,17 +33,12 @@ const Header = (props) => {
SelectionHeaderCellComp = withHeaderSelection(SelectionHeaderCell); SelectionHeaderCellComp = withHeaderSelection(SelectionHeaderCell);
} }
return ( const isRenderExpandColumnInLeft = (
<thead> expandColumnPosition = Const.INDICATOR_POSITION_LEFT
<tr className={ className }> ) => expandColumnPosition === Const.INDICATOR_POSITION_LEFT;
<ExpansionHeaderCellComp />
{ const childrens = [
!selectRow.hideSelectColumn ?
<SelectionHeaderCellComp /> : null
}
{
columns.map((column, i) => { columns.map((column, i) => {
if (!column.hidden) {
const currSort = column.dataField === sortField; const currSort = column.dataField === sortField;
const isLastSorting = column.dataField === sortField; const isLastSorting = column.dataField === sortField;
@@ -58,10 +54,25 @@ const Header = (props) => {
sortOrder={ sortOrder } sortOrder={ sortOrder }
isLastSorting={ isLastSorting } 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 }>
{ childrens }
</tr> </tr>
</thead> </thead>
); );

View File

@@ -63,6 +63,7 @@ export default class SelectionCell extends Component {
selected, selected,
disabled, disabled,
tabIndex, tabIndex,
rowIndex,
selectionRenderer selectionRenderer
} = this.props; } = this.props;
@@ -78,7 +79,8 @@ export default class SelectionCell extends Component {
selectionRenderer ? selectionRenderer({ selectionRenderer ? selectionRenderer({
mode: inputType, mode: inputType,
checked: selected, checked: selected,
disabled disabled,
rowIndex
}) : ( }) : (
<input <input
type={ inputType } type={ inputType }

View File

@@ -1,3 +1,4 @@
/* eslint class-methods-use-this: 0 */
/* eslint react/prop-types: 0 */ /* eslint react/prop-types: 0 */
/* eslint no-plusplus: 0 */ /* eslint no-plusplus: 0 */
import React from 'react'; import React from 'react';
@@ -8,6 +9,7 @@ import SelectionCell from '../row-selection/selection-cell';
import shouldUpdater from './should-updater'; import shouldUpdater from './should-updater';
import eventDelegater from './event-delegater'; import eventDelegater from './event-delegater';
import RowPureContent from './row-pure-content'; import RowPureContent from './row-pure-content';
import Const from '../const';
export default class RowAggregator extends shouldUpdater(eventDelegater(React.Component)) { export default class RowAggregator extends shouldUpdater(eventDelegater(React.Component)) {
static propTypes = { static propTypes = {
@@ -43,6 +45,12 @@ export default class RowAggregator extends shouldUpdater(eventDelegater(React.Co
return this.shouldUpdateRowContent; return this.shouldUpdateRowContent;
} }
isRenderExpandColumnInLeft(
expandColumnPosition = Const.INDICATOR_POSITION_LEFT
) {
return expandColumnPosition === Const.INDICATOR_POSITION_LEFT;
}
render() { render() {
const { const {
row, row,
@@ -64,7 +72,7 @@ export default class RowAggregator extends shouldUpdater(eventDelegater(React.Co
} = this.props; } = this.props;
const key = _.get(row, keyField); const key = _.get(row, keyField);
const { hideSelectColumn, clickToSelect } = selectRow; const { hideSelectColumn, clickToSelect } = selectRow;
const { showExpandColumn } = expandRow; const { showExpandColumn, expandColumnPosition } = expandRow;
const newAttrs = this.delegate({ ...attrs }); const newAttrs = this.delegate({ ...attrs });
if (clickToSelect || !!expandRow.renderer) { if (clickToSelect || !!expandRow.renderer) {
@@ -73,39 +81,9 @@ export default class RowAggregator extends shouldUpdater(eventDelegater(React.Co
let tabIndexStart = (rowIndex * visibleColumnSize) + 1; let tabIndexStart = (rowIndex * visibleColumnSize) + 1;
return ( const childrens = [(
<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 <RowPureContent
key="row"
row={ row } row={ row }
columns={ columns } columns={ columns }
keyField={ keyField } keyField={ keyField }
@@ -114,6 +92,48 @@ export default class RowAggregator extends shouldUpdater(eventDelegater(React.Co
tabIndexStart={ tabIndexCell ? tabIndexStart : -1 } tabIndexStart={ tabIndexCell ? tabIndexStart : -1 }
{ ...rest } { ...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 }
>
{ childrens }
</tr> </tr>
); );
} }

View File

@@ -33,7 +33,6 @@ export default class RowPureContent extends React.Component {
let tabIndex = tabIndexStart; let tabIndex = tabIndexStart;
return columns.map((column, index) => { return columns.map((column, index) => {
if (!column.hidden) {
const { dataField } = column; const { dataField } = column;
const content = _.get(row, dataField); const content = _.get(row, dataField);
if (rowIndex === editingRowIdx && index === editingColIdx) { if (rowIndex === editingRowIdx && index === editingColIdx) {
@@ -117,8 +116,6 @@ export default class RowPureContent extends React.Component {
{ ...cellAttrs } { ...cellAttrs }
/> />
); );
}
return false;
}); });
} }
} }

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) { shouldUpdatedByNormalProps(nextProps) {
const shouldUpdate = const shouldUpdate =
this.props.rowIndex !== nextProps.rowIndex || this.props.rowIndex !== nextProps.rowIndex ||

View File

@@ -15,7 +15,8 @@ class SimpleRow extends shouldUpdater(eventDelegater(Component)) {
shouldComponentUpdate(nextProps) { shouldComponentUpdate(nextProps) {
this.shouldUpdateRowContent = false; this.shouldUpdateRowContent = false;
this.shouldUpdateRowContent = this.shouldUpdateChild(nextProps); this.shouldUpdateRowContent =
this.shouldUpdateChild(nextProps) || this.shouldUpdateByColumnsForSimpleCheck(nextProps);
if (this.shouldUpdateRowContent) return true; if (this.shouldUpdateRowContent) return true;
return this.shouldUpdatedBySelfProps(nextProps); 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', () => { describe('when props.classes was defined', () => {
const classes = 'foo'; const classes = 'foo';

View File

@@ -218,6 +218,47 @@ describe('Cell', () => {
}); });
}); });
describe('when props.row is change', () => {
describe('and column.formatter is enable', () => {
const column = { dataField: 'name', text: 'Product Name', formatter: () => 123 };
beforeEach(() => {
props = {
row,
columnIndex: 1,
rowIndex: 1,
tabIndex: 5,
column
};
wrapper = shallow(
<Cell { ...props } />);
});
it('should return true', () => {
nextProps = { ...props, row: { ...row, alert: 'test' } };
expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(true);
});
});
describe('but column.formatter is disable', () => {
const column = { dataField: 'name', text: 'Product Name' };
beforeEach(() => {
props = {
row,
columnIndex: 1,
rowIndex: 1,
tabIndex: 5,
column
};
wrapper = shallow(
<Cell { ...props } />);
});
it('should return true', () => {
nextProps = { ...props, row: { ...row, alert: 'test' } };
expect(wrapper.instance().shouldComponentUpdate(nextProps)).toBe(false);
});
});
});
describe('if column.isDummyField is true', () => { describe('if column.isDummyField is true', () => {
describe('when content is change', () => { describe('when content is change', () => {
const column = { dataField: '', text: 'Product Name', isDummyField: true }; const column = { dataField: '', text: 'Product Name', isDummyField: true };

View File

@@ -0,0 +1,104 @@
import 'jsdom-global/register';
import React from 'react';
import { shallow } from 'enzyme';
import BootstrapTable from '../../src/bootstrap-table';
import createColumnManagementContext from '../../src/contexts/column-context';
describe('ColumnManagementContext', () => {
let wrapper;
const data = [{
id: 1,
name: 'A'
}, {
id: 2,
name: 'B'
}];
const columns = [{
dataField: 'id',
text: 'ID'
}, {
dataField: 'name',
text: 'Name'
}];
const mockBase = jest.fn((props => (
<BootstrapTable
data={ data }
columns={ columns }
keyField="id"
{ ...props }
/>
)));
const ColumnManagementContext = createColumnManagementContext();
function shallowContext(options = {}) {
return (
<ColumnManagementContext.Provider
data={ data }
columns={ columns }
{ ...options }
>
<ColumnManagementContext.Consumer>
{
columnToggleProps => mockBase(columnToggleProps)
}
</ColumnManagementContext.Consumer>
</ColumnManagementContext.Provider>
);
}
describe('default render', () => {
beforeEach(() => {
wrapper = shallow(shallowContext());
wrapper.render();
});
it('should have correct Provider property after calling createColumnManagementContext', () => {
expect(ColumnManagementContext.Provider).toBeDefined();
});
it('should have correct Consumer property after calling createColumnManagementContext', () => {
expect(ColumnManagementContext.Consumer).toBeDefined();
});
});
describe('when toggles props exist', () => {
beforeEach(() => {
wrapper = shallow(shallowContext({
toggles: {
id: true,
name: false
}
}));
});
it('should render component with correct columns props', () => {
expect(wrapper.prop('value').columns).toHaveLength(columns.length - 1);
expect(wrapper.prop('value').columns[0].dataField).toEqual('id');
});
});
describe('if there is any column.hidden is true', () => {
beforeEach(() => {
wrapper = shallow(shallowContext({
columns: [{
dataField: 'id',
text: 'ID'
}, {
dataField: 'name',
text: 'Name',
hidden: true
}]
}));
});
it('should render component with correct columns props', () => {
expect(wrapper.prop('value').columns).toHaveLength(columns.length - 1);
expect(wrapper.prop('value').columns[0].dataField).toEqual('id');
});
});
});

View File

@@ -47,6 +47,7 @@ describe('Context', () => {
expect(wrapper.instance().CellEditContext).not.toBeDefined(); expect(wrapper.instance().CellEditContext).not.toBeDefined();
expect(wrapper.instance().FilterContext).not.toBeDefined(); expect(wrapper.instance().FilterContext).not.toBeDefined();
expect(wrapper.instance().PaginationContext).not.toBeDefined(); expect(wrapper.instance().PaginationContext).not.toBeDefined();
expect(wrapper.instance().ColumnManagementContext).not.toBeDefined();
}); });
it('should render correctly', () => { it('should render correctly', () => {
@@ -77,6 +78,57 @@ describe('Context', () => {
expect(wrapper.instance().CellEditContext).not.toBeDefined(); expect(wrapper.instance().CellEditContext).not.toBeDefined();
expect(wrapper.instance().FilterContext).not.toBeDefined(); expect(wrapper.instance().FilterContext).not.toBeDefined();
expect(wrapper.instance().PaginationContext).not.toBeDefined(); expect(wrapper.instance().PaginationContext).not.toBeDefined();
expect(wrapper.instance().ColumnManagementContext).not.toBeDefined();
});
});
describe('if thers\'s any column hidden', () => {
beforeEach(() => {
const columnsWithHidden = [{
dataField: keyField,
text: 'ID'
}, {
dataField: 'name',
text: 'Name',
hidden: true
}];
wrapper = shallow(
<BootstrapTable keyField={ keyField } data={ data } columns={ columnsWithHidden } />
);
wrapper.render();
});
it('should create contexts correctly', () => {
expect(wrapper.instance().DataContext).toBeDefined();
expect(wrapper.instance().ColumnManagementContext).toBeDefined();
expect(wrapper.instance().SelectionContext).not.toBeDefined();
expect(wrapper.instance().CellEditContext).not.toBeDefined();
expect(wrapper.instance().FilterContext).not.toBeDefined();
expect(wrapper.instance().PaginationContext).not.toBeDefined();
});
});
describe('if columnToggle is enable', () => {
beforeEach(() => {
const columnToggle = { toggles: { id: true, name: true } };
wrapper = shallow(
<BootstrapTable
keyField={ keyField }
data={ data }
columns={ columns }
columnToggle={ columnToggle }
/>
);
wrapper.render();
});
it('should create contexts correctly', () => {
expect(wrapper.instance().DataContext).toBeDefined();
expect(wrapper.instance().ColumnManagementContext).toBeDefined();
expect(wrapper.instance().SelectionContext).not.toBeDefined();
expect(wrapper.instance().CellEditContext).not.toBeDefined();
expect(wrapper.instance().FilterContext).not.toBeDefined();
expect(wrapper.instance().PaginationContext).not.toBeDefined();
}); });
}); });
@@ -101,6 +153,7 @@ describe('Context', () => {
expect(wrapper.instance().CellEditContext).not.toBeDefined(); expect(wrapper.instance().CellEditContext).not.toBeDefined();
expect(wrapper.instance().FilterContext).not.toBeDefined(); expect(wrapper.instance().FilterContext).not.toBeDefined();
expect(wrapper.instance().PaginationContext).not.toBeDefined(); expect(wrapper.instance().PaginationContext).not.toBeDefined();
expect(wrapper.instance().ColumnManagementContext).not.toBeDefined();
}); });
}); });
@@ -134,6 +187,7 @@ describe('Context', () => {
expect(wrapper.instance().CellEditContext).toBeDefined(); expect(wrapper.instance().CellEditContext).toBeDefined();
expect(wrapper.instance().FilterContext).not.toBeDefined(); expect(wrapper.instance().FilterContext).not.toBeDefined();
expect(wrapper.instance().PaginationContext).not.toBeDefined(); expect(wrapper.instance().PaginationContext).not.toBeDefined();
expect(wrapper.instance().ColumnManagementContext).not.toBeDefined();
}); });
}); });
@@ -163,6 +217,7 @@ describe('Context', () => {
expect(wrapper.instance().CellEditContext).not.toBeDefined(); expect(wrapper.instance().CellEditContext).not.toBeDefined();
expect(wrapper.instance().FilterContext).not.toBeDefined(); expect(wrapper.instance().FilterContext).not.toBeDefined();
expect(wrapper.instance().PaginationContext).not.toBeDefined(); expect(wrapper.instance().PaginationContext).not.toBeDefined();
expect(wrapper.instance().ColumnManagementContext).not.toBeDefined();
}); });
}); });
@@ -193,6 +248,7 @@ describe('Context', () => {
expect(wrapper.instance().CellEditContext).not.toBeDefined(); expect(wrapper.instance().CellEditContext).not.toBeDefined();
expect(wrapper.instance().FilterContext).toBeDefined(); expect(wrapper.instance().FilterContext).toBeDefined();
expect(wrapper.instance().PaginationContext).not.toBeDefined(); expect(wrapper.instance().PaginationContext).not.toBeDefined();
expect(wrapper.instance().ColumnManagementContext).not.toBeDefined();
}); });
}); });
@@ -223,6 +279,34 @@ describe('Context', () => {
expect(wrapper.instance().CellEditContext).not.toBeDefined(); expect(wrapper.instance().CellEditContext).not.toBeDefined();
expect(wrapper.instance().FilterContext).not.toBeDefined(); expect(wrapper.instance().FilterContext).not.toBeDefined();
expect(wrapper.instance().PaginationContext).toBeDefined(); expect(wrapper.instance().PaginationContext).toBeDefined();
expect(wrapper.instance().ColumnManagementContext).not.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

@@ -73,15 +73,15 @@ describe('DataContext', () => {
expect(SelectionContext.Consumer).toBeDefined(); expect(SelectionContext.Consumer).toBeDefined();
}); });
it('should have correct state.data', () => { it('should have correct this.selected', () => {
expect(wrapper.state().selected).toEqual([]); expect(wrapper.instance().selected).toEqual([]);
}); });
it('should pass correct sort props to children element', () => { it('should pass correct sort props to children element', () => {
expect(wrapper.length).toBe(1); expect(wrapper.length).toBe(1);
expect(mockBase).toHaveBeenCalledWith({ expect(mockBase).toHaveBeenCalledWith({
...defaultSelectRow, ...defaultSelectRow,
selected: wrapper.state().selected, selected: wrapper.instance().selected,
onRowSelect: wrapper.instance().handleRowSelect, onRowSelect: wrapper.instance().handleRowSelect,
onAllRowsSelect: wrapper.instance().handleAllRowsSelect, onAllRowsSelect: wrapper.instance().handleAllRowsSelect,
allRowsNotSelected: true, allRowsNotSelected: true,
@@ -104,8 +104,8 @@ describe('DataContext', () => {
}); });
}); });
it('should have correct state.selected', () => { it('should have correct this.selected', () => {
expect(wrapper.state().selected).toEqual(newSelectRow.selected); expect(wrapper.instance().selected).toEqual(newSelectRow.selected);
}); });
describe('if nextProps.selectRow is not existing', () => { describe('if nextProps.selectRow is not existing', () => {
@@ -120,8 +120,8 @@ describe('DataContext', () => {
}); });
}); });
it('should keep origin state.selected', () => { it('should keep origin this.selected', () => {
expect(wrapper.state().selected).toEqual(defaultSelected); expect(wrapper.instance().selected).toEqual(defaultSelected);
}); });
}); });
@@ -131,8 +131,8 @@ describe('DataContext', () => {
wrapper.instance().componentWillReceiveProps({}); wrapper.instance().componentWillReceiveProps({});
}); });
it('should not set state.selected', () => { it('should not set this.selected', () => {
expect(wrapper.state().selected).toEqual([]); expect(wrapper.instance().selected).toEqual([]);
}); });
}); });
}); });
@@ -148,8 +148,8 @@ describe('DataContext', () => {
wrapper = shallow(shallowContext(selectRow)); wrapper = shallow(shallowContext(selectRow));
}); });
it('should have correct state.data', () => { it('should have correct this.selected', () => {
expect(wrapper.state().selected).toEqual(selectRow.selected); expect(wrapper.instance().selected).toEqual(selectRow.selected);
}); });
}); });
@@ -164,12 +164,12 @@ describe('DataContext', () => {
wrapper = shallow(shallowContext(selectRow)); wrapper = shallow(shallowContext(selectRow));
}); });
it('should set state.selected correctly', () => { it('should set this.selected correctly', () => {
wrapper.instance().handleRowSelect(firstSelectedRow, true, rowIndex); wrapper.instance().handleRowSelect(firstSelectedRow, true, rowIndex);
expect(wrapper.state('selected')).toEqual([firstSelectedRow]); expect(wrapper.instance().selected).toEqual([firstSelectedRow]);
wrapper.instance().handleRowSelect(secondSelectedRow, true, rowIndex); wrapper.instance().handleRowSelect(secondSelectedRow, true, rowIndex);
expect(wrapper.state('selected')).toEqual([secondSelectedRow]); expect(wrapper.instance().selected).toEqual([secondSelectedRow]);
}); });
}); });
@@ -178,18 +178,19 @@ describe('DataContext', () => {
wrapper = shallow(shallowContext()); wrapper = shallow(shallowContext());
}); });
it('should set state.selected correctly', () => { it('should set this.selected correctly', () => {
wrapper.instance().handleRowSelect(firstSelectedRow, true, rowIndex); wrapper.instance().handleRowSelect(firstSelectedRow, true, rowIndex);
expect(wrapper.state('selected')).toEqual(expect.arrayContaining([firstSelectedRow])); expect(wrapper.instance().selected).toEqual(expect.arrayContaining([firstSelectedRow]));
wrapper.instance().handleRowSelect(secondSelectedRow, true, rowIndex); wrapper.instance().handleRowSelect(secondSelectedRow, true, rowIndex);
expect(wrapper.state('selected')).toEqual(expect.arrayContaining([firstSelectedRow, secondSelectedRow])); expect(wrapper.instance().selected)
.toEqual(expect.arrayContaining([firstSelectedRow, secondSelectedRow]));
wrapper.instance().handleRowSelect(firstSelectedRow, false, rowIndex); wrapper.instance().handleRowSelect(firstSelectedRow, false, rowIndex);
expect(wrapper.state('selected')).toEqual(expect.arrayContaining([secondSelectedRow])); expect(wrapper.instance().selected).toEqual(expect.arrayContaining([secondSelectedRow]));
wrapper.instance().handleRowSelect(secondSelectedRow, false, rowIndex); wrapper.instance().handleRowSelect(secondSelectedRow, false, rowIndex);
expect(wrapper.state('selected')).toEqual([]); expect(wrapper.instance().selected).toEqual([]);
}); });
}); });
@@ -220,8 +221,8 @@ describe('DataContext', () => {
wrapper.instance().handleAllRowsSelect(e, false); wrapper.instance().handleAllRowsSelect(e, false);
}); });
it('should set state.selected correctly', () => { it('should set this.selected correctly', () => {
expect(wrapper.state('selected')).toEqual(data.map(d => d[keyField])); expect(wrapper.instance().selected).toEqual(data.map(d => d[keyField]));
}); });
describe('when selectRow.onSelectAll is defined', () => { describe('when selectRow.onSelectAll is defined', () => {
@@ -237,7 +238,7 @@ describe('DataContext', () => {
it('should call selectRow.onSelectAll correctly', () => { it('should call selectRow.onSelectAll correctly', () => {
expect(onSelectAll).toHaveBeenCalledWith( expect(onSelectAll).toHaveBeenCalledWith(
true, true,
dataOperator.getSelectedRows(data, keyField, wrapper.state('selected')), dataOperator.getSelectedRows(data, keyField, wrapper.instance().selected),
e e
); );
}); });
@@ -253,8 +254,8 @@ describe('DataContext', () => {
wrapper.instance().handleAllRowsSelect(e, true); wrapper.instance().handleAllRowsSelect(e, true);
}); });
it('should set state.selected correctly', () => { it('should set this.selected correctly', () => {
expect(wrapper.state('selected')).toEqual([]); expect(wrapper.instance().selected).toEqual([]);
}); });
describe('when selectRow.onSelectAll is defined', () => { describe('when selectRow.onSelectAll is defined', () => {

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

@@ -146,29 +146,6 @@ describe('Header', () => {
}); });
}); });
describe('when column.hidden is true', () => {
beforeEach(() => {
const newColumns = [{
dataField: 'id',
text: 'ID',
hidden: true
}, {
dataField: 'name',
text: 'Name'
}];
wrapper = shallow(
<Header
{ ...mockHeaderResolvedProps }
columns={ newColumns }
/>
);
});
it('should not render column with hidden value true', () => {
expect(wrapper.find(HeaderCell).length).toBe(1);
});
});
describe('when selectRow.mode is checkbox (multiple selection)', () => { describe('when selectRow.mode is checkbox (multiple selection)', () => {
beforeEach(() => { beforeEach(() => {
const selectRow = { mode: 'checkbox' }; const selectRow = { mode: 'checkbox' };
@@ -254,5 +231,33 @@ describe('Header', () => {
expect(wrapper.find(ExpandHeaderCell).length).toBe(1); 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

@@ -295,7 +295,8 @@ describe('<SelectionCell />', () => {
expect(selectionRenderer).toHaveBeenCalledWith({ expect(selectionRenderer).toHaveBeenCalledWith({
mode, mode,
checked: selected, checked: selected,
disabled: wrapper.prop('disabled') disabled: wrapper.prop('disabled'),
rowIndex
}); });
}); });
}); });

View File

@@ -9,6 +9,7 @@ import bindExpansion from '../../src/row-expand/row-consumer';
import ExpandCell from '../../src/row-expand/expand-cell'; import ExpandCell from '../../src/row-expand/expand-cell';
import SelectionCell from '../../src/row-selection/selection-cell'; import SelectionCell from '../../src/row-selection/selection-cell';
import RowAggregator from '../../src/row/aggregate-row'; import RowAggregator from '../../src/row/aggregate-row';
import Const from '../../src/const';
describe('Row Aggregator', () => { describe('Row Aggregator', () => {
let wrapper; let wrapper;
@@ -157,6 +158,27 @@ describe('Row Aggregator', () => {
expect(expandCell.props().expanded).toEqual(rowAggregator.props().expanded); 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', () => { describe('createClickEventHandler', () => {

View File

@@ -173,33 +173,6 @@ describe('RowPureContent', () => {
}); });
}); });
describe('when column.hidden is true', () => {
beforeEach(() => {
const newColumns = [{
dataField: 'id',
text: 'ID',
hidden: true
}, {
dataField: 'name',
text: 'Name'
}, {
dataField: 'price',
text: 'Price'
}];
wrapper = shallow(
<RowPureContent
keyField={ keyField }
rowIndex={ rowIndex }
columns={ newColumns }
row={ row }
/>);
});
it('should not render column with hidden value true', () => {
expect(wrapper.find(Cell).length).toBe(2);
});
});
describe('when column.style prop is defined', () => { describe('when column.style prop is defined', () => {
let columns; let columns;
const columnIndex = 1; const columnIndex = 1;

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('shouldUpdatedByNormalProps', () => {
describe('when nextProps.rowIndex is not eq props.rowIndex', () => { describe('when nextProps.rowIndex is not eq props.rowIndex', () => {
beforeEach(() => { beforeEach(() => {