Compare commits

...

75 Commits

Author SHA1 Message Date
AllenFang
64df3e1fae Publish
- react-bootstrap-table2-example@0.1.9
 - react-bootstrap-table2-filter@0.2.0
 - react-bootstrap-table2-overlay@0.1.2
 - react-bootstrap-table2-paginator@0.1.4
 - react-bootstrap-table-next@0.1.13
2018-06-04 13:53:02 +08:00
Allen
5cdd1ad093 20180604 release #366 from react-bootstrap-table/develop
20180604 release
2018-06-04 13:49:03 +08:00
AllenFang
36e754b6bc patch docs 2018-06-03 22:58:03 +08:00
AllenFang
6730dcf60d fix overlay test bugs 2018-06-03 22:52:15 +08:00
AllenFang
fb54809dc9 fix docs 2018-06-03 21:36:09 +08:00
AllenFang
d43c622fdb fix docs 2018-06-03 21:31:13 +08:00
AllenFang
7253d7a1d7 fix overlay tests 2018-06-03 20:40:31 +08:00
AllenFang
a6daa50417 improve overlay wrapping 2018-06-03 15:50:10 +08:00
AllenFang
b11019ce20 fix #358 2018-06-03 14:49:58 +08:00
Allen
dda47f7b7d Merge pull request #365 from react-bootstrap-table/feat/349
Feat/349
2018-06-03 14:39:22 +08:00
AllenFang
4da8ba7ecc patch docs for date filter 2018-06-03 14:24:20 +08:00
Allen
2ff0b27747 Merge pull request #359 from sean-ww/feature/pagination-total
Adding custom pagination total
2018-06-03 14:18:50 +08:00
AllenFang
c3f279fb0c patch tests for date filter 2018-06-03 14:04:06 +08:00
AllenFang
06bcf1edca add stories for date filter 2018-06-03 14:03:38 +08:00
AllenFang
fc1f75cfac implement date filter 2018-06-03 14:02:39 +08:00
sean
1cf12ab707 paginationTotal renamed to paginationTotalRenderer 2018-06-02 10:43:56 +02:00
Allen
288ccc1049 Merge pull request #364 from react-bootstrap-table/feat/351
Fix #351
2018-06-02 15:37:51 +08:00
AllenFang
f13c139f63 patch docs for custom selection 2018-06-02 15:27:10 +08:00
AllenFang
e72ad0586e add story for custom selection column 2018-06-02 15:20:57 +08:00
AllenFang
c2044fe8b5 patch test for selection box 2018-06-02 15:20:40 +08:00
AllenFang
a7b3690a7c custom selection box 2018-06-02 15:20:19 +08:00
Amol Udage
68afc348db fixes sorting issue (#354)
+ when remote sort is true then disable client side sorting
2018-06-02 13:21:25 +08:00
sean-ww88
5404124a78 Added missing commas on the custom pagination example 2018-05-30 17:35:27 +02:00
sean-ww88
d592871c0d Adding custom pagination total 2018-05-30 17:29:58 +02:00
AllenFang
6019e550fd Merge branch 'develop' of https://github.com/react-bootstrap-table/react-bootstrap-table2 into develop 2018-05-28 18:07:31 +08:00
AllenFang
765a49fb07 Publish
- react-bootstrap-table-next@0.1.12
2018-05-23 22:44:40 +08:00
Allen
fe2fd93c20 fix the bool rendering issues in React (#340) (#348) 2018-05-23 22:42:46 +08:00
Allen
19ba336e32 gix the bool rendering issues in React (#340) 2018-05-19 13:07:57 +08:00
AllenFang
a50148fe85 Publish
- react-bootstrap-table2-example@0.1.8
 - react-bootstrap-table2-filter@0.1.7
 - react-bootstrap-table-next@0.1.11
2018-05-14 23:09:49 +08:00
Allen
c96156503f Merge pull request #336 from react-bootstrap-table/develop
20180513 release
2018-05-14 23:02:15 +08:00
AllenFang
ed2ba2a5c5 fix #334 2018-05-14 22:44:28 +08:00
AllenFang
f87fe3e544 patch docs, example and test for wrapperClasses 2018-05-12 13:54:40 +08:00
MikeSha
43e73313e6 implement wrapperClasses(#325) 2018-05-12 13:40:24 +08:00
Allen
888aa1d08b fix #303 (#330) 2018-05-10 14:07:24 +08:00
AllenFang
028834da8b add release command 2018-05-08 22:33:46 +08:00
AllenFang
8f3b989b00 Publish
- react-bootstrap-table2-editor@0.2.1
2018-05-08 22:23:23 +08:00
AllenFang
fe8761427d Publish
- react-bootstrap-table2-paginator@0.1.3
2018-05-08 22:22:47 +08:00
AllenFang
27a09de008 Publish
- react-bootstrap-table-next@0.1.10
2018-05-08 22:22:10 +08:00
AllenFang
20ba8cc24e Publish
- react-bootstrap-table2-editor@0.2.0
 - react-bootstrap-table2-example@0.1.7
 - react-bootstrap-table2-paginator@0.1.2
 - react-bootstrap-table-next@0.1.9
2018-05-06 16:59:28 +08:00
Allen
b8b52e7fc0 20180507 release #326
20180507 release
2018-05-06 16:57:29 +08:00
Allen
05a8c3be5f fix #309 (#324) 2018-05-06 15:50:18 +08:00
Allen
2f9bedbeeb Merge pull request #323 from react-bootstrap-table/feat/pagination-total
Implement pagination total
2018-05-06 15:16:49 +08:00
AllenFang
01be6fc275 example for #238 2018-05-06 15:08:43 +08:00
AllenFang
c20a4bb220 fix #238 2018-05-06 15:08:20 +08:00
Allen
ed21b3cb65 Merge pull request #322 from react-bootstrap-table/feat/rich-editor
Rich Editors
2018-05-06 14:05:08 +08:00
AllenFang
f2a44c976d patch docs 2018-05-06 13:50:59 +08:00
AllenFang
ca5189d8ad patch tests for rich editors 2018-05-05 18:18:52 +08:00
AllenFang
03f51c36ac add example for rich cell editor 2018-05-05 18:18:52 +08:00
AllenFang
607202b4e9 implement rich cell editor 2018-05-05 18:18:52 +08:00
AllenFang
4db4f4fb2d Publish
- react-bootstrap-table2-example@0.1.6
 - react-bootstrap-table2-filter@0.1.6
 - react-bootstrap-table-next@0.1.8
2018-04-15 21:25:09 +08:00
Allen
1d7df6819e 2018/04/01 release
2018/04/01 release
2018-04-15 21:21:50 +08:00
Allen
e4b6993692 fix #302 (#304) 2018-04-15 17:29:17 +08:00
Allen
b15d7a3412 Merge pull request #287 from react-bootstrap-table/feature/pogrammatically-filter
Feature/programmatically filter
2018-04-15 15:46:54 +08:00
Allen
b172c6801c fix #228 2018-04-08 21:17:24 +08:00
Chun-MingChen
dc1f4dcc38 update travis.yml 2018-04-05 16:35:26 +08:00
Chun-MingChen
a82e611358 [test] add test for pogrammatically filter 2018-04-04 17:58:19 +08:00
Chun-MingChen
c64951fd6f [test] correct tests for filter components
* <Text />, <Select /> and <Numbner />
2018-04-04 17:58:19 +08:00
Chun-MingChen
a35701fabf [test] correct test for filter wrapper 2018-04-04 17:58:19 +08:00
Chun-MingChen
f54c1f77b4 display filter condition correctly and make sure text filter to be String 2018-04-04 17:58:19 +08:00
Chun-MingChen
377534512a rename props and variable in samples for better readability 2018-04-04 17:45:56 +08:00
Chun-MingChen
09032349d0 [example] example for programmatically filter by text, number and select 2018-04-04 17:45:56 +08:00
Chun-MingChen
4dd39aeed8 export onFilter function to allow user to access 2018-04-04 17:45:56 +08:00
Chun-MingChen
a1477e2ad3 filter column by new onFilter 2018-04-04 17:45:56 +08:00
Chun-MingChen
f34cb4bf63 allow user to filter column without inputField
* wrap onFilter to HOF to allow filter dynamically
2018-04-04 17:45:56 +08:00
AllenFang
3dc9cd3941 Publish
- react-bootstrap-table2-editor@0.1.5
 - react-bootstrap-table2-filter@0.1.5
 - react-bootstrap-table-next@0.1.7
2018-04-02 23:46:59 +08:00
AllenFang
e8458b4b63 Publish
- react-bootstrap-table2-editor@0.1.4
 - react-bootstrap-table2-example@0.1.5
 - react-bootstrap-table2-filter@0.1.4
 - react-bootstrap-table-next@0.1.6
2018-04-01 15:56:53 +08:00
Allen
1d87ce9ffc 2018/04/01 release
2018/04/01 release
2018-04-01 15:55:45 +08:00
Allen
88e1a0774b fix #281 2018-04-01 14:14:32 +08:00
Patrick O'Meara
11d4f40089 noDataIndication (#276)
* use the correct amount of cells when the first row is select
* storybook added for development, not necessary in docs

fixes react-bootstrap-table/react-bootstrap-table2#264
2018-04-01 13:34:06 +08:00
Patrick O'Meara
41dc3ef619 empty noDataIndication when empty (#275)
* don't display unneeded empty row when noDataIndication isn't set
2018-04-01 13:32:24 +08:00
Nixon Kwok
4501ddb632 Fix textFilter() for Internet Explorer (includes() and find() are not supported) (#274)
* Fix textFilter() for Internet Explorer 11
- replace includes() with indexOf() !== -1
- replace find() with for loop

* Requested changes; more readability with for loop
- use .length of the columns instead of the Object.keys()
2018-04-01 13:30:59 +08:00
Allen
05657ee217 Merge pull request #273 from react-bootstrap-table/revert-265-264_indication_no_data
Revert "fix#264: wrong col span when enable selection in a empty tabl…
2018-03-25 17:38:32 +08:00
Allen
c91f521913 fix #258 (#268) 2018-03-25 16:44:27 +08:00
Allen
9ee9c7de43 Revert "fix#264: wrong col span when enable selection in a empty table (#265)"
This reverts commit 42dbd00fd9.
2018-03-25 16:37:27 +08:00
Patrick O'Meara
42dbd00fd9 fix#264: wrong col span when enable selection in a empty table (#265)
* noDataIndication

* use the correct amount of cells when the first row is select
* storybook added for development, not necessary in docs

fixes react-bootstrap-table/react-bootstrap-table2#264

* eslint complaints

  4:11  error  'columnLen' is never reassigned. Use 'const' instead                   prefer-const
  7:9   error  Expected an assignment or function call and instead saw an expression  no-unused-expressions

* tests updated
2018-03-25 16:36:58 +08:00
101 changed files with 3868 additions and 333 deletions

View File

@@ -11,6 +11,8 @@ branches:
only: only:
- master - master
- develop - develop
except:
- gh-pages-src
before_install: before_install:
- curl -o- -L https://yarnpkg.com/install.sh | bash -s - curl -o- -L https://yarnpkg.com/install.sh | bash -s

View File

@@ -17,12 +17,14 @@
* [condensed](#condensed) * [condensed](#condensed)
* [id](#id) * [id](#id)
* [classes](#classes) * [classes](#classes)
* [wrapperClasses](#wrapperClasses)
* [cellEdit](#cellEdit) * [cellEdit](#cellEdit)
* [selectRow](#selectRow) * [selectRow](#selectRow)
* [rowStyle](#rowStyle) * [rowStyle](#rowStyle)
* [rowClasses](#rowClasses) * [rowClasses](#rowClasses)
* [rowEvents](#rowEvents) * [rowEvents](#rowEvents)
* [defaultSorted](#defaultSorted) * [defaultSorted](#defaultSorted)
* [defaultSortDirection](#defaultSortDirection)
* [pagination](#pagination) * [pagination](#pagination)
* [filter](#filter) * [filter](#filter)
* [onTableChange](#onTableChange) * [onTableChange](#onTableChange)
@@ -106,6 +108,9 @@ Same as bootstrap `.table-condensed` class for making a table more compact by cu
Customize id on `table` element. Customize id on `table` element.
### <a name='classes'>classes - [String]</a> ### <a name='classes'>classes - [String]</a>
Customize class on `table` element. Customize class on `table` element.
### <a name='wrapperClasses'>wrapperClasses - [String]</a>
Customize class on the outer element which wrap up the `table` element.
### <a name='cellEdit'>cellEdit - [Object]</a> ### <a name='cellEdit'>cellEdit - [Object]</a>
Makes table cells editable, please see [cellEdit definition](./cell-edit.md) for more detail. Makes table cells editable, please see [cellEdit definition](./cell-edit.md) for more detail.
@@ -168,6 +173,9 @@ const defaultSorted = [{
}]; }];
``` ```
### <a name='defaultSortDirection'>defaultSortDirection - [String]</a>
Default sort direction when user click on header column at first time, available value is `asc` and `desc`. Default is `desc`.
### <a name='pagination'>pagination - [Object]</a> ### <a name='pagination'>pagination - [Object]</a>
`pagination` allow user to render a pagination panel on the bottom of table. But pagination functionality is separated from core of `react-bootstrap-table2` so that you are suppose to install `react-bootstrap-table2-paginator` additionally. `pagination` allow user to render a pagination panel on the bottom of table. But pagination functionality is separated from core of `react-bootstrap-table2` so that you are suppose to install `react-bootstrap-table2-paginator` additionally.
@@ -194,6 +202,7 @@ paginator({
totalSize, // Total data size. It's necessary when remote is enabled totalSize, // Total data size. It's necessary when remote is enabled
pageStartIndex: 0, // first page will be 0, default is 1 pageStartIndex: 0, // first page will be 0, default is 1
paginationSize: 3, // the pagination bar size, default is 5 paginationSize: 3, // the pagination bar size, default is 5
showTotal: true, // display pagination information
sizePerPageList: [ { sizePerPageList: [ {
text: '5', value: 5 text: '5', value: 5
}, { }, {
@@ -215,6 +224,7 @@ paginator({
hidePageListOnlyOnePage: true, // hide pagination bar when only one page, default is false hidePageListOnlyOnePage: true, // hide pagination bar when only one page, default is false
onPageChange: (page, sizePerPage) => {}, // callback function when page was changing onPageChange: (page, sizePerPage) => {}, // callback function when page was changing
onSizePerPageChange: (sizePerPage, page) => {}, // callback function when page size was changing onSizePerPageChange: (sizePerPage, page) => {}, // callback function when page size was changing
paginationTotalRenderer: (from, to, size) => { ... } // custom the pagination total
}) })
``` ```

View File

@@ -32,6 +32,10 @@ Available properties in a column object:
* [validator](#validator) * [validator](#validator)
* [editCellStyle](#editCellStyle) * [editCellStyle](#editCellStyle)
* [editCellClasses](#editCellClasses) * [editCellClasses](#editCellClasses)
* [editorStyle](#editorStyle)
* [editorClasses](#editorClasses)
* [editor](#editor)
* [editorRenderer](#editorRenderer)
* [filter](#filter) * [filter](#filter)
* [filterValue](#filterValue) * [filterValue](#filterValue)
@@ -552,11 +556,100 @@ Or take a callback function
} }
``` ```
## <a name='editorStyle'>column.editorStyle - [Object | Function]</a>
This is almost same as [`column.editCellStyle`](#editCellStyle), but `column.editorStyle` is custom the style on editor instead of cell(`td`).
## <a name='editorClasses'>column.editorClasses - [Object | Function]</a>
This is almost same as [`column.editCellClasses`](#editCellClasses), but `column.editorClasses` is custom the class on editor instead of cell(`td`).
## <a name='editor'>column.editor - [Object]</a>
`column.editor` allow you to custom the type of cell editor by following predefined type:
* Text(Default)
* Dropdown
* Date
* Textarea
* Checkbox
Following is a quite example:
```js
import cellEditFactory, { Type } from 'react-bootstrap-table2-editor';
const columns = [
//...
, {
dataField: 'done',
text: 'Done',
editor: {
type: Type.CHECKBOX,
value: 'Y:N'
}
}
];
```
If you want more information, please check [here](https://github.com/react-bootstrap-table/react-bootstrap-table2/tree/master/packages/react-bootstrap-table2-editor).
## <a name='editorRenderer'>column.editorRenderer - [Function]</a>
If you feel above predefined editors are not satisfied to your requirement, you can totally custom the editor via `column.editorRenderer`:
```js
import cellEditFactory, { Type } from 'react-bootstrap-table2-editor';
// Custom Editor
class QualityRanger extends React.Component {
static propTypes = {
value: PropTypes.number,
onUpdate: PropTypes.func.isRequired
}
static defaultProps = {
value: 0
}
getValue() {
return parseInt(this.range.value, 10);
}
render() {
const { value, onUpdate, ...rest } = this.props;
return [
<input
{ ...rest }
key="range"
ref={ node => this.range = node }
type="range"
min="0"
max="100"
/>,
<button
key="submit"
className="btn btn-default"
onClick={ () => onUpdate(this.getValue()) }
>
done
</button>
];
}
}
const columns = [
//...
, {
dataField: 'done',
text: 'Done',
editorRenderer: (editorProps, value, row, column, rowIndex, columnIndex) =>
<QualityRanger { ...editorProps } value={ value } />;
}
];
```
## <a name='filter'>column.filter - [Object]</a> ## <a name='filter'>column.filter - [Object]</a>
Configure `column.filter` will able to setup a column level filter on the header column. Currently, `react-bootstrap-table2` support following filters: Configure `column.filter` will able to setup a column level filter on the header column. Currently, `react-bootstrap-table2` support following filters:
* Text(`textFilter`) * Text(`textFilter`)
* Select(`selectFilter`) * Select(`selectFilter`)
* Number(`numberFilter`)
* Date(`dateFilter`)
We have a quick example to show you how to use `column.filter`: We have a quick example to show you how to use `column.filter`:

View File

@@ -72,7 +72,7 @@ Due to no `TableHeaderColumn` so that no `dataSort` here, please add [`sort`](ht
Please see [Work with selection](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/basic-row-select.html). Please see [Work with selection](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/basic-row-select.html).
Please see [available selectRow configurations](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/row-select-props.html). Please see [available selectRow configurations](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/row-select-props.html).
No huge change for row selection, but can not custom the selection column currently. Coming soon!!! No huge change for row selection.
## Column Filter ## Column Filter
@@ -87,9 +87,9 @@ Please see [available filter configuration](https://react-bootstrap-table.github
- [x] Select Filter - [x] Select Filter
- [x] Custom Select Filter - [x] Custom Select Filter
- [X] Number Filter - [X] Number Filter
- [ ] Date Filter - [X] Date Filter
- [ ] Array Filter - [ ] Array Filter
- [ ] Programmatically Filter - [X] Programmatically Filter
Remember to install [`react-bootstrap-table2-filter`](https://www.npmjs.com/package/react-bootstrap-table2-filter) firstly. Remember to install [`react-bootstrap-table2-filter`](https://www.npmjs.com/package/react-bootstrap-table2-filter) firstly.

View File

@@ -16,6 +16,8 @@
* [onSelect](#onSelect) * [onSelect](#onSelect)
* [onSelectAll](#onSelectAll) * [onSelectAll](#onSelectAll)
* [hideSelectColumn](#hideSelectColumn) * [hideSelectColumn](#hideSelectColumn)
* [selectionRenderer](#selectionRenderer)
* [selectionHeaderRenderer](#selectionHeaderRenderer)
### <a name="mode">selectRow.mode - [String]</a> ### <a name="mode">selectRow.mode - [String]</a>
@@ -156,14 +158,42 @@ const selectRow = {
}; };
``` ```
### <a name='onSelect'>selectRow.onSelect - [Function]</a> ### <a name='selectionRenderer'>selectRow.selectionRenderer - [Function]</a>
This callback function will be called when a row is select/unselect and pass following three arguments: Provide a callback function which allow you to custom the checkbox/radio box. This callback only have one argument which is an object and contain following properties:
`row`, `isSelect` and `rowIndex`.
```js ```js
const selectRow = { const selectRow = {
mode: 'checkbox', mode: 'checkbox',
onSelect: (row, isSelect, rowIndex) => { selectionRenderer: ({ mode, checked, disabled }) => (
// ....
)
};
```
> By default, `react-bootstrap-table2` will help you to handle the click event, it's not necessary to handle again by developer.
### <a name='selectionHeaderRenderer'>selectRow.selectionHeaderRenderer - [Function]</a>
Provide a callback function which allow you to custom the checkbox/radio box in the selection header column. This callback only have one argument which is an object and contain following properties:
```js
const selectRow = {
mode: 'checkbox',
selectionHeaderRenderer: ({ mode, checked, indeterminate }) => (
// ....
)
};
```
> By default, `react-bootstrap-table2` will help you to handle the click event, it's not necessary to handle again by developer.
### <a name='onSelect'>selectRow.onSelect - [Function]</a>
This callback function will be called when a row is select/unselect and pass following three arguments:
`row`, `isSelect`, `rowIndex` and `e`.
```js
const selectRow = {
mode: 'checkbox',
onSelect: (row, isSelect, rowIndex, e) => {
// ... // ...
} }
}; };
@@ -175,7 +205,7 @@ This callback function will be called when select/unselect all and it only work
```js ```js
const selectRow = { const selectRow = {
mode: 'checkbox', mode: 'checkbox',
onSelectAll: (isSelect, results) => { onSelectAll: (isSelect, results, e) => {
// ... // ...
} }
}; };

View File

@@ -72,9 +72,15 @@ function styles() {
.pipe(gulp.dest(PKG_PATH)); .pipe(gulp.dest(PKG_PATH));
} }
function umd() { function umd(done) {
return gulp.src('./webpack.prod.config.babel.js') gulp.parallel(
.pipe(shell(['webpack --config <%= file.path %>'])); () => gulp.src('./webpack/next.umd.babel.js').pipe(shell(['webpack --config <%= file.path %>'])),
() => gulp.src('./webpack/editor.umd.babel.js').pipe(shell(['webpack --config <%= file.path %>'])),
() => gulp.src('./webpack/filter.umd.babel.js').pipe(shell(['webpack --config <%= file.path %>'])),
() => gulp.src('./webpack/overlay.umd.babel.js').pipe(shell(['webpack --config <%= file.path %>'])),
() => gulp.src('./webpack/paginator.umd.babel.js').pipe(shell(['webpack --config <%= file.path %>']))
)();
done();
} }
const buildJS = gulp.parallel(umd, scripts); const buildJS = gulp.parallel(umd, scripts);

View File

@@ -14,7 +14,8 @@
"test:watch": "jest --watch", "test:watch": "jest --watch",
"storybook": "cd ./packages/react-bootstrap-table2-example && yarn storybook", "storybook": "cd ./packages/react-bootstrap-table2-example && yarn storybook",
"gh-pages:clean": "cd ./packages/react-bootstrap-table2-example && yarn gh-pages:clean", "gh-pages:clean": "cd ./packages/react-bootstrap-table2-example && yarn gh-pages:clean",
"gh-pages:build": "cd ./packages/react-bootstrap-table2-example && yarn gh-pages:build" "gh-pages:build": "cd ./packages/react-bootstrap-table2-example && yarn gh-pages:build",
"release": "yarn install && yarn build && lerna publish"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -85,7 +86,8 @@
}, },
"jest": { "jest": {
"collectCoverageFrom": [ "collectCoverageFrom": [
"packages/**/*.js" "packages/*/src/*.js",
"packages/*/index.js"
], ],
"roots": [ "roots": [
"<rootDir>/packages" "<rootDir>/packages"

View File

@@ -48,14 +48,182 @@ How user save their new editings? We offer two ways:
* Column Level (Configure [column.editable](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditable-bool-function) as bool value) * Column Level (Configure [column.editable](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditable-bool-function) as bool value)
* Cell Level (Configure [column.editable](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditable-bool-function) as a callback function) * Cell Level (Configure [column.editable](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditable-bool-function) as a callback function)
## Customize Style/Class ## Validation
Currently, we only support the editing cell style/class customization, in the future, we will offer more customizations.
[column.validator](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columnvalidator-function) will help you to work on it!
## Customize Style/Class
### Editing Cell ### Editing Cell
* Customize the editing cell style via [column.editCellStyle](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditcellstyle-object-function) * Customize the editing cell style via [column.editCellStyle](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditcellstyle-object-function)
* Customize the editing cell classname via [column.editCellClasses](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditcellclasses-string-function) * Customize the editing cell classname via [column.editCellClasses](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditcellclasses-string-function)
## Validation ### Editor
* Customize the editor style via [column.editorStyle](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditorstyle-object-function)
* Customize the editor classname via [column.editoClasses](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditorclasses-string-function)
[`column.validator`](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columnvalidator-function) will help you to work on it! ## Rich Editors
`react-bootstrap-table2` have following predefined editor:
* Text(Default)
* Dropdown
* Date
* Textarea
* Checkbox
In a nutshell, you just only give a [column.editor](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditor-object) and define the `type`:
```js
import { Type } from 'react-bootstrap-table2-editor';
const columns = [
..., {
dataField: 'done',
text: 'Done',
editor: {
type: Type.SELECT | Type.TEXTAREA | Type.CHECKBOX | Type.DATE,
... // The rest properties will be rendered into the editor's DOM element
}
}
]
```
In the following, we go though all the predefined editors:
### Dropdown Editor
Dropdown editor give a select menu to choose a data from a list, the `editor.options` is required property for dropdown editor.
```js
import { Type } from 'react-bootstrap-table2-editor';
const columns = [
..., {
dataField: 'type',
text: 'Job Type',
editor: {
type: Type.SELECT,
options: [{
value: 'A',
label: 'A'
}, {
value: 'B',
label: 'B'
}, {
value: 'C',
label: 'C'
}, {
value: 'D',
label: 'D'
}, {
value: 'E',
label: 'E'
}]
}
}];
```
### Date Editor
Date editor is use `<input type="date">`, the configuration is very simple:
```js
const columns = [
..., {
dataField: 'inStockDate',
text: 'Stock Date',
formatter: (cell) => {
let dateObj = cell;
if (typeof cell !== 'object') {
dateObj = new Date(cell);
}
return `${('0' + dateObj.getDate()).slice(-2)}/${('0' + (dateObj.getMonth() + 1)).slice(-2)}/${dateObj.getFullYear()}`;
},
editor: {
type: Type.DATE
}
}];
```
### Textarea Editor
Textarea editor is use `<input type="textarea">`, user can press `ENTER` to change line and in the `react-bootstrap-table2`, user allow to save result via press `SHIFT` + `ENTER`.
```js
const columns = [
..., {
dataField: 'comment',
text: 'Product Comments',
editor: {
type: Type.TEXTAREA
}
}];
```
### Checkbox Editor
Checkbox editor allow you to have a pair value choice, the `editor.value` is required value to represent the actual value for check and uncheck.
```js
const columns = [
..., {
dataField: 'comment',
text: 'Product Comments',
editor: {
type: Type.CHECKBOX,
value: 'Y:N'
}
}];
```
## Customize Editor
If you feel above predefined editors are not satisfied to your requirement, you can certainly custom the editor via [column.editorRenderer](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditorrenderer-function). It accept a function and pass following arguments when function called:
* `editorProps`: Some useful attributes you can use on DOM editor, like class, style etc.
* `value`: Current cell value
* `row`: Current row data
* `column`: Current column definition
* `rowIndex`: Current row index
* `columnIndex`: Current column index
> Note when implement a custom React editor component, this component should have a **getValue** function which return current value on editor
> Note when you want to save value, you can call **editorProps.onUpdate** function
Following is a short example:
```js
class QualityRanger extends React.Component {
static propTypes = {
value: PropTypes.number,
onUpdate: PropTypes.func.isRequired
}
static defaultProps = {
value: 0
}
getValue() {
return parseInt(this.range.value, 10);
}
render() {
const { value, onUpdate, ...rest } = this.props;
return [
<input
{ ...rest }
key="range"
ref={ node => this.range = node }
type="range"
min="0"
max="100"
/>,
<button
key="submit"
className="btn btn-default"
onClick={ () => onUpdate(this.getValue()) }
>
done
</button>
];
}
}
const columns = [
..., {
dataField: 'quality',
text: 'Product Quality',
editorRenderer: (editorProps, value, row, column, rowIndex, columnIndex) => (
<QualityRanger { ...editorProps } value={ value } />
)
}];
```

View File

@@ -1,6 +1,7 @@
import wrapperFactory from './src/wrapper'; import wrapperFactory from './src/wrapper';
import editingCellFactory from './src/editing-cell'; import editingCellFactory from './src/editing-cell';
import { import {
EDITTYPE,
CLICK_TO_CELL_EDIT, CLICK_TO_CELL_EDIT,
DBCLICK_TO_CELL_EDIT, DBCLICK_TO_CELL_EDIT,
DELAY_FOR_DBCLICK DELAY_FOR_DBCLICK
@@ -14,3 +15,5 @@ export default (options = {}) => ({
DELAY_FOR_DBCLICK, DELAY_FOR_DBCLICK,
options options
}); });
export const Type = EDITTYPE;

View File

@@ -1,6 +1,6 @@
{ {
"name": "react-bootstrap-table2-editor", "name": "react-bootstrap-table2-editor",
"version": "0.1.3", "version": "0.2.1",
"description": "it's the editor addon for react-bootstrap-table2", "description": "it's the editor addon for react-bootstrap-table2",
"main": "./lib/index.js", "main": "./lib/index.js",
"scripts": { "scripts": {

View File

@@ -0,0 +1,61 @@
/* eslint no-return-assign: 0 */
import React, { Component } from 'react';
import cs from 'classnames';
import PropTypes from 'prop-types';
class CheckBoxEditor extends Component {
constructor(props) {
super(props);
this.state = {
checked: props.defaultValue.toString() === props.value.split(':')[0]
};
this.handleChange = this.handleChange.bind(this);
}
componentDidMount() {
this.checkbox.focus();
}
getValue() {
const [positive, negative] = this.props.value.split(':');
return this.checkbox.checked ? positive : negative;
}
handleChange(e) {
if (this.props.onChange) this.props.onChange(e);
const { target } = e;
this.setState(() => ({ checked: target.checked }));
}
render() {
const { defaultValue, className, ...rest } = this.props;
const editorClass = cs('editor edit-chseckbox checkbox', className);
return (
<input
ref={ node => this.checkbox = node }
type="checkbox"
className={ editorClass }
{ ...rest }
checked={ this.state.checked }
onChange={ this.handleChange }
/>
);
}
}
CheckBoxEditor.propTypes = {
className: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object
]),
value: PropTypes.string,
defaultValue: PropTypes.any,
onChange: PropTypes.func
};
CheckBoxEditor.defaultProps = {
className: '',
value: 'on:off',
defaultValue: false,
onChange: undefined
};
export default CheckBoxEditor;

View File

@@ -2,3 +2,11 @@ export const TIME_TO_CLOSE_MESSAGE = 3000;
export const DELAY_FOR_DBCLICK = 200; export const DELAY_FOR_DBCLICK = 200;
export const CLICK_TO_CELL_EDIT = 'click'; export const CLICK_TO_CELL_EDIT = 'click';
export const DBCLICK_TO_CELL_EDIT = 'dbclick'; export const DBCLICK_TO_CELL_EDIT = 'dbclick';
export const EDITTYPE = {
TEXT: 'text',
SELECT: 'select',
TEXTAREA: 'textarea',
CHECKBOX: 'checkbox',
DATE: 'date'
};

View File

@@ -0,0 +1,42 @@
/* eslint no-return-assign: 0 */
import React, { Component } from 'react';
import cs from 'classnames';
import PropTypes from 'prop-types';
class DateEditor extends Component {
componentDidMount() {
const { defaultValue } = this.props;
this.date.valueAsDate = new Date(defaultValue);
this.date.focus();
}
getValue() {
return this.date.value;
}
render() {
const { defaultValue, className, ...rest } = this.props;
const editorClass = cs('form-control editor edit-date', className);
return (
<input
ref={ node => this.date = node }
type="date"
className={ editorClass }
{ ...rest }
/>
);
}
}
DateEditor.propTypes = {
className: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object
]),
defaultValue: PropTypes.string
};
DateEditor.defaultProps = {
className: '',
defaultValue: ''
};
export default DateEditor;

View File

@@ -0,0 +1,61 @@
/* eslint no-return-assign: 0 */
import React, { Component } from 'react';
import cs from 'classnames';
import PropTypes from 'prop-types';
class DropDownEditor extends Component {
componentDidMount() {
const { defaultValue } = this.props;
this.select.value = defaultValue;
this.select.focus();
}
getValue() {
return this.select.value;
}
render() {
const { defaultValue, className, options, ...rest } = this.props;
const editorClass = cs('form-control editor edit-select', className);
const attr = {
...rest,
className: editorClass
};
return (
<select
{ ...attr }
ref={ node => this.select = node }
defaultValue={ defaultValue }
>
{
options.map(({ label, value }) => (
<option key={ value } value={ value }>{ label }</option>
))
}
</select>
);
}
}
DropDownEditor.propTypes = {
defaultValue: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]),
className: PropTypes.string,
style: PropTypes.object,
options: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.shape({
label: PropTypes.string,
value: PropTypes.any
}))
]).isRequired
};
DropDownEditor.defaultProps = {
className: '',
defaultValue: '',
style: {}
};
export default DropDownEditor;

View File

@@ -6,15 +6,21 @@ import React, { Component } from 'react';
import cs from 'classnames'; import cs from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import DropdownEditor from './dropdown-editor';
import TextAreaEditor from './textarea-editor';
import CheckBoxEditor from './checkbox-editor';
import DateEditor from './date-editor';
import TextEditor from './text-editor'; import TextEditor from './text-editor';
import EditorIndicator from './editor-indicator'; import EditorIndicator from './editor-indicator';
import { TIME_TO_CLOSE_MESSAGE } from './const'; import { TIME_TO_CLOSE_MESSAGE, EDITTYPE } from './const';
export default _ => export default _ =>
class EditingCell extends Component { class EditingCell extends Component {
static propTypes = { static propTypes = {
row: PropTypes.object.isRequired, row: PropTypes.object.isRequired,
rowIndex: PropTypes.number.isRequired,
column: PropTypes.object.isRequired, column: PropTypes.object.isRequired,
columnIndex: PropTypes.number.isRequired,
onUpdate: PropTypes.func.isRequired, onUpdate: PropTypes.func.isRequired,
onEscape: PropTypes.func.isRequired, onEscape: PropTypes.func.isRequired,
timeToCloseMessage: PropTypes.number, timeToCloseMessage: PropTypes.number,
@@ -71,8 +77,8 @@ export default _ =>
}, timeToCloseMessage); }, timeToCloseMessage);
} }
beforeComplete(row, column, newValue) { beforeComplete(newValue) {
const { onUpdate } = this.props; const { onUpdate, row, column } = this.props;
if (_.isFunction(column.validator)) { if (_.isFunction(column.validator)) {
const validateForm = column.validator(newValue, row, column); const validateForm = column.validator(newValue, row, column);
if (_.isObject(validateForm) && !validateForm.valid) { if (_.isObject(validateForm) && !validateForm.valid) {
@@ -87,28 +93,20 @@ export default _ =>
} }
handleBlur() { handleBlur() {
const { onEscape, blurToSave, row, column } = this.props; const { onEscape, blurToSave } = this.props;
if (blurToSave) { if (blurToSave) {
const value = this.editor.text.value; this.beforeComplete(this.editor.getValue());
if (!_.isDefined(value)) {
// TODO: for other custom or embed editor
}
this.beforeComplete(row, column, value);
} else { } else {
onEscape(); onEscape();
} }
} }
handleKeyDown(e) { handleKeyDown(e) {
const { onEscape, row, column } = this.props; const { onEscape } = this.props;
if (e.keyCode === 27) { // ESC if (e.keyCode === 27) { // ESC
onEscape(); onEscape();
} else if (e.keyCode === 13) { // ENTER } else if (e.keyCode === 13) { // ENTER
const value = e.currentTarget.value; this.beforeComplete(this.editor.getValue());
if (!_.isDefined(value)) {
// TODO: for other custom or embed editor
}
this.beforeComplete(row, column, value);
} }
} }
@@ -122,31 +120,73 @@ export default _ =>
} }
render() { render() {
const { invalidMessage } = this.state; let editor;
const { row, column, className, style } = this.props; const { row, column, className, style, rowIndex, columnIndex } = this.props;
const { dataField } = column; const { dataField } = column;
const value = _.get(row, dataField); const value = _.get(row, dataField);
const editorAttrs = { const hasError = _.isDefined(this.state.invalidMessage);
let customEditorClass = column.editorClasses || '';
if (_.isFunction(column.editorClasses)) {
customEditorClass = column.editorClasses(value, row, rowIndex, columnIndex);
}
let editorStyle = column.editorStyle || {};
if (_.isFunction(column.editorStyle)) {
editorStyle = column.editorStyle(value, row, rowIndex, columnIndex);
}
const editorClass = cs({
animated: hasError,
shake: hasError
}, customEditorClass);
let editorProps = {
ref: node => this.editor = node,
defaultValue: value,
style: editorStyle,
className: editorClass,
onKeyDown: this.handleKeyDown, onKeyDown: this.handleKeyDown,
onBlur: this.handleBlur onBlur: this.handleBlur
}; };
const hasError = _.isDefined(invalidMessage); const isDefaultEditorDefined = _.isObject(column.editor);
const editorClass = hasError ? cs('animated', 'shake') : null;
if (isDefaultEditorDefined) {
editorProps = {
...editorProps,
...column.editor
};
} else if (_.isFunction(column.editorRenderer)) {
editorProps = {
...editorProps,
onUpdate: this.beforeComplete
};
}
if (_.isFunction(column.editorRenderer)) {
editor = column.editorRenderer(editorProps, value, row, column, rowIndex, columnIndex);
} else if (isDefaultEditorDefined && column.editor.type === EDITTYPE.SELECT) {
editor = <DropdownEditor { ...editorProps } />;
} else if (isDefaultEditorDefined && column.editor.type === EDITTYPE.TEXTAREA) {
editor = <TextAreaEditor { ...editorProps } />;
} else if (isDefaultEditorDefined && column.editor.type === EDITTYPE.CHECKBOX) {
editor = <CheckBoxEditor { ...editorProps } />;
} else if (isDefaultEditorDefined && column.editor.type === EDITTYPE.DATE) {
editor = <DateEditor { ...editorProps } />;
} else {
editor = <TextEditor { ...editorProps } />;
}
return ( return (
<td <td
className={ cs('react-bootstrap-table-editing-cell', className) } className={ cs('react-bootstrap-table-editing-cell', className) }
style={ style } style={ style }
onClick={ this.handleClick } onClick={ this.handleClick }
> >
<TextEditor { editor }
ref={ node => this.editor = node } { hasError ? <EditorIndicator invalidMessage={ this.state.invalidMessage } /> : null }
defaultValue={ value }
className={ editorClass }
{ ...editorAttrs }
/>
{ hasError ? <EditorIndicator invalidMessage={ invalidMessage } /> : null }
</td> </td>
); );
} }

View File

@@ -10,6 +10,10 @@ class TextEditor extends Component {
this.text.focus(); this.text.focus();
} }
getValue() {
return this.text.value;
}
render() { render() {
const { defaultValue, className, ...rest } = this.props; const { defaultValue, className, ...rest } = this.props;
const editorClass = cs('form-control editor edit-text', className); const editorClass = cs('form-control editor edit-text', className);

View File

@@ -0,0 +1,60 @@
/* eslint no-return-assign: 0 */
import React, { Component } from 'react';
import cs from 'classnames';
import PropTypes from 'prop-types';
class TextAreaEditor extends Component {
constructor(props) {
super(props);
this.handleKeyDown = this.handleKeyDown.bind(this);
}
componentDidMount() {
const { defaultValue } = this.props;
this.text.value = defaultValue;
this.text.focus();
}
getValue() {
return this.text.value;
}
handleKeyDown(e) {
if (e.keyCode === 13 && !e.shiftKey) return;
if (this.props.onKeyDown) {
this.props.onKeyDown(e);
}
}
render() {
const { defaultValue, className, ...rest } = this.props;
const editorClass = cs('form-control editor edit-textarea', className);
return (
<textarea
ref={ node => this.text = node }
type="textarea"
className={ editorClass }
{ ...rest }
onKeyDown={ this.handleKeyDown }
/>
);
}
}
TextAreaEditor.propTypes = {
className: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object
]),
defaultValue: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]),
onKeyDown: PropTypes.func
};
TextAreaEditor.defaultProps = {
className: '',
defaultValue: '',
onKeyDown: undefined
};
export default TextAreaEditor;

View File

@@ -6,7 +6,12 @@ import { shallow, mount } from 'enzyme';
import _ from 'react-bootstrap-table-next/src/utils'; import _ from 'react-bootstrap-table-next/src/utils';
import editingCellFactory from '../src/editing-cell'; import editingCellFactory from '../src/editing-cell';
import * as constants from '../src/const';
import TextEditor from '../src/text-editor'; import TextEditor from '../src/text-editor';
import DateEditor from '../src/date-editor';
import DropDownEditor from '../src/dropdown-editor';
import TextAreaEditor from '../src/textarea-editor';
import CheckBoxEditor from '../src/checkbox-editor';
import EditorIndicator from '../src/editor-indicator'; import EditorIndicator from '../src/editor-indicator';
const EditingCell = editingCellFactory(_); const EditingCell = editingCellFactory(_);
@@ -28,6 +33,9 @@ describe('EditingCell', () => {
name: 'A' name: 'A'
}; };
const rowIndex = 1;
const columnIndex = 1;
let column = { let column = {
dataField: 'id', dataField: 'id',
text: 'ID' text: 'ID'
@@ -36,9 +44,11 @@ describe('EditingCell', () => {
beforeEach(() => { beforeEach(() => {
onEscape = sinon.stub(); onEscape = sinon.stub();
onUpdate = sinon.stub(); onUpdate = sinon.stub();
wrapper = shallow( wrapper = mount(
<EditingCell <EditingCell
row={ row } row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ column } column={ column }
onUpdate={ onUpdate } onUpdate={ onUpdate }
onEscape={ onEscape } onEscape={ onEscape }
@@ -58,7 +68,7 @@ describe('EditingCell', () => {
expect(textEditor.props().defaultValue).toEqual(row[column.dataField]); expect(textEditor.props().defaultValue).toEqual(row[column.dataField]);
expect(textEditor.props().onKeyDown).toBeDefined(); expect(textEditor.props().onKeyDown).toBeDefined();
expect(textEditor.props().onBlur).toBeDefined(); expect(textEditor.props().onBlur).toBeDefined();
expect(textEditor.props().className).toBeNull(); expect(textEditor.props().className).toEqual('');
}); });
it('should not render EditorIndicator due to state.invalidMessage is null', () => { it('should not render EditorIndicator due to state.invalidMessage is null', () => {
@@ -69,7 +79,8 @@ describe('EditingCell', () => {
it('when press ENTER on TextEditor should call onUpdate correctly', () => { it('when press ENTER on TextEditor should call onUpdate correctly', () => {
const newValue = 'test'; const newValue = 'test';
const textEditor = wrapper.find(TextEditor); const textEditor = wrapper.find(TextEditor);
textEditor.simulate('keyDown', { keyCode: 13, currentTarget: { value: newValue } }); sinon.stub(textEditor.instance(), 'getValue').returns(newValue);
textEditor.simulate('keyDown', { keyCode: 13 });
expect(onUpdate.callCount).toBe(1); expect(onUpdate.callCount).toBe(1);
expect(onUpdate.calledWith(row, column, newValue)).toBe(true); expect(onUpdate.calledWith(row, column, newValue)).toBe(true);
}); });
@@ -92,6 +103,8 @@ describe('EditingCell', () => {
wrapper = shallow( wrapper = shallow(
<EditingCell <EditingCell
row={ row } row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ column } column={ column }
onUpdate={ onUpdate } onUpdate={ onUpdate }
onEscape={ onEscape } onEscape={ onEscape }
@@ -112,6 +125,8 @@ describe('EditingCell', () => {
wrapper = shallow( wrapper = shallow(
<EditingCell <EditingCell
row={ row } row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ column } column={ column }
onUpdate={ onUpdate } onUpdate={ onUpdate }
onEscape={ onEscape } onEscape={ onEscape }
@@ -126,12 +141,140 @@ describe('EditingCell', () => {
}); });
}); });
describe('if column.editorClasses is defined', () => {
let columnWithEditorClasses;
const classes = 'test test1';
describe('and it is a function', () => {
beforeEach(() => {
columnWithEditorClasses = {
...column,
editorClasses: jest.fn(() => classes)
};
wrapper = shallow(
<EditingCell
row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ columnWithEditorClasses }
onUpdate={ onUpdate }
onEscape={ onEscape }
/>
);
});
it('should render TextEditor with correct props', () => {
const textEditor = wrapper.find(TextEditor);
expect(textEditor.props().className).toEqual(classes);
});
it('should call column.editorClasses correctly', () => {
expect(columnWithEditorClasses.editorClasses).toHaveBeenCalledTimes(1);
expect(columnWithEditorClasses.editorClasses).toHaveBeenCalledWith(
_.get(row, column.dataField),
row,
rowIndex,
columnIndex
);
});
});
describe('and it is a string', () => {
beforeEach(() => {
columnWithEditorClasses = {
...column,
editorClasses: classes
};
wrapper = shallow(
<EditingCell
row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ columnWithEditorClasses }
onUpdate={ onUpdate }
onEscape={ onEscape }
/>
);
});
it('should render TextEditor with correct props', () => {
const textEditor = wrapper.find(TextEditor);
expect(textEditor.props().className).toEqual(classes);
});
});
});
describe('if column.editorStyle is defined', () => {
let columnWithEditorStyle;
const style = { color: 'red' };
describe('and it is a function', () => {
beforeEach(() => {
columnWithEditorStyle = {
...column,
editorStyle: jest.fn(() => style)
};
wrapper = shallow(
<EditingCell
row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ columnWithEditorStyle }
onUpdate={ onUpdate }
onEscape={ onEscape }
/>
);
});
it('should render TextEditor with correct props', () => {
const textEditor = wrapper.find(TextEditor);
expect(textEditor.props().style).toEqual(style);
});
it('should call column.editorStyle correctly', () => {
expect(columnWithEditorStyle.editorStyle).toHaveBeenCalledTimes(1);
expect(columnWithEditorStyle.editorStyle).toHaveBeenCalledWith(
_.get(row, column.dataField),
row,
rowIndex,
columnIndex
);
});
});
describe('and it is an object', () => {
beforeEach(() => {
columnWithEditorStyle = {
...column,
editorStyle: style
};
wrapper = shallow(
<EditingCell
row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ columnWithEditorStyle }
onUpdate={ onUpdate }
onEscape={ onEscape }
/>
);
});
it('should render TextEditor with correct props', () => {
const textEditor = wrapper.find(TextEditor);
expect(textEditor.props().style).toEqual(style);
});
});
});
describe('if blurToSave prop is true', () => { describe('if blurToSave prop is true', () => {
beforeEach(() => { beforeEach(() => {
wrapper = mount( wrapper = mount(
<TableRowWrapper> <TableRowWrapper>
<EditingCell <EditingCell
row={ row } row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ column } column={ column }
onUpdate={ onUpdate } onUpdate={ onUpdate }
onEscape={ onEscape } onEscape={ onEscape }
@@ -167,12 +310,14 @@ describe('EditingCell', () => {
wrapper = mount( wrapper = mount(
<EditingCell <EditingCell
row={ row } row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ column } column={ column }
onUpdate={ onUpdate } onUpdate={ onUpdate }
onEscape={ onEscape } onEscape={ onEscape }
/> />
); );
wrapper.instance().beforeComplete(row, column, newValue); wrapper.instance().beforeComplete(newValue);
}); });
it('should call column.validator successfully', () => { it('should call column.validator successfully', () => {
@@ -218,7 +363,17 @@ describe('EditingCell', () => {
text: 'ID', text: 'ID',
validator: validatorCallBack validator: validatorCallBack
}; };
wrapper.instance().beforeComplete(row, column, newValue); wrapper = mount(
<EditingCell
row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ column }
onUpdate={ onUpdate }
onEscape={ onEscape }
/>
);
wrapper.instance().beforeComplete(newValue);
}); });
it('should call column.validator successfully', () => { it('should call column.validator successfully', () => {
@@ -231,4 +386,156 @@ describe('EditingCell', () => {
}); });
}); });
}); });
describe('if column.editorRenderer is defined', () => {
const TestEditor = () => <input type="text" />;
beforeEach(() => {
column = {
dataField: 'id',
text: 'ID',
editorRenderer: sinon.stub().returns(<TestEditor />)
};
wrapper = mount(
<EditingCell
row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ column }
onUpdate={ onUpdate }
onEscape={ onEscape }
/>
);
});
it('should call column.editorRenderer correctly', () => {
expect(column.editorRenderer.callCount).toBe(1);
});
it('should render correctly', () => {
expect(wrapper.find(TestEditor)).toHaveLength(1);
});
});
describe('if column.editor is select', () => {
beforeEach(() => {
column = {
dataField: 'id',
text: 'ID',
editor: {
type: constants.EDITTYPE.SELECT,
options: [{
value: 1,
label: 'A'
}]
}
};
wrapper = mount(
<EditingCell
row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ column }
onUpdate={ onUpdate }
onEscape={ onEscape }
/>
);
});
it('should render dropdown editor successfully', () => {
const editor = wrapper.find(DropDownEditor);
expect(wrapper.length).toBe(1);
expect(editor.length).toBe(1);
expect(editor.props().options).toEqual(column.editor.options);
});
});
describe('if column.editor is textarea', () => {
beforeEach(() => {
column = {
dataField: 'id',
text: 'ID',
editor: {
type: constants.EDITTYPE.TEXTAREA
}
};
wrapper = mount(
<EditingCell
row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ column }
onUpdate={ onUpdate }
onEscape={ onEscape }
/>
);
});
it('should render textarea editor successfully', () => {
const editor = wrapper.find(TextAreaEditor);
expect(wrapper.length).toBe(1);
expect(editor.length).toBe(1);
});
});
describe('if column.editor is checkbox', () => {
beforeEach(() => {
column = {
dataField: 'id',
text: 'ID',
editor: {
type: constants.EDITTYPE.CHECKBOX
}
};
wrapper = mount(
<EditingCell
row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ column }
onUpdate={ onUpdate }
onEscape={ onEscape }
/>
);
});
it('should render checkbox editor successfully', () => {
const editor = wrapper.find(CheckBoxEditor);
expect(wrapper.length).toBe(1);
expect(editor.length).toBe(1);
});
});
describe('if column.editor is date', () => {
beforeEach(() => {
column = {
dataField: 'id',
text: 'ID',
editor: {
type: constants.EDITTYPE.DATE
}
};
wrapper = mount(
<EditingCell
row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ column }
onUpdate={ onUpdate }
onEscape={ onEscape }
/>
);
});
it('should render date editor successfully', () => {
const editor = wrapper.find(DateEditor);
expect(wrapper.length).toBe(1);
expect(editor.length).toBe(1);
});
});
}); });

View File

@@ -33,6 +33,7 @@ const columns = [{
<BootstrapTable id="bar" keyField='id' data={ products } columns={ columns } /> <BootstrapTable id="bar" keyField='id' data={ products } columns={ columns } />
<BootstrapTable classes="foo" keyField='id' data={ products } columns={ columns } /> <BootstrapTable classes="foo" keyField='id' data={ products } columns={ columns } />
<BootstrapTable wrapperClasses="boo" keyField="id" data={ products } columns={ columns } />
`; `;
export default () => ( export default () => (
@@ -43,6 +44,9 @@ export default () => (
<h4> Customized table className </h4> <h4> Customized table className </h4>
<BootstrapTable classes="foo" keyField="id" data={ products } columns={ columns } /> <BootstrapTable classes="foo" keyField="id" data={ products } columns={ columns } />
<h4> Customized wrapper className </h4>
<BootstrapTable wrapperClasses="boo" keyField="id" data={ products } columns={ columns } />
<Code>{ sourceCode }</Code> <Code>{ sourceCode }</Code>
</div> </div>
); );

View File

@@ -0,0 +1,64 @@
/* eslint react/prefer-stateless-function: 0 */
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import cellEditFactory, { Type } from 'react-bootstrap-table2-editor';
import Code from 'components/common/code-block';
import { todosGenerator } from 'utils/common';
const todos = todosGenerator();
const columns = [{
dataField: 'id',
text: 'Todo ID'
}, {
dataField: 'todo',
text: 'Todo Name'
}, {
dataField: 'done',
text: 'Done',
editor: {
type: Type.CHECKBOX,
value: 'Y:N'
}
}];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
import cellEditFactory, { Type } from 'react-bootstrap-table2-editor';
const columns = [{
dataField: 'id',
text: 'Todo ID'
}, {
dataField: 'todo',
text: 'Todo Name'
}, {
dataField: 'done',
text: 'Done',
editor: {
type: Type.CHECKBOX,
value: 'Y:N'
}
}];
<BootstrapTable
keyField="id"
data={ todos }
columns={ columns }
cellEdit={ cellEditFactory({ mode: 'click', blurToSave: true }) }
/>
`;
export default () => (
<div>
<h3>Dropdown Editor</h3>
<BootstrapTable
keyField="id"
data={ todos }
columns={ columns }
cellEdit={ cellEditFactory({ mode: 'click', blurToSave: true }) }
/>
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -0,0 +1,130 @@
/* eslint react/prefer-stateless-function: 0 */
/* eslint no-return-assign: 0 */
/* eslint no-unused-vars: 0 */
import React from 'react';
import PropTypes from 'prop-types';
import BootstrapTable from 'react-bootstrap-table-next';
import cellEditFactory from 'react-bootstrap-table2-editor';
import Code from 'components/common/code-block';
import { productsQualityGenerator } from 'utils/common';
const products = productsQualityGenerator();
class QualityRanger extends React.Component {
static propTypes = {
value: PropTypes.number,
onUpdate: PropTypes.func.isRequired
}
static defaultProps = {
value: 0
}
getValue() {
return parseInt(this.range.value, 10);
}
render() {
const { value, onUpdate, ...rest } = this.props;
return [
<input
{ ...rest }
key="range"
ref={ node => this.range = node }
type="range"
min="0"
max="100"
/>,
<button
key="submit"
className="btn btn-default"
onClick={ () => onUpdate(this.getValue()) }
>
done
</button>
];
}
}
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'quality',
text: 'Product Quality',
editorRenderer: (editorProps, value, row, column, rowIndex, columnIndex) => (
<QualityRanger { ...editorProps } value={ value } />
)
}];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
import cellEditFactory from 'react-bootstrap-table2-editor';
class QualityRanger extends React.Component {
static propTypes = {
value: PropTypes.number,
onUpdate: PropTypes.func.isRequired
}
static defaultProps = {
value: 0
}
getValue() {
return parseInt(this.range.value, 10);
}
render() {
const { value, onUpdate, ...rest } = this.props;
return [
<input
{ ...rest }
key="range"
ref={ node => this.range = node }
type="range"
min="0"
max="100"
/>,
<button
key="submit"
className="btn btn-default"
onClick={ () => onUpdate(this.getValue()) }
>
done
</button>
];
}
}
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'quality',
text: 'Product Quality',
editorRenderer: (editorProps, value, row, rowIndex, columnIndex) => (
<QualityRanger { ...editorProps } value={ value } />
)
}];
<BootstrapTable
keyField="id"
data={ products }
columns={ columns }
cellEdit={ cellEditFactory({ mode: 'click', blurToSave: true }) }
/>
`;
export default () => (
<div>
<h3>Dropdown Editor</h3>
<BootstrapTable
keyField="id"
data={ products }
columns={ columns }
cellEdit={ cellEditFactory({ mode: 'click', blurToSave: true }) }
/>
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -0,0 +1,77 @@
/* eslint prefer-template: 0 */
/* eslint react/prefer-stateless-function: 0 */
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import cellEditFactory, { Type } from 'react-bootstrap-table2-editor';
import Code from 'components/common/code-block';
import { stockGenerator } from 'utils/common';
const stocks = stockGenerator();
const columns = [{
dataField: 'id',
text: 'ID'
}, {
dataField: 'name',
text: 'Name'
}, {
dataField: 'inStockDate',
text: 'Stock Date',
formatter: (cell) => {
let dateObj = cell;
if (typeof cell !== 'object') {
dateObj = new Date(cell);
}
return `${('0' + dateObj.getDate()).slice(-2)}/${('0' + (dateObj.getMonth() + 1)).slice(-2)}/${dateObj.getFullYear()}`;
},
editor: {
type: Type.DATE
}
}];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
import cellEditFactory, { Type } from 'react-bootstrap-table2-editor';
const columns = [{
dataField: 'id',
text: 'ID'
}, {
dataField: 'name',
text: 'Name'
}, {
dataField: 'inStockDate',
text: 'Stock Date',
formatter: (cell) => {
let dateObj = cell;
if (typeof cell !== 'object') {
dateObj = new Date(cell);
}
return \`$\{('0' + dateObj.getDate()).slice(-2)}/$\{('0' + (dateObj.getMonth() + 1)).slice(-2)}/$\{dateObj.getFullYear()}\`;
},
editor: {
type: Type.DATE
}
}];
<BootstrapTable
keyField="id"
data={ stocks }
columns={ columns }
cellEdit={ cellEditFactory({ mode: 'click', blurToSave: true }) }
/>
`;
export default () => (
<div>
<h3>Dropdown Editor</h3>
<BootstrapTable
keyField="id"
data={ stocks }
columns={ columns }
cellEdit={ cellEditFactory({ mode: 'click', blurToSave: true }) }
/>
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -0,0 +1,100 @@
/* eslint react/prefer-stateless-function: 0 */
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import cellEditFactory, { Type } from 'react-bootstrap-table2-editor';
import Code from 'components/common/code-block';
import { jobsGenerator } from 'utils/common';
const jobs = jobsGenerator();
const columns = [{
dataField: 'id',
text: 'Job ID'
}, {
dataField: 'name',
text: 'Job Name'
}, {
dataField: 'owner',
text: 'Job Owner'
}, {
dataField: 'type',
text: 'Job Type',
editor: {
type: Type.SELECT,
options: [{
value: 'A',
label: 'A'
}, {
value: 'B',
label: 'B'
}, {
value: 'C',
label: 'C'
}, {
value: 'D',
label: 'D'
}, {
value: 'E',
label: 'E'
}]
}
}];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
import cellEditFactory, { Type } from 'react-bootstrap-table2-editor';
const columns = [{
dataField: 'id',
text: 'Job ID'
}, {
dataField: 'name',
text: 'Job Name'
}, {
dataField: 'owner',
text: 'Job Owner'
}, {
dataField: 'type',
text: 'Job Type',
editor: {
type: Type.SELECT,
options: [{
value: 'A',
label: 'A'
}, {
value: 'B',
label: 'B'
}, {
value: 'C',
label: 'C'
}, {
value: 'D',
label: 'D'
}, {
value: 'E',
label: 'E'
}]
}
}];
<BootstrapTable
keyField="id"
data={ jobs }
columns={ columns }
cellEdit={ cellEditFactory({ mode: 'click', blurToSave: true }) }
/>
`;
export default () => (
<div>
<h3>Dropdown Editor</h3>
<BootstrapTable
keyField="id"
data={ jobs }
columns={ columns }
cellEdit={ cellEditFactory({ mode: 'click', blurToSave: true }) }
/>
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -0,0 +1,61 @@
/* eslint no-unused-vars: 0 */
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import cellEditFactory from 'react-bootstrap-table2-editor';
import Code from 'components/common/code-block';
import { productsGenerator } from 'utils/common';
const products = productsGenerator();
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name',
editorClasses: 'editing-name'
}, {
dataField: 'price',
text: 'Product Price',
editorClasses: (cell, row, rowIndex, colIndex) =>
(cell > 2101 ? 'editing-price-bigger-than-2101' : 'editing-price-small-than-2101')
}];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
import cellEditFactory from 'react-bootstrap-table2-editor';
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name',
editorClasses: 'editing-name'
}, {
dataField: 'price',
text: 'Product Price',
editorClasses: (cell, row, rowIndex, colIndex) =>
(cell > 2101 ? 'editing-price-bigger-than-2101' : 'editing-price-small-than-2101')
}];
<BootstrapTable
keyField="id"
data={ products }
columns={ columns }
cellEdit={ cellEditFactory({ mode: 'click' }) }
/>
`;
export default () => (
<div>
<BootstrapTable
keyField="id"
data={ products }
columns={ columns }
cellEdit={ cellEditFactory({ mode: 'click' }) }
/>
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -0,0 +1,69 @@
/* eslint no-unused-vars: 0 */
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import cellEditFactory from 'react-bootstrap-table2-editor';
import Code from 'components/common/code-block';
import { productsGenerator } from 'utils/common';
const products = productsGenerator();
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name',
editorStyle: {
backgroundColor: '#20B2AA'
}
}, {
dataField: 'price',
text: 'Product Price',
editorStyle: (cell, row, rowIndex, colIndex) => {
const backgroundColor = cell > 2101 ? '#00BFFF' : '#00FFFF';
return { backgroundColor };
}
}];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
import cellEditFactory from 'react-bootstrap-table2-editor';
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name',
editorStyle: {
backgroundColor: '#20B2AA'
}
}, {
dataField: 'price',
text: 'Product Price',
editorStyle: (cell, row, rowIndex, colIndex) => {
const backgroundColor = cell > 2101 ? '#00BFFF' : '#00FFFF';
return { backgroundColor };
}
}];
<BootstrapTable
keyField="id"
data={ products }
columns={ columns }
cellEdit={ cellEditFactory({ mode: 'click' }) }
/>
`;
export default () => (
<div>
<BootstrapTable
keyField="id"
data={ products }
columns={ columns }
cellEdit={ cellEditFactory({ mode: 'click' }) }
/>
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -0,0 +1,68 @@
/* eslint react/prefer-stateless-function: 0 */
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import cellEditFactory, { Type } from 'react-bootstrap-table2-editor';
import Code from 'components/common/code-block';
import { jobsGenerator } from 'utils/common';
const jobs = jobsGenerator();
const columns = [{
dataField: 'id',
text: 'Job ID'
}, {
dataField: 'name',
text: 'Job Name'
}, {
dataField: 'owner',
text: 'Job Owner'
}, {
dataField: 'type',
text: 'Job Type',
editor: {
type: Type.TEXTAREA
}
}];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
import cellEditFactory, { Type } from 'react-bootstrap-table2-editor';
const columns = [{
dataField: 'id',
text: 'Job ID'
}, {
dataField: 'name',
text: 'Job Name'
}, {
dataField: 'owner',
text: 'Job Owner'
}, {
dataField: 'type',
text: 'Job Type',
editor: {
type: Type.TEXTAREA
}
}];
<BootstrapTable
keyField="id"
data={ jobs }
columns={ columns }
cellEdit={ cellEditFactory({ mode: 'click', blurToSave: true }) }
/>
`;
export default () => (
<div>
<h3>Dropdown Editor</h3>
<BootstrapTable
keyField="id"
data={ jobs }
columns={ columns }
cellEdit={ cellEditFactory({ mode: 'click', blurToSave: true }) }
/>
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -0,0 +1,77 @@
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, { dateFilter, Comparator } from 'react-bootstrap-table2-filter';
import Code from 'components/common/code-block';
import { stockGenerator } from 'utils/common';
const stocks = stockGenerator(8);
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'inStockDate',
text: 'InStock Date',
filter: dateFilter({
delay: 400,
placeholder: 'custom placeholder',
withoutEmptyComparatorOption: true,
comparators: [Comparator.EQ, Comparator.GT, Comparator.LT],
style: { display: 'inline-grid' },
className: 'custom-datefilter-class',
comparatorStyle: { backgroundColor: 'antiquewhite' },
comparatorClassName: 'custom-comparator-class',
dateStyle: { backgroundColor: 'cadetblue', margin: '0px' },
dateClassName: 'custom-date-class'
})
}];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, { dateFilter } from 'react-bootstrap-table2-filter';
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'inStockDate',
text: 'InStock Date',
filter: dateFilter({
delay: 400,
placeholder: 'custom placeholder',
withoutEmptyComparatorOption: true,
comparators: [Comparator.EQ, Comparator.GT, Comparator.LT],
style: { display: 'inline-grid' },
className: 'custom-datefilter-class',
comparatorStyle: { backgroundColor: 'antiquewhite' },
comparatorClassName: 'custom-comparator-class',
dateStyle: { backgroundColor: 'cadetblue', margin: '0px' },
dateClassName: 'custom-date-class'
})
}];
<BootstrapTable
keyField="id"
data={ stocks }
columns={ columns }
filter={ filterFactory() }
/>
`;
export default () => (
<div>
<BootstrapTable
keyField="id"
data={ stocks }
columns={ columns }
filter={ filterFactory() }
/>
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -0,0 +1,59 @@
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, { dateFilter, Comparator } from 'react-bootstrap-table2-filter';
import Code from 'components/common/code-block';
import { stockGenerator } from 'utils/common';
const stocks = stockGenerator(8);
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'inStockDate',
text: 'InStock Date',
filter: dateFilter({
defaultValue: { date: new Date(2018, 0, 1), comparator: Comparator.GT }
})
}];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, { dateFilter } from 'react-bootstrap-table2-filter';
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'inStockDate',
text: 'InStock Date',
filter: dateFilter({
defaultValue: { date: new Date(2018, 0, 1), comparator: Comparator.GT }
})
}];
<BootstrapTable
keyField="id"
data={ stocks }
columns={ columns }
filter={ filterFactory() }
/>
`;
export default () => (
<div>
<BootstrapTable
keyField="id"
data={ stocks }
columns={ columns }
filter={ filterFactory() }
/>
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -0,0 +1,55 @@
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, { dateFilter } from 'react-bootstrap-table2-filter';
import Code from 'components/common/code-block';
import { stockGenerator } from 'utils/common';
const stocks = stockGenerator(8);
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'inStockDate',
text: 'InStock Date',
filter: dateFilter()
}];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, { dateFilter } from 'react-bootstrap-table2-filter';
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'inStockDate',
text: 'InStock Date',
filter: dateFilter()
}];
<BootstrapTable
keyField="id"
data={ stocks }
columns={ columns }
filter={ filterFactory() }
/>
`;
export default () => (
<div>
<BootstrapTable
keyField="id"
data={ stocks }
columns={ columns }
filter={ filterFactory() }
/>
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -0,0 +1,85 @@
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, { dateFilter, Comparator } from 'react-bootstrap-table2-filter';
import Code from 'components/common/code-block';
import { stockGenerator } from 'utils/common';
const stocks = stockGenerator(8);
let inStockDateFilter;
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'inStockDate',
text: 'InStock Date',
filter: dateFilter({
getFilter: (filter) => {
// inStockDateFilter was assigned once the component has been mounted.
inStockDateFilter = filter;
}
})
}];
const handleClick = () => {
inStockDateFilter({
date: new Date(2018, 0, 1),
comparator: Comparator.GT
});
};
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, { dateFilter, Comparator } from 'react-bootstrap-table2-filter';
let inStockDateFilter;
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'inStockDate',
text: 'InStock Date',
filter: dateFilter({
getFilter: (filter) => {
// inStockDateFilter was assigned once the component has been mounted.
inStockDateFilter = filter;
}
})
}];
const handleClick = () => {
inStockDateFilter({
date: new Date(2018, 0, 1),
comparator: Comparator.GT
});
};
export default () => (
<div>
<button className="btn btn-lg btn-primary" onClick={ handleClick }> filter InStock Date columns which is greater than 2018.01.01 </button>
<BootstrapTable keyField='id' data={ stocks } columns={ columns } filter={ filterFactory() } />
</div>
);
`;
export default () => (
<div>
<button className="btn btn-lg btn-primary" onClick={ handleClick }> filter InStock Date columns which is greater than 2018.01.01 </button>
<BootstrapTable
keyField="id"
data={ stocks }
columns={ columns }
filter={ filterFactory() }
/>
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -0,0 +1,85 @@
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, { numberFilter, Comparator } from 'react-bootstrap-table2-filter';
import Code from 'components/common/code-block';
import { productsGenerator } from 'utils/common';
const products = productsGenerator(8);
let priceFilter;
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'price',
text: 'Product Price',
filter: numberFilter({
getFilter: (filter) => {
// pricerFilter was assigned once the component has been mounted.
priceFilter = filter;
}
})
}];
const handleClick = () => {
priceFilter({
number: 2103,
comparator: Comparator.GT
});
};
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, { numberFilter } from 'react-bootstrap-table2-filter';
let priceFilter;
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'price',
text: 'Product Price',
filter: numberFilter({
getFilter: (filter) => {
// pricerFilter was assigned once the component has been mounted.
priceFilter = filter;
}
})
}];
const handleClick = () => {
priceFilter({
number: 2103,
comparator: Comparator.GT
});
};
export default () => (
<div>
<button className="btn btn-lg btn-primary" onClick={ handleClick }> filter all columns which is greater than 2103 </button>
<BootstrapTable keyField='id' data={ products } columns={ columns } filter={ filterFactory() } />
</div>
);
`;
export default () => (
<div>
<button className="btn btn-lg btn-primary" onClick={ handleClick }> filter all columns which is greater than 2103 </button>
<BootstrapTable
keyField="id"
data={ products }
columns={ columns }
filter={ filterFactory() }
/>
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -0,0 +1,96 @@
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, { selectFilter } from 'react-bootstrap-table2-filter';
import Code from 'components/common/code-block';
import { productsQualityGenerator } from 'utils/common';
const products = productsQualityGenerator(6);
let qualityFilter;
const selectOptions = {
0: 'good',
1: 'Bad',
2: 'unknown'
};
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'quality',
text: 'Product Quality',
formatter: cell => selectOptions[cell],
filter: selectFilter({
options: selectOptions,
getFilter: (filter) => {
// qualityFilter was assigned once the component has been mounted.
qualityFilter = filter;
}
})
}];
const handleClick = () => {
qualityFilter(0);
};
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, { selectFilter } from 'react-bootstrap-table2-filter';
let qualityFilter;
const selectOptions = {
0: 'good',
1: 'Bad',
2: 'unknown'
};
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'quality',
text: 'Product Quality',
formatter: cell => selectOptions[cell],
filter: selectFilter({
options: selectOptions,
getFilter: (filter) => {
// qualityFilter was assigned once the component has been mounted.
qualityFilter = filter;
}
})
}];
const handleClick = () => {
qualityFilter(0);
};
export default () => (
<div>
<button className="btn btn-lg btn-primary" onClick={ handleClick }>{' filter columns by option "good" '}</button>
<BootstrapTable keyField='id' data={ products } columns={ columns } filter={ filterFactory() } />
</div>
);
`;
export default () => (
<div>
<button className="btn btn-lg btn-primary" onClick={ handleClick }>{' filter columns by option "good" '}</button>
<BootstrapTable
keyField="id"
data={ products }
columns={ columns }
filter={ filterFactory() }
/>
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -0,0 +1,81 @@
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);
let nameFilter;
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name',
filter: textFilter({
getFilter: (filter) => {
// nameFilter was assigned once the component has been mounted.
nameFilter = filter;
}
})
}, {
dataField: 'price',
text: 'Product Price',
filter: textFilter()
}];
const handleClick = () => {
nameFilter(0);
};
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, { textFilter } from 'react-bootstrap-table2-filter';
let nameFilter;
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name',
filter: textFilter({
getFilter: (filter) => {
// nameFilter was assigned once the component has been mounted.
nameFilter = filter;
}
})
}, {
dataField: 'price',
text: 'Product Price',
filter: textFilter()
}];
const handleClick = () => {
nameFilter(0);
};
export default () => (
<div>
<button className="btn btn-lg btn-primary" onClick={ handleClick }> filter columns by 0 </button>
<BootstrapTable keyField='id' data={ products } columns={ columns } filter={ filterFactory() } />
</div>
);
`;
export default () => (
<div>
<button className="btn btn-lg btn-primary" onClick={ handleClick }> filter columns by 0 </button>
<BootstrapTable
keyField="id"
data={ products }
columns={ columns }
filter={ filterFactory() }
/>
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -24,6 +24,12 @@ import BootstrapTable from 'react-bootstrap-table-next';
import paginationFactory from 'react-bootstrap-table2-paginator'; import paginationFactory from 'react-bootstrap-table2-paginator';
// ... // ...
const customTotal = (from, to, size) => (
<span className="react-bootstrap-table-pagination-total">
Showing { from } to { to } of { size } Results
</span>
);
const options = { const options = {
paginationSize: 4, paginationSize: 4,
pageStartIndex: 0, pageStartIndex: 0,
@@ -39,6 +45,8 @@ const options = {
prePageTitle: 'Pre page', prePageTitle: 'Pre page',
firstPageTitle: 'Next page', firstPageTitle: 'Next page',
lastPageTitle: 'Last page', lastPageTitle: 'Last page',
showTotal: true,
paginationTotalRenderer: customTotal,
sizePerPageList: [{ sizePerPageList: [{
text: '5', value: 5 text: '5', value: 5
}, { }, {
@@ -50,11 +58,18 @@ const options = {
<BootstrapTable keyField='id' data={ products } columns={ columns } pagination={ paginationFactory(options) } /> <BootstrapTable keyField='id' data={ products } columns={ columns } pagination={ paginationFactory(options) } />
`; `;
const customTotal = (from, to, size) => (
<span className="react-bootstrap-table-pagination-total">
Showing { from } to { to } of { size } Results
</span>
);
const options = { const options = {
paginationSize: 4, paginationSize: 4,
pageStartIndex: 0, pageStartIndex: 0,
// alwaysShowAllBtns: true // Always show next and previous button // alwaysShowAllBtns: true, // Always show next and previous button
// withFirstAndLast: false // Hide the going to First and Last page button // withFirstAndLast: false, // Hide the going to First and Last page button
// hideSizePerPage: true, // Hide the sizePerPage dropdown always // hideSizePerPage: true, // Hide the sizePerPage dropdown always
// hidePageListOnlyOnePage: true, // Hide the pagination list when only one page // hidePageListOnlyOnePage: true, // Hide the pagination list when only one page
firstPageText: 'First', firstPageText: 'First',
@@ -65,6 +80,8 @@ const options = {
prePageTitle: 'Pre page', prePageTitle: 'Pre page',
firstPageTitle: 'Next page', firstPageTitle: 'Next page',
lastPageTitle: 'Last page', lastPageTitle: 'Last page',
showTotal: true,
paginationTotalRenderer: customTotal,
sizePerPageList: [{ sizePerPageList: [{
text: '5', value: 5 text: '5', value: 5
}, { }, {

View File

@@ -0,0 +1,107 @@
/* eslint react/prop-types: 0 */
/* eslint no-param-reassign: 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'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'price',
text: 'Product Price'
}];
const selectRow1 = {
mode: 'radio',
clickToSelect: true,
selectionHeaderRenderer: () => 'X',
selectionRenderer: ({ mode, ...rest }) => (
<input type={ mode } { ...rest } />
)
};
const selectRow2 = {
mode: 'checkbox',
clickToSelect: true,
selectionHeaderRenderer: ({ indeterminate, ...rest }) => (
<input
type="checkbox"
ref={ (input) => {
if (input) input.indeterminate = indeterminate;
} }
{ ...rest }
/>
),
selectionRenderer: ({ mode, ...rest }) => (
<input type={ mode } { ...rest } />
)
};
const sourceCode1 = `\
import BootstrapTable from 'react-bootstrap-table-next';
const columns = ....;
const selectRow = {
mode: 'radio',
clickToSelect: true,
selectionHeaderRenderer: () => 'X',
selectionRenderer: ({ mode, ...rest }) => (
<input type={ mode } { ...rest } />
)
};
<BootstrapTable
keyField='id'
data={ products }
columns={ columns }
selectRow={ selectRow }
/>
`;
const sourceCode2 = `\
import BootstrapTable from 'react-bootstrap-table-next';
const columns = ....;
const selectRow = {
mode: 'checkbox',
clickToSelect: true,
selectionHeaderRenderer: ({ indeterminate, ...rest }) => (
<input
type="checkbox"
ref={ (input) => {
if (input) input.indeterminate = indeterminate;
} }
{ ...rest }
/>
),
selectionRenderer: ({ mode, ...rest }) => (
<input type={ mode } { ...rest } />
)
};
<BootstrapTable
keyField='id'
data={ products }
columns={ columns }
selectRow={ selectRow }
/>
`;
export default () => (
<div>
<BootstrapTable keyField="id" data={ products } columns={ columns } selectRow={ selectRow1 } />
<Code>{ sourceCode1 }</Code>
<BootstrapTable keyField="id" data={ products } columns={ columns } selectRow={ selectRow2 } />
<Code>{ sourceCode2 }</Code>
</div>
);

View File

@@ -22,14 +22,16 @@ const columns = [{
const selectRow = { const selectRow = {
mode: 'checkbox', mode: 'checkbox',
clickToSelect: true, clickToSelect: true,
onSelect: (row, isSelect, rowIndex) => { onSelect: (row, isSelect, rowIndex, e) => {
console.log(row.id); console.log(row.id);
console.log(isSelect); console.log(isSelect);
console.log(rowIndex); console.log(rowIndex);
console.log(e);
}, },
onSelectAll: (isSelect, rows) => { onSelectAll: (isSelect, rows, e) => {
console.log(isSelect); console.log(isSelect);
console.log(rows); console.log(rows);
console.log(e);
} }
}; };
@@ -49,7 +51,18 @@ const columns = [{
const selectRow = { const selectRow = {
mode: 'checkbox', mode: 'checkbox',
clickToSelect: true clickToSelect: true,
onSelect: (row, isSelect, rowIndex, e) => {
console.log(row.id);
console.log(isSelect);
console.log(rowIndex);
console.log(e);
},
onSelectAll: (isSelect, rows, e) => {
console.log(isSelect);
console.log(rows);
console.log(e);
}
}; };
<BootstrapTable <BootstrapTable

View File

@@ -0,0 +1,62 @@
/* eslint no-unused-vars: 0 */
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import Code from 'components/common/code-block';
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'price',
text: 'Product Price'
}];
const selectRow1 = {
mode: 'checkbox',
clickToSelect: true
};
const sourceCode1 = `\
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 selectRow = {
mode: 'checkbox',
clickToSelect: true
};
<BootstrapTable
keyField='id'
data={ [] }
columns={ columns }
selectRow={ selectRow }
noDataIndication={ 'no results found' }
/>
`;
export default () => (
<div>
<BootstrapTable
keyField="id"
data={ [] }
columns={ columns }
selectRow={ selectRow1 }
noDataIndication={ 'no results found' }
/>
<Code>{ sourceCode1 }</Code>
</div>
);

View File

@@ -0,0 +1,67 @@
/* eslint react/prefer-stateless-function: 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',
sort: true
}, {
dataField: 'name',
text: 'Product Name',
sort: true
}, {
dataField: 'price',
text: 'Product Price',
sort: true
}];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
const columns = [{
dataField: 'id',
text: 'Product ID',
sort: true
}, {
dataField: 'name',
text: 'Product Name',
sort: true
}, {
dataField: 'price',
text: 'Product Price',
sort: true
}];
const defaultSorted = [{
dataField: 'name',
order: 'desc'
}];
<BootstrapTable
keyField="id"
data={ products }
columns={ columns }
defaultSortDirection="asc"
/>
`;
class DefaultSortDirectionTable extends React.PureComponent {
render() {
return (
<div>
<BootstrapTable keyField="id" data={ products } columns={ columns } defaultSortDirection="asc" />
<Code>{ sourceCode }</Code>
</div>
);
}
}
export default DefaultSortDirectionTable;

View File

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

View File

@@ -1,3 +1,5 @@
/* eslint no-mixed-operators: 0 */
/** /**
* products generator for stories * products generator for stories
* *
@@ -27,12 +29,34 @@ export const productsQualityGenerator = (quantity = 5) =>
quality: index % 3 quality: index % 3
})); }));
const jobType = ['A', 'B', 'C', 'D', 'E'];
const jobOwner = ['Allen', 'Bob', 'Cindy'];
export const jobsGenerator = (quantity = 5) => export const jobsGenerator = (quantity = 5) =>
Array.from({ length: quantity }, (value, index) => ({ Array.from({ length: quantity }, (value, index) => ({
id: index, id: index,
name: `Job name ${index}`, name: `Job name ${index}`,
owner: Math.floor(Math.random() * 3), owner: jobOwner[Math.floor((Math.random() * 2) + 1)],
type: Math.floor(Math.random() * 5) type: jobType[Math.floor((Math.random() * 4) + 1)]
}));
export const todosGenerator = (quantity = 5) =>
Array.from({ length: quantity }, (value, index) => ({
id: index,
todo: `Todo item ${index}`,
done: Math.random() > 0.4 ? 'Y' : 'N'
}));
const startDate = new Date(2017, 0, 1);
const endDate = new Date();
export const stockGenerator = (quantity = 5) =>
Array.from({ length: quantity }, (value, index) => ({
id: index,
name: `Todo item ${index}`,
inStockDate:
new Date(startDate.getTime() + Math.random() * (endDate.getTime() - startDate.getTime()))
})); }));
export const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); export const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

View File

@@ -48,6 +48,13 @@ import CustomSelectFilter from 'examples/column-filter/custom-select-filter';
import NumberFilter from 'examples/column-filter/number-filter'; import NumberFilter from 'examples/column-filter/number-filter';
import NumberFilterWithDefaultValue from 'examples/column-filter/number-filter-default-value'; import NumberFilterWithDefaultValue from 'examples/column-filter/number-filter-default-value';
import CustomNumberFilter from 'examples/column-filter/custom-number-filter'; import CustomNumberFilter from 'examples/column-filter/custom-number-filter';
import DateFilter from 'examples/column-filter/date-filter';
import DateFilterWithDefaultValue from 'examples/column-filter/date-filter-default-value';
import CustomDateFilter from 'examples/column-filter/custom-date-filter';
import ProgrammaticallyTextFilter from 'examples/column-filter/programmatically-text-filter';
import ProgrammaticallySelectFilter from 'examples/column-filter/programmatically-select-filter';
import ProgrammaticallyNumberFilter from 'examples/column-filter/programmatically-number-filter';
import ProgrammaticallyDateFilter from 'examples/column-filter/programmatically-date-filter';
// work on rows // work on rows
import RowStyleTable from 'examples/rows/row-style'; import RowStyleTable from 'examples/rows/row-style';
@@ -57,6 +64,7 @@ import RowEventTable from 'examples/rows/row-event';
// table sort // table sort
import EnableSortTable from 'examples/sort/enable-sort-table'; import EnableSortTable from 'examples/sort/enable-sort-table';
import DefaultSortTable from 'examples/sort/default-sort-table'; import DefaultSortTable from 'examples/sort/default-sort-table';
import DefaultSortDirectionTable from 'examples/sort/default-sort-direction';
import SortEvents from 'examples/sort/sort-events'; import SortEvents from 'examples/sort/sort-events';
import CustomSortTable from 'examples/sort/custom-sort-table'; import CustomSortTable from 'examples/sort/custom-sort-table';
import HeaderSortingClassesTable from 'examples/sort/header-sorting-classes'; import HeaderSortingClassesTable from 'examples/sort/header-sorting-classes';
@@ -73,6 +81,13 @@ import CellEditHooks from 'examples/cell-edit/cell-edit-hooks-table';
import CellEditValidator from 'examples/cell-edit/cell-edit-validator-table'; import CellEditValidator from 'examples/cell-edit/cell-edit-validator-table';
import CellEditStyleTable from 'examples/cell-edit/cell-edit-style-table'; import CellEditStyleTable from 'examples/cell-edit/cell-edit-style-table';
import CellEditClassTable from 'examples/cell-edit/cell-edit-class-table'; import CellEditClassTable from 'examples/cell-edit/cell-edit-class-table';
import EditorStyleTable from 'examples/cell-edit/editor-style-table';
import EditorClassTable from 'examples/cell-edit/editor-class-table';
import DropdownEditorTable from 'examples/cell-edit/dropdown-editor-table';
import TextareaEditorTable from 'examples/cell-edit/textarea-editor-table';
import CheckboxEditorTable from 'examples/cell-edit/checkbox-editor-table';
import DateEditorTable from 'examples/cell-edit/date-editor-table';
import CustomEditorTable from 'examples/cell-edit/custom-editor-table';
// work on row selection // work on row selection
import SingleSelectionTable from 'examples/row-selection/single-selection'; import SingleSelectionTable from 'examples/row-selection/single-selection';
@@ -81,8 +96,10 @@ import ClickToSelectTable from 'examples/row-selection/click-to-select';
import DefaultSelectTable from 'examples/row-selection/default-select'; import DefaultSelectTable from 'examples/row-selection/default-select';
import SelectionManagement from 'examples/row-selection/selection-management'; import SelectionManagement from 'examples/row-selection/selection-management';
import ClickToSelectWithCellEditTable from 'examples/row-selection/click-to-select-with-cell-edit'; import ClickToSelectWithCellEditTable from 'examples/row-selection/click-to-select-with-cell-edit';
import SelectionNoDataTable from 'examples/row-selection/selection-no-data';
import SelectionStyleTable from 'examples/row-selection/selection-style'; import SelectionStyleTable from 'examples/row-selection/selection-style';
import SelectionClassTable from 'examples/row-selection/selection-class'; import SelectionClassTable from 'examples/row-selection/selection-class';
import CustomSelectionTable from 'examples/row-selection/custom-selection';
import NonSelectableRowsTable from 'examples/row-selection/non-selectable-rows'; import NonSelectableRowsTable from 'examples/row-selection/non-selectable-rows';
import SelectionBgColorTable from 'examples/row-selection/selection-bgcolor'; import SelectionBgColorTable from 'examples/row-selection/selection-bgcolor';
import SelectionHooks from 'examples/row-selection/selection-hooks'; import SelectionHooks from 'examples/row-selection/selection-hooks';
@@ -159,10 +176,17 @@ storiesOf('Column Filter', module)
.add('Select Filter with Comparator', () => <SelectFilterComparator />) .add('Select Filter with Comparator', () => <SelectFilterComparator />)
.add('Number Filter', () => <NumberFilter />) .add('Number Filter', () => <NumberFilter />)
.add('Number Filter with Default Value', () => <NumberFilterWithDefaultValue />) .add('Number Filter with Default Value', () => <NumberFilterWithDefaultValue />)
.add('Date Filter', () => <DateFilter />)
.add('Date Filter with Default Value', () => <DateFilterWithDefaultValue />)
.add('Custom Text Filter', () => <CustomTextFilter />) .add('Custom Text Filter', () => <CustomTextFilter />)
.add('Custom Select Filter', () => <CustomSelectFilter />) .add('Custom Select Filter', () => <CustomSelectFilter />)
.add('Custom Number Filter', () => <CustomNumberFilter />) .add('Custom Number Filter', () => <CustomNumberFilter />)
.add('Custom Filter Value', () => <CustomFilterValue />); .add('Custom Date Filter', () => <CustomDateFilter />)
.add('Custom Filter Value', () => <CustomFilterValue />)
.add('Programmatically Text Filter', () => <ProgrammaticallyTextFilter />)
.add('Programmatically Select Filter', () => <ProgrammaticallySelectFilter />)
.add('Programmatically Number Filter', () => <ProgrammaticallyNumberFilter />)
.add('Programmatically Date Filter', () => <ProgrammaticallyDateFilter />);
storiesOf('Work on Rows', module) storiesOf('Work on Rows', module)
.add('Customize Row Style', () => <RowStyleTable />) .add('Customize Row Style', () => <RowStyleTable />)
@@ -172,6 +196,7 @@ storiesOf('Work on Rows', module)
storiesOf('Sort Table', module) storiesOf('Sort Table', module)
.add('Enable Sort', () => <EnableSortTable />) .add('Enable Sort', () => <EnableSortTable />)
.add('Default Sort Table', () => <DefaultSortTable />) .add('Default Sort Table', () => <DefaultSortTable />)
.add('Default Sort Direction Table', () => <DefaultSortDirectionTable />)
.add('Sort Events', () => <SortEvents />) .add('Sort Events', () => <SortEvents />)
.add('Custom Sort Fuction', () => <CustomSortTable />) .add('Custom Sort Fuction', () => <CustomSortTable />)
.add('Custom Classes on Sorting Header Column', () => <HeaderSortingClassesTable />) .add('Custom Classes on Sorting Header Column', () => <HeaderSortingClassesTable />)
@@ -186,8 +211,15 @@ storiesOf('Cell Editing', module)
.add('Cell Level Editable', () => <CellLevelEditable />) .add('Cell Level Editable', () => <CellLevelEditable />)
.add('Rich Hook Functions', () => <CellEditHooks />) .add('Rich Hook Functions', () => <CellEditHooks />)
.add('Validation', () => <CellEditValidator />) .add('Validation', () => <CellEditValidator />)
.add('Custom Cell Style When Editing', () => <CellEditStyleTable />) .add('Custom Cell Style', () => <CellEditStyleTable />)
.add('Custom Cell Classes When Editing', () => <CellEditClassTable />); .add('Custom Cell Classes', () => <CellEditClassTable />)
.add('Custom Editor Classes', () => <EditorClassTable />)
.add('Custom Editor Style', () => <EditorStyleTable />)
.add('Dropdown Editor', () => <DropdownEditorTable />)
.add('Textarea Editor', () => <TextareaEditorTable />)
.add('Checkbox Editor', () => <CheckboxEditorTable />)
.add('Date Editor', () => <DateEditorTable />)
.add('Custom Editor', () => <CustomEditorTable />);
storiesOf('Row Selection', module) storiesOf('Row Selection', module)
.add('Single Selection', () => <SingleSelectionTable />) .add('Single Selection', () => <SingleSelectionTable />)
@@ -196,8 +228,10 @@ storiesOf('Row Selection', module)
.add('Default Select', () => <DefaultSelectTable />) .add('Default Select', () => <DefaultSelectTable />)
.add('Selection Management', () => <SelectionManagement />) .add('Selection Management', () => <SelectionManagement />)
.add('Click to Select and Edit Cell', () => <ClickToSelectWithCellEditTable />) .add('Click to Select and Edit Cell', () => <ClickToSelectWithCellEditTable />)
.add('Selection without Data', () => <SelectionNoDataTable />)
.add('Selection Style', () => <SelectionStyleTable />) .add('Selection Style', () => <SelectionStyleTable />)
.add('Selection Class', () => <SelectionClassTable />) .add('Selection Class', () => <SelectionClassTable />)
.add('Custom Selection', () => <CustomSelectionTable />)
.add('Selection Background Color', () => <SelectionBgColorTable />) .add('Selection Background Color', () => <SelectionBgColorTable />)
.add('Not Selectabled Rows', () => <NonSelectableRowsTable />) .add('Not Selectabled Rows', () => <NonSelectableRowsTable />)
.add('Selection Hooks', () => <SelectionHooks />) .add('Selection Hooks', () => <SelectionHooks />)

View File

@@ -5,3 +5,7 @@ table.foo {
table#bar { table#bar {
background-color: $light-blue; background-color: $light-blue;
} }
div.boo {
border: 2px solid salmon;
}

View File

@@ -19,6 +19,7 @@ You can get all types of filters via import and these filters are a factory func
* TextFilter * TextFilter
* SelectFilter * SelectFilter
* NumberFilter * NumberFilter
* DateFilter
* **Coming soon!** * **Coming soon!**
## Add CSS ## Add CSS
@@ -148,5 +149,44 @@ const numberFilter = numberFilter({
defaultValue: { number: 2103, comparator: Comparator.GT } // default value defaultValue: { number: 2103, comparator: Comparator.GT } // default value
}) })
// omit...
```
## Date Filter
```js
import filterFactory, { dateFilter } from 'react-bootstrap-table2-filter';
const columns = [..., {
dataField: 'date',
text: 'Product date',
filter: dateFilter()
}];
<BootstrapTable keyField='id' data={ products } columns={ columns } filter={ filterFactory() } />
```
> **Notes:** date filter accept a Javascript Date object in your raw data.
Date filter is same as other filter, you can custom the date filter via `dateFilter` factory function:
```js
import filterFactory, { selectFilter, Comparator } from 'react-bootstrap-table2-filter';
// omit...
const dateFilter = dateFilter({
delay: 600, // how long will trigger filtering after user typing, default is 500 ms
placeholder: 'custom placeholder', // placeholder for date input
withoutEmptyComparatorOption: true, // dont render empty option for comparator
comparators: [Comparator.EQ, Comparator.GT, Comparator.LT], // Custom the comparators
style: { display: 'inline-grid' }, // custom the style on date filter
className: 'custom-dateFilter-class', // custom the class on date filter
comparatorStyle: { backgroundColor: 'antiquewhite' }, // custom the style on comparator select
comparatorClassName: 'custom-comparator-class', // custom the class on comparator select
dateStyle: { backgroundColor: 'cadetblue', margin: '0px' }, // custom the style on date input
dateClassName: 'custom-date-class', // custom the class on date input
defaultValue: { date: new Date(2018, 0, 1), comparator: Comparator.GT } // default value
})
// omit... // omit...
``` ```

View File

@@ -1,6 +1,7 @@
import TextFilter from './src/components/text'; import TextFilter from './src/components/text';
import SelectFilter from './src/components/select'; import SelectFilter from './src/components/select';
import NumberFilter from './src/components/number'; import NumberFilter from './src/components/number';
import DateFilter from './src/components/date';
import wrapperFactory from './src/wrapper'; import wrapperFactory from './src/wrapper';
import * as Comparison from './src/comparison'; import * as Comparison from './src/comparison';
@@ -25,3 +26,8 @@ export const numberFilter = (props = {}) => ({
Filter: NumberFilter, Filter: NumberFilter,
props props
}); });
export const dateFilter = (props = {}) => ({
Filter: DateFilter,
props
});

View File

@@ -1,6 +1,6 @@
{ {
"name": "react-bootstrap-table2-filter", "name": "react-bootstrap-table2-filter",
"version": "0.1.3", "version": "0.2.0",
"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

@@ -0,0 +1,204 @@
/* eslint react/require-default-props: 0 */
/* eslint no-return-assign: 0 */
/* eslint prefer-template: 0 */
import React, { Component } from 'react';
import { PropTypes } from 'prop-types';
import * as Comparator from '../comparison';
import { FILTER_TYPE } from '../const';
const legalComparators = [
Comparator.EQ,
Comparator.NE,
Comparator.GT,
Comparator.GE,
Comparator.LT,
Comparator.LE
];
function dateParser(d) {
return `${d.getFullYear()}-${('0' + (d.getMonth() + 1)).slice(-2)}-${('0' + d.getDate()).slice(-2)}`;
}
class DateFilter extends Component {
constructor(props) {
super(props);
this.timeout = null;
this.comparators = props.comparators || legalComparators;
this.applyFilter = this.applyFilter.bind(this);
this.onChangeDate = this.onChangeDate.bind(this);
this.onChangeComparator = this.onChangeComparator.bind(this);
}
componentDidMount() {
const { getFilter } = this.props;
const comparator = this.dateFilterComparator.value;
const date = this.inputDate.value;
if (comparator && date) {
this.applyFilter(date, comparator);
}
// export onFilter function to allow users to access
if (getFilter) {
getFilter((filterVal) => {
this.dateFilterComparator.value = filterVal.comparator;
this.inputDate.value = dateParser(filterVal.date);
this.applyFilter(filterVal.date, filterVal.comparator);
});
}
}
componentWillUnmount() {
if (this.timeout) clearTimeout(this.timeout);
}
onChangeDate(e) {
const comparator = this.dateFilterComparator.value;
const filterValue = e.target.value;
this.applyFilter(filterValue, comparator);
}
onChangeComparator(e) {
const value = this.inputDate.value;
const comparator = e.target.value;
this.applyFilter(value, comparator);
}
getComparatorOptions() {
const optionTags = [];
const { withoutEmptyComparatorOption } = this.props;
if (!withoutEmptyComparatorOption) {
optionTags.push(<option key="-1" />);
}
for (let i = 0; i < this.comparators.length; i += 1) {
optionTags.push(
<option key={ i } value={ this.comparators[i] }>
{ this.comparators[i] }
</option>
);
}
return optionTags;
}
getDefaultDate() {
let defaultDate = '';
const { defaultValue } = this.props;
if (defaultValue && defaultValue.date) {
// Set the appropriate format for the input type=date, i.e. "YYYY-MM-DD"
defaultDate = dateParser(new Date(defaultValue.date));
}
return defaultDate;
}
applyFilter(value, comparator) {
if (!comparator || !value) {
return;
}
const { column, onFilter, delay } = this.props;
const execute = () => {
const date = typeof value !== 'object' ? new Date(value) : value;
onFilter(column, FILTER_TYPE.DATE)({ date, comparator });
};
if (delay) {
this.timeout = setTimeout(() => { execute(); }, delay);
} else {
execute();
}
}
render() {
const {
placeholder,
column: { text },
style,
comparatorStyle,
dateStyle,
className,
comparatorClassName,
dateClassName,
defaultValue
} = this.props;
return (
<div className={ `filter date-filter ${className}` } style={ style }>
<select
ref={ n => this.dateFilterComparator = n }
style={ comparatorStyle }
className={ `date-filter-comparator form-control ${comparatorClassName}` }
onChange={ this.onChangeComparator }
defaultValue={ defaultValue ? defaultValue.comparator : '' }
>
{ this.getComparatorOptions() }
</select>
<input
ref={ n => this.inputDate = n }
className={ `filter date-filter-input form-control ${dateClassName}` }
style={ dateStyle }
type="date"
onChange={ this.onChangeDate }
placeholder={ placeholder || `Enter ${text}...` }
defaultValue={ this.getDefaultDate() }
/>
</div>
);
}
}
DateFilter.propTypes = {
onFilter: PropTypes.func.isRequired,
column: PropTypes.object.isRequired,
delay: PropTypes.number,
defaultValue: PropTypes.shape({
date: PropTypes.oneOfType([PropTypes.object]),
comparator: PropTypes.oneOf([...legalComparators, ''])
}),
/* eslint consistent-return: 0 */
comparators: (props, propName) => {
if (!props[propName]) {
return;
}
for (let i = 0; i < props[propName].length; i += 1) {
let comparatorIsValid = false;
for (let j = 0; j < legalComparators.length; j += 1) {
if (legalComparators[j] === props[propName][i] || props[propName][i] === '') {
comparatorIsValid = true;
break;
}
}
if (!comparatorIsValid) {
return new Error(`Date comparator provided is not supported.
Use only ${legalComparators}`);
}
}
},
placeholder: PropTypes.string,
withoutEmptyComparatorOption: PropTypes.bool,
style: PropTypes.object,
comparatorStyle: PropTypes.object,
dateStyle: PropTypes.object,
className: PropTypes.string,
comparatorClassName: PropTypes.string,
dateClassName: PropTypes.string,
getFilter: PropTypes.func
};
DateFilter.defaultProps = {
delay: 0,
defaultValue: {
date: undefined,
comparator: ''
},
withoutEmptyComparatorOption: false,
comparators: legalComparators,
placeholder: undefined,
style: undefined,
className: '',
comparatorStyle: undefined,
comparatorClassName: '',
dateStyle: undefined,
dateClassName: ''
};
export default DateFilter;

View File

@@ -1,3 +1,4 @@
/* eslint react/require-default-props: 0 */
/* eslint no-return-assign: 0 */ /* eslint no-return-assign: 0 */
import React, { Component } from 'react'; import React, { Component } from 'react';
@@ -30,11 +31,25 @@ class NumberFilter extends Component {
} }
componentDidMount() { componentDidMount() {
const { column, onFilter } = this.props; const { column, onFilter, getFilter } = this.props;
const comparator = this.numberFilterComparator.value; const comparator = this.numberFilterComparator.value;
const number = this.numberFilter.value; const number = this.numberFilter.value;
if (comparator && number) { if (comparator && number) {
onFilter(column, { number, comparator }, FILTER_TYPE.NUMBER); onFilter(column, FILTER_TYPE.NUMBER)({ number, comparator });
}
// export onFilter function to allow users to access
if (getFilter) {
getFilter((filterVal) => {
this.setState(() => ({ isSelected: (filterVal !== '') }));
this.numberFilterComparator.value = filterVal.comparator;
this.numberFilter.value = filterVal.number;
onFilter(column, FILTER_TYPE.NUMBER)({
number: filterVal.number,
comparator: filterVal.comparator
});
});
} }
} }
@@ -53,7 +68,7 @@ class NumberFilter extends Component {
} }
const filterValue = e.target.value; const filterValue = e.target.value;
this.timeout = setTimeout(() => { this.timeout = setTimeout(() => {
onFilter(column, { number: filterValue, comparator }, FILTER_TYPE.NUMBER); onFilter(column, FILTER_TYPE.NUMBER)({ number: filterValue, comparator });
}, delay); }, delay);
} }
@@ -65,7 +80,7 @@ class NumberFilter extends Component {
// if (comparator === '') { // if (comparator === '') {
// return; // return;
// } // }
onFilter(column, { number: value, comparator }, FILTER_TYPE.NUMBER); onFilter(column, FILTER_TYPE.NUMBER)({ number: value, comparator });
} }
onChangeComparator(e) { onChangeComparator(e) {
@@ -75,7 +90,7 @@ class NumberFilter extends Component {
// if (value === '') { // if (value === '') {
// return; // return;
// } // }
onFilter(column, { number: value, comparator }, FILTER_TYPE.NUMBER); onFilter(column, FILTER_TYPE.NUMBER)({ number: value, comparator });
} }
getComparatorOptions() { getComparatorOptions() {
@@ -116,7 +131,7 @@ class NumberFilter extends Component {
this.setState(() => ({ isSelected: (number !== '') })); this.setState(() => ({ isSelected: (number !== '') }));
this.numberFilterComparator.value = comparator; this.numberFilterComparator.value = comparator;
this.numberFilter.value = number; this.numberFilter.value = number;
onFilter(column, { number, comparator }, FILTER_TYPE.NUMBER); onFilter(column, FILTER_TYPE.NUMBER)({ number, comparator });
} }
cleanFiltered() { cleanFiltered() {
@@ -126,7 +141,7 @@ class NumberFilter extends Component {
this.setState(() => ({ isSelected: (value !== '') })); this.setState(() => ({ isSelected: (value !== '') }));
this.numberFilterComparator.value = comparator; this.numberFilterComparator.value = comparator;
this.numberFilter.value = value; this.numberFilter.value = value;
onFilter(column, { number: value, comparator }, FILTER_TYPE.NUMBER); onFilter(column, FILTER_TYPE.NUMBER)({ number: value, comparator });
} }
render() { render() {
@@ -224,7 +239,8 @@ NumberFilter.propTypes = {
comparatorStyle: PropTypes.object, comparatorStyle: PropTypes.object,
comparatorClassName: PropTypes.string, comparatorClassName: PropTypes.string,
numberStyle: PropTypes.object, numberStyle: PropTypes.object,
numberClassName: PropTypes.string numberClassName: PropTypes.string,
getFilter: PropTypes.func
}; };
NumberFilter.defaultProps = { NumberFilter.defaultProps = {

View File

@@ -25,9 +25,21 @@ class SelectFilter extends Component {
} }
componentDidMount() { componentDidMount() {
const { column, onFilter, getFilter } = this.props;
const value = this.selectInput.value; const value = this.selectInput.value;
if (value && value !== '') { if (value && value !== '') {
this.props.onFilter(this.props.column, value, FILTER_TYPE.SELECT); onFilter(column, FILTER_TYPE.SELECT)(value);
}
// export onFilter function to allow users to access
if (getFilter) {
getFilter((filterVal) => {
this.setState(() => ({ isSelected: filterVal !== '' }));
this.selectInput.value = filterVal;
onFilter(column, FILTER_TYPE.SELECT)(filterVal);
});
} }
} }
@@ -41,7 +53,7 @@ class SelectFilter extends Component {
if (needFilter) { if (needFilter) {
const value = this.selectInput.value; const value = this.selectInput.value;
if (value) { if (value) {
this.props.onFilter(this.props.column, value, FILTER_TYPE.SELECT); this.props.onFilter(this.props.column, FILTER_TYPE.SELECT)(value);
} }
} }
} }
@@ -64,19 +76,19 @@ class SelectFilter extends Component {
const value = (this.props.defaultValue !== undefined) ? this.props.defaultValue : ''; const value = (this.props.defaultValue !== undefined) ? this.props.defaultValue : '';
this.setState(() => ({ isSelected: value !== '' })); this.setState(() => ({ isSelected: value !== '' }));
this.selectInput.value = value; this.selectInput.value = value;
this.props.onFilter(this.props.column, value, FILTER_TYPE.SELECT); this.props.onFilter(this.props.column, FILTER_TYPE.SELECT)(value);
} }
applyFilter(value) { applyFilter(value) {
this.selectInput.value = value; this.selectInput.value = value;
this.setState(() => ({ isSelected: value !== '' })); this.setState(() => ({ isSelected: value !== '' }));
this.props.onFilter(this.props.column, value, FILTER_TYPE.SELECT); this.props.onFilter(this.props.column, FILTER_TYPE.SELECT)(value);
} }
filter(e) { filter(e) {
const { value } = e.target; const { value } = e.target;
this.setState(() => ({ isSelected: value !== '' })); this.setState(() => ({ isSelected: value !== '' }));
this.props.onFilter(this.props.column, value, FILTER_TYPE.SELECT); this.props.onFilter(this.props.column, FILTER_TYPE.SELECT)(value);
} }
render() { render() {
@@ -90,6 +102,7 @@ class SelectFilter extends Component {
comparator, comparator,
withoutEmptyOption, withoutEmptyOption,
caseSensitive, caseSensitive,
getFilter,
...rest ...rest
} = this.props; } = this.props;
@@ -121,7 +134,8 @@ SelectFilter.propTypes = {
className: PropTypes.string, className: PropTypes.string,
withoutEmptyOption: PropTypes.bool, withoutEmptyOption: PropTypes.bool,
defaultValue: PropTypes.any, defaultValue: PropTypes.any,
caseSensitive: PropTypes.bool caseSensitive: PropTypes.bool,
getFilter: PropTypes.func
}; };
SelectFilter.defaultProps = { SelectFilter.defaultProps = {

View File

@@ -17,10 +17,21 @@ class TextFilter extends Component {
value: props.defaultValue value: props.defaultValue
}; };
} }
componentDidMount() { componentDidMount() {
const { onFilter, getFilter, column } = this.props;
const defaultValue = this.input.value; const defaultValue = this.input.value;
if (defaultValue) { if (defaultValue) {
this.props.onFilter(this.props.column, defaultValue, FILTER_TYPE.TEXT); onFilter(this.props.column, FILTER_TYPE.TEXT)(defaultValue);
}
// export onFilter function to allow users to access
if (getFilter) {
getFilter((filterVal) => {
this.setState(() => ({ value: filterVal }));
onFilter(column, FILTER_TYPE.TEXT)(filterVal);
});
} }
} }
@@ -40,7 +51,7 @@ class TextFilter extends Component {
const filterValue = e.target.value; const filterValue = e.target.value;
this.setState(() => ({ value: filterValue })); this.setState(() => ({ value: filterValue }));
this.timeout = setTimeout(() => { this.timeout = setTimeout(() => {
this.props.onFilter(this.props.column, filterValue, FILTER_TYPE.TEXT); this.props.onFilter(this.props.column, FILTER_TYPE.TEXT)(filterValue);
}, this.props.delay); }, this.props.delay);
} }
@@ -53,12 +64,12 @@ class TextFilter extends Component {
cleanFiltered() { cleanFiltered() {
const value = this.props.defaultValue; const value = this.props.defaultValue;
this.setState(() => ({ value })); this.setState(() => ({ value }));
this.props.onFilter(this.props.column, value, FILTER_TYPE.TEXT); this.props.onFilter(this.props.column, FILTER_TYPE.TEXT)(value);
} }
applyFilter(filterText) { applyFilter(filterText) {
this.setState(() => ({ value: filterText })); this.setState(() => ({ value: filterText }));
this.props.onFilter(this.props.column, filterText, FILTER_TYPE.TEXT); this.props.onFilter(this.props.column, FILTER_TYPE.TEXT)(filterText);
} }
handleClick(e) { handleClick(e) {
@@ -77,8 +88,10 @@ class TextFilter extends Component {
onFilter, onFilter,
caseSensitive, caseSensitive,
defaultValue, defaultValue,
getFilter,
...rest ...rest
} = this.props; } = this.props;
// stopPropagation for onClick event is try to prevent sort was triggered. // stopPropagation for onClick event is try to prevent sort was triggered.
return ( return (
<input <input
@@ -105,7 +118,8 @@ TextFilter.propTypes = {
placeholder: PropTypes.string, placeholder: PropTypes.string,
style: PropTypes.object, style: PropTypes.object,
className: PropTypes.string, className: PropTypes.string,
caseSensitive: PropTypes.bool caseSensitive: PropTypes.bool,
getFilter: PropTypes.func
}; };
TextFilter.defaultProps = { TextFilter.defaultProps = {

View File

@@ -1,7 +1,8 @@
export const FILTER_TYPE = { export const FILTER_TYPE = {
TEXT: 'TEXT', TEXT: 'TEXT',
SELECT: 'SELECT', SELECT: 'SELECT',
NUMBER: 'NUMBER' NUMBER: 'NUMBER',
DATE: 'DATE'
}; };
export const FILTER_DELAY = 500; export const FILTER_DELAY = 500;

View File

@@ -6,30 +6,37 @@ import { LIKE, EQ, NE, GT, GE, LT, LE } from './comparison';
export const filterByText = _ => ( export const filterByText = _ => (
data, data,
dataField, dataField,
{ filterVal = '', comparator = LIKE, caseSensitive }, { filterVal: userInput = '', comparator = LIKE, caseSensitive },
customFilterValue customFilterValue
) => ) => {
data.filter((row) => { // make sure filter value to be a string
let cell = _.get(row, dataField); const filterVal = userInput.toString();
if (customFilterValue) {
cell = customFilterValue(cell, row); return (
} data.filter((row) => {
const cellStr = _.isDefined(cell) ? cell.toString() : ''; let cell = _.get(row, dataField);
if (comparator === EQ) { if (customFilterValue) {
return cellStr === filterVal; cell = customFilterValue(cell, row);
} }
if (caseSensitive) { const cellStr = _.isDefined(cell) ? cell.toString() : '';
return cellStr.includes(filterVal); if (comparator === EQ) {
} return cellStr === filterVal;
return cellStr.toLocaleUpperCase().includes(filterVal.toLocaleUpperCase()); }
}); if (caseSensitive) {
return cellStr.includes(filterVal);
}
return cellStr.toLocaleUpperCase().indexOf(filterVal.toLocaleUpperCase()) !== -1;
})
);
};
export const filterByNumber = _ => ( export const filterByNumber = _ => (
data, data,
dataField, dataField,
{ filterVal: { comparator, number } }, { filterVal: { comparator, number } },
customFilterValue customFilterValue
) => ) => (
data.filter((row) => { data.filter((row) => {
if (number === '' || !comparator) return true; if (number === '' || !comparator) return true;
let valid = true; let valid = true;
@@ -81,7 +88,104 @@ export const filterByNumber = _ => (
} }
} }
return valid; return valid;
})
);
export const filterByDate = _ => (
data,
dataField,
{ filterVal: { comparator, date } },
customFilterValue
) => {
if (!date || !comparator) return data;
const filterDate = date.getDate();
const filterMonth = date.getMonth();
const filterYear = date.getFullYear();
return data.filter((row) => {
let valid = true;
let cell = _.get(row, dataField);
if (customFilterValue) {
cell = customFilterValue(cell, row);
}
if (typeof cell !== 'object') {
cell = new Date(cell);
}
const targetDate = cell.getDate();
const targetMonth = cell.getMonth();
const targetYear = cell.getFullYear();
switch (comparator) {
case EQ: {
if (
filterDate !== targetDate ||
filterMonth !== targetMonth ||
filterYear !== targetYear
) {
valid = false;
}
break;
}
case GT: {
if (cell <= date) {
valid = false;
}
break;
}
case GE: {
if (targetYear < filterYear) {
valid = false;
} else if (targetYear === filterYear &&
targetMonth < filterMonth) {
valid = false;
} else if (targetYear === filterYear &&
targetMonth === filterMonth &&
targetDate < filterDate) {
valid = false;
}
break;
}
case LT: {
if (cell >= date) {
valid = false;
}
break;
}
case LE: {
if (targetYear > filterYear) {
valid = false;
} else if (targetYear === filterYear &&
targetMonth > filterMonth) {
valid = false;
} else if (targetYear === filterYear &&
targetMonth === filterMonth &&
targetDate > filterDate) {
valid = false;
}
break;
}
case NE: {
if (
filterDate === targetDate &&
filterMonth === targetMonth &&
filterYear === targetYear
) {
valid = false;
}
break;
}
default: {
console.error('Date comparator provided is not supported');
break;
}
}
return valid;
}); });
};
export const filterFactory = _ => (filterType) => { export const filterFactory = _ => (filterType) => {
let filterFn; let filterFn;
@@ -93,6 +197,9 @@ export const filterFactory = _ => (filterType) => {
case FILTER_TYPE.NUMBER: case FILTER_TYPE.NUMBER:
filterFn = filterByNumber(_); filterFn = filterByNumber(_);
break; break;
case FILTER_TYPE.DATE:
filterFn = filterByDate(_);
break;
default: default:
filterFn = filterByText(_); filterFn = filterByText(_);
} }
@@ -106,7 +213,13 @@ export const filters = (store, columns, _) => (currFilters) => {
Object.keys(currFilters).forEach((dataField) => { Object.keys(currFilters).forEach((dataField) => {
const filterObj = currFilters[dataField]; const filterObj = currFilters[dataField];
filterFn = factory(filterObj.filterType); filterFn = factory(filterObj.filterType);
const { filterValue } = columns.find(col => col.dataField === dataField); let filterValue;
for (let i = 0; i < columns.length; i += 1) {
if (columns[i].dataField === dataField) {
filterValue = columns[i].filterValue;
break;
}
}
result = filterFn(result, dataField, filterObj, filterValue); result = filterFn(result, dataField, filterObj, filterValue);
}); });
return result; return result;

View File

@@ -30,43 +30,52 @@ export default (Base, {
// I think this condition only isRemoteFilter is enough // I think this condition only isRemoteFilter is enough
store.filteredData = store.getAllData(); store.filteredData = store.getAllData();
this.setState(() => ({ isDataChanged: true, currFilters: store.filters })); this.setState(() => ({ isDataChanged: true, currFilters: store.filters }));
} else if (isDataChanged) { } else {
if (!isRemoteFilter && Object.keys(this.state.currFilters).length > 0) { if (Object.keys(this.state.currFilters).length > 0) {
store.filteredData = filters(store, columns, _)(this.state.currFilters); store.filteredData = filters(store, columns, _)(this.state.currFilters);
} }
this.setState(() => ({ isDataChanged })); this.setState(() => ({ isDataChanged }));
} else {
this.setState(() => ({ isDataChanged: false }));
} }
} }
onFilter(column, filterVal, filterType) { /**
const { store, columns } = this.props; * filter the table like below:
const currFilters = Object.assign({}, this.state.currFilters); * onFilter(column, filterType)(filterVal)
const { dataField, filter } = column; * @param {Object} column
* @param {String} filterType
* @param {String} filterVal - user input for filtering.
*/
onFilter(column, filterType) {
return (filterVal) => {
const { store, columns } = this.props;
// watch out here if migration to context API, #334
const currFilters = Object.assign({}, store.filters);
const { dataField, filter } = column;
if (!_.isDefined(filterVal) || filterVal === '') { if (!_.isDefined(filterVal) || filterVal === '') {
delete currFilters[dataField]; delete currFilters[dataField];
} else { } else {
// select default comparator is EQ, others are LIKE // select default comparator is EQ, others are LIKE
const { const {
comparator = (filterType === FILTER_TYPE.SELECT ? EQ : LIKE), comparator = (filterType === FILTER_TYPE.SELECT ? EQ : LIKE),
caseSensitive = false caseSensitive = false
} = filter.props; } = filter.props;
currFilters[dataField] = { filterVal, filterType, comparator, caseSensitive }; currFilters[dataField] = { filterVal, filterType, comparator, caseSensitive };
} }
store.filters = currFilters;
if (this.isRemoteFiltering() || this.isRemotePagination()) { store.filters = currFilters;
this.handleRemoteFilterChange();
// when remote filtering is enable, dont set currFilters state
// in the componentWillReceiveProps,
// it's the key point that we can know the filter is changed
return;
}
store.filteredData = filters(store, columns, _)(currFilters); if (this.isRemoteFiltering() || this.isRemotePagination()) {
this.setState(() => ({ currFilters, isDataChanged: true })); this.handleRemoteFilterChange();
// when remote filtering is enable, dont set currFilters state
// in the componentWillReceiveProps,
// it's the key point that we can know the filter is changed
return;
}
store.filteredData = filters(store, columns, _)(currFilters);
this.setState(() => ({ currFilters, isDataChanged: true }));
};
} }
render() { render() {

View File

@@ -5,7 +5,8 @@
.react-bootstrap-table > table > thead > tr > th .select-filter option[value=''], .react-bootstrap-table > table > thead > tr > th .select-filter option[value=''],
.react-bootstrap-table > table > thead > tr > th .select-filter.placeholder-selected, .react-bootstrap-table > table > thead > tr > th .select-filter.placeholder-selected,
.react-bootstrap-table > table > thead > tr > th .filter::-webkit-input-placeholder, .react-bootstrap-table > table > thead > tr > th .filter::-webkit-input-placeholder,
.react-bootstrap-table > table > thead > tr > th .number-filter-input::-webkit-input-placeholder { .react-bootstrap-table > table > thead > tr > th .number-filter-input::-webkit-input-placeholder,
.react-bootstrap-table > table > thead > tr > th .date-filter-input::-webkit-input-placeholder {
color: lightgrey; color: lightgrey;
font-style: italic; font-style: italic;
} }
@@ -15,17 +16,20 @@
font-style: initial; font-style: initial;
} }
.react-bootstrap-table > table > thead > tr > th .number-filter { .react-bootstrap-table > table > thead > tr > th .number-filter,
.react-bootstrap-table > table > thead > tr > th .date-filter {
display: flex; display: flex;
} }
.react-bootstrap-table > table > thead > tr > th .number-filter-input { .react-bootstrap-table > table > thead > tr > th .number-filter-input,
.react-bootstrap-table > table > thead > tr > th .date-filter-input {
margin-left: 5px; margin-left: 5px;
float: left; float: left;
width: calc(100% - 67px - 5px); width: calc(100% - 67px - 5px);
} }
.react-bootstrap-table > table > thead > tr > th .number-filter-comparator { .react-bootstrap-table > table > thead > tr > th .number-filter-comparator,
.react-bootstrap-table > table > thead > tr > th .date-filter-comparator {
width: 67px; width: 67px;
float: left; float: left;
} }

View File

@@ -0,0 +1,264 @@
import 'jsdom-global/register';
import React from 'react';
import { mount } from 'enzyme';
import DateFilter from '../../src/components/date';
import { FILTER_TYPE } from '../../src/const';
import * as Comparator from '../../src/comparison';
describe('Date Filter', () => {
let wrapper;
const onFilterFirstReturn = jest.fn();
const onFilter = jest.fn().mockReturnValue(onFilterFirstReturn);
const column = {
dataField: 'price',
text: 'Product Price'
};
afterEach(() => {
onFilter.mockClear();
onFilterFirstReturn.mockClear();
// onFilter.returns(onFilterFirstReturn);
});
describe('initialization', () => {
beforeEach(() => {
wrapper = mount(
<DateFilter onFilter={ onFilter } column={ column } />
);
});
it('should rendering component successfully', () => {
expect(wrapper).toHaveLength(1);
expect(wrapper.find('.date-filter-input')).toHaveLength(1);
expect(wrapper.find('.date-filter-comparator')).toHaveLength(1);
expect(wrapper.find('.date-filter')).toHaveLength(1);
});
it('should rendering comparator options correctly', () => {
const select = wrapper.find('select');
expect(select.find('option')).toHaveLength(wrapper.prop('comparators').length + 1);
});
});
describe('when withoutEmptyComparatorOption prop is true', () => {
beforeEach(() => {
wrapper = mount(
<DateFilter
onFilter={ onFilter }
column={ column }
withoutEmptyComparatorOption
/>
);
});
it('should rendering comparator options correctly', () => {
const select = wrapper.find('.date-filter-comparator');
expect(select.find('option')).toHaveLength(wrapper.prop('comparators').length);
});
});
describe('when defaultValue.date props is defined', () => {
const date = new Date(2018, 0, 1);
beforeEach(() => {
wrapper = mount(
<DateFilter
onFilter={ onFilter }
column={ column }
defaultValue={ { date } }
/>
);
});
it('should rendering input successfully', () => {
expect(wrapper).toHaveLength(1);
const input = wrapper.find('.date-filter-input');
expect(input).toHaveLength(1);
expect(input.props().defaultValue).toEqual(wrapper.instance().getDefaultDate());
});
});
describe('when defaultValue.comparator props is defined', () => {
const comparator = Comparator.EQ;
beforeEach(() => {
wrapper = mount(
<DateFilter
onFilter={ onFilter }
column={ column }
defaultValue={ { comparator } }
/>
);
});
it('should rendering comparator select successfully', () => {
expect(wrapper).toHaveLength(1);
const select = wrapper.find('.date-filter-comparator');
expect(select).toHaveLength(1);
expect(select.props().defaultValue).toEqual(comparator);
});
});
describe('when props.getFilter is defined', () => {
let programmaticallyFilter;
const comparator = Comparator.EQ;
const date = new Date(2018, 0, 1);
const getFilter = (filter) => {
programmaticallyFilter = filter;
};
beforeEach(() => {
wrapper = mount(
<DateFilter onFilter={ onFilter } column={ column } getFilter={ getFilter } />
);
programmaticallyFilter({ comparator, date });
});
it('should do onFilter correctly when exported function was executed', () => {
expect(onFilter).toHaveBeenCalledTimes(1);
expect(onFilter).toHaveBeenCalledWith(column, FILTER_TYPE.DATE);
expect(onFilterFirstReturn).toHaveBeenCalledTimes(1);
expect(onFilterFirstReturn).toHaveBeenCalledWith({ comparator, date });
});
});
describe('when defaultValue.number and defaultValue.comparator props are defined', () => {
let date;
let comparator;
beforeEach(() => {
date = new Date();
comparator = Comparator.EQ;
wrapper = mount(
<DateFilter
onFilter={ onFilter }
column={ column }
defaultValue={ { date, comparator } }
/>
);
});
it('should calling onFilter on componentDidMount', () => {
expect(onFilter).toHaveBeenCalledTimes(1);
expect(onFilter).toHaveBeenCalledWith(column, FILTER_TYPE.DATE);
expect(onFilterFirstReturn).toHaveBeenCalledTimes(1);
// expect(onFilterFirstReturn).toHaveBeenCalledWith({ comparator, date });
});
});
describe('when style props is defined', () => {
const style = { backgroundColor: 'red' };
beforeEach(() => {
wrapper = mount(
<DateFilter
onFilter={ onFilter }
column={ column }
style={ style }
/>
);
});
it('should rendering component successfully', () => {
expect(wrapper).toHaveLength(1);
expect(wrapper.find('.date-filter').prop('style')).toEqual(style);
});
});
describe('when dateStyle props is defined', () => {
const dateStyle = { backgroundColor: 'red' };
beforeEach(() => {
wrapper = mount(
<DateFilter
onFilter={ onFilter }
column={ column }
dateStyle={ dateStyle }
/>
);
});
it('should rendering component successfully', () => {
expect(wrapper).toHaveLength(1);
expect(wrapper.find('.date-filter-input').prop('style')).toEqual(dateStyle);
});
});
describe('when comparatorStyle props is defined', () => {
const comparatorStyle = { backgroundColor: 'red' };
beforeEach(() => {
wrapper = mount(
<DateFilter
onFilter={ onFilter }
column={ column }
comparatorStyle={ comparatorStyle }
/>
);
});
it('should rendering component successfully', () => {
expect(wrapper).toHaveLength(1);
expect(wrapper.find('.date-filter-comparator').prop('style')).toEqual(comparatorStyle);
});
});
describe('when className props is defined', () => {
const className = 'test';
beforeEach(() => {
wrapper = mount(
<DateFilter
onFilter={ onFilter }
column={ column }
className={ className }
/>
);
});
it('should rendering component successfully', () => {
expect(wrapper).toHaveLength(1);
expect(wrapper.hasClass(className)).toBeTruthy();
});
});
describe('when dateClassName props is defined', () => {
const className = 'test';
beforeEach(() => {
wrapper = mount(
<DateFilter
onFilter={ onFilter }
column={ column }
dateClassName={ className }
/>
);
});
it('should rendering component successfully', () => {
expect(wrapper).toHaveLength(1);
expect(wrapper.find('.date-filter-input').prop('className').indexOf(className) > -1).toBeTruthy();
});
});
describe('when comparatorClassName props is defined', () => {
const className = 'test';
beforeEach(() => {
wrapper = mount(
<DateFilter
onFilter={ onFilter }
column={ column }
comparatorClassName={ className }
/>
);
});
it('should rendering component successfully', () => {
expect(wrapper).toHaveLength(1);
expect(wrapper.find('.date-filter-comparator').prop('className').indexOf(className) > -1).toBeTruthy();
});
});
});

View File

@@ -9,7 +9,11 @@ import * as Comparator from '../../src/comparison';
describe('Number Filter', () => { describe('Number Filter', () => {
let wrapper; let wrapper;
// onFilter(x)(y) = filter result
const onFilter = sinon.stub(); const onFilter = sinon.stub();
const onFilterFirstReturn = sinon.stub();
const column = { const column = {
dataField: 'price', dataField: 'price',
text: 'Product Price' text: 'Product Price'
@@ -17,6 +21,9 @@ describe('Number Filter', () => {
afterEach(() => { afterEach(() => {
onFilter.reset(); onFilter.reset();
onFilterFirstReturn.reset();
onFilter.returns(onFilterFirstReturn);
}); });
describe('initialization', () => { describe('initialization', () => {
@@ -90,6 +97,36 @@ describe('Number Filter', () => {
}); });
}); });
describe('when props.getFilter is defined', () => {
let programmaticallyFilter;
const comparator = Comparator.EQ;
const number = 123;
const getFilter = (filter) => {
programmaticallyFilter = filter;
};
beforeEach(() => {
wrapper = mount(
<NumberFilter onFilter={ onFilter } column={ column } getFilter={ getFilter } />
);
programmaticallyFilter({ comparator, number });
});
it('should do onFilter correctly when exported function was executed', () => {
expect(onFilter.calledOnce).toBeTruthy();
expect(onFilter.calledWith(column, FILTER_TYPE.NUMBER)).toBeTruthy();
expect(onFilterFirstReturn.calledOnce).toBeTruthy();
expect(onFilterFirstReturn.calledWith({ comparator, number })).toBeTruthy();
});
it('should setState correctly when exported function was executed', () => {
expect(wrapper.state().isSelected).toBeTruthy();
});
});
describe('when defaultValue.number and defaultValue.comparator props is defined', () => { describe('when defaultValue.number and defaultValue.comparator props is defined', () => {
const number = 203; const number = 203;
const comparator = Comparator.EQ; const comparator = Comparator.EQ;
@@ -110,8 +147,9 @@ describe('Number Filter', () => {
it('should calling onFilter on componentDidMount', () => { it('should calling onFilter on componentDidMount', () => {
expect(onFilter.calledOnce).toBeTruthy(); expect(onFilter.calledOnce).toBeTruthy();
expect(onFilter.calledWith( expect(onFilter.calledWith(column, FILTER_TYPE.NUMBER)).toBeTruthy();
column, { number: `${number}`, comparator }, FILTER_TYPE.NUMBER)).toBeTruthy(); expect(onFilterFirstReturn.calledOnce).toBeTruthy();
expect(onFilterFirstReturn.calledWith({ number: `${number}`, comparator })).toBeTruthy();
}); });
}); });

View File

@@ -9,19 +9,27 @@ import { FILTER_TYPE } from '../../src/const';
describe('Select Filter', () => { describe('Select Filter', () => {
let wrapper; let wrapper;
let instance; let instance;
// onFilter(x)(y) = filter result
const onFilter = sinon.stub(); const onFilter = sinon.stub();
const onFilterFirstReturn = sinon.stub();
const column = { const column = {
dataField: 'quality', dataField: 'quality',
text: 'Product Quality' text: 'Product Quality'
}; };
const options = { const options = {
0: 'Bad', 0: 'Bad',
1: 'Good', 1: 'Good',
2: 'Unknow' 2: 'Unknown'
}; };
afterEach(() => { afterEach(() => {
onFilter.reset(); onFilter.reset();
onFilterFirstReturn.reset();
onFilter.returns(onFilterFirstReturn);
}); });
describe('initialization', () => { describe('initialization', () => {
@@ -83,11 +91,48 @@ describe('Select Filter', () => {
it('should calling onFilter on componentDidMount', () => { it('should calling onFilter on componentDidMount', () => {
expect(onFilter.calledOnce).toBeTruthy(); expect(onFilter.calledOnce).toBeTruthy();
expect(onFilter.calledWith(column, defaultValue, FILTER_TYPE.SELECT)).toBeTruthy(); expect(onFilter.calledWith(column, FILTER_TYPE.SELECT)).toBeTruthy();
expect(onFilterFirstReturn.calledOnce).toBeTruthy();
expect(onFilterFirstReturn.calledWith(defaultValue)).toBeTruthy();
}); });
}); });
}); });
describe('when props.getFilter is defined', () => {
let programmaticallyFilter;
const filterValue = 'foo';
const getFilter = (filter) => {
programmaticallyFilter = filter;
};
beforeEach(() => {
wrapper = mount(
<SelectFilter
onFilter={ onFilter }
column={ column }
options={ options }
getFilter={ getFilter }
/>
);
instance = wrapper.instance();
programmaticallyFilter(filterValue);
});
it('should do onFilter correctly when exported function was executed', () => {
expect(onFilter.calledOnce).toBeTruthy();
expect(onFilter.calledWith(column, FILTER_TYPE.SELECT)).toBeTruthy();
expect(onFilterFirstReturn.calledOnce).toBeTruthy();
expect(onFilterFirstReturn.calledWith(filterValue)).toBeTruthy();
});
it('should setState correctly when exported function was executed', () => {
expect(instance.state.isSelected).toBeTruthy();
});
});
describe('when placeholder is defined', () => { describe('when placeholder is defined', () => {
const placeholder = 'test'; const placeholder = 'test';
beforeEach(() => { beforeEach(() => {
@@ -170,8 +215,9 @@ describe('Select Filter', () => {
it('should update', () => { it('should update', () => {
expect(onFilter.callCount).toBe(2); expect(onFilter.callCount).toBe(2);
expect(onFilter.calledWith( expect(onFilter.calledWith(column, FILTER_TYPE.SELECT)).toBeTruthy();
column, instance.props.defaultValue, FILTER_TYPE.SELECT)).toBeTruthy(); expect(onFilterFirstReturn.callCount).toBe(2);
expect(onFilterFirstReturn.calledWith(instance.props.defaultValue)).toBeTruthy();
}); });
}); });
@@ -198,8 +244,9 @@ describe('Select Filter', () => {
it('should update', () => { it('should update', () => {
expect(onFilter.callCount).toBe(2); expect(onFilter.callCount).toBe(2);
expect(onFilter.calledWith( expect(onFilter.calledWith(column, FILTER_TYPE.SELECT)).toBeTruthy();
column, instance.props.defaultValue, FILTER_TYPE.SELECT)).toBeTruthy(); expect(onFilterFirstReturn.callCount).toBe(2);
expect(onFilterFirstReturn.calledWith(instance.props.defaultValue)).toBeTruthy();
}); });
}); });
}); });
@@ -226,7 +273,9 @@ describe('Select Filter', () => {
it('should calling onFilter correctly', () => { it('should calling onFilter correctly', () => {
expect(onFilter.callCount).toBe(2); expect(onFilter.callCount).toBe(2);
expect(onFilter.calledWith(column, defaultValue, FILTER_TYPE.SELECT)).toBeTruthy(); expect(onFilter.calledWith(column, FILTER_TYPE.SELECT)).toBeTruthy();
expect(onFilterFirstReturn.callCount).toBe(2);
expect(onFilterFirstReturn.calledWith(defaultValue)).toBeTruthy();
}); });
}); });
@@ -249,6 +298,7 @@ describe('Select Filter', () => {
it('should calling onFilter correctly', () => { it('should calling onFilter correctly', () => {
expect(onFilter.callCount).toBe(1); expect(onFilter.callCount).toBe(1);
expect(onFilterFirstReturn.callCount).toBe(1);
}); });
}); });
}); });
@@ -268,8 +318,10 @@ describe('Select Filter', () => {
}); });
it('should calling onFilter correctly', () => { it('should calling onFilter correctly', () => {
expect(onFilter.callCount).toBe(1); expect(onFilter.calledOnce).toBeTruthy();
expect(onFilter.calledWith(column, value, FILTER_TYPE.SELECT)).toBeTruthy(); expect(onFilter.calledWith(column, FILTER_TYPE.SELECT)).toBeTruthy();
expect(onFilterFirstReturn.calledOnce).toBeTruthy();
expect(onFilterFirstReturn.calledWith(value)).toBeTruthy();
}); });
}); });
@@ -289,8 +341,10 @@ describe('Select Filter', () => {
}); });
it('should calling onFilter correctly', () => { it('should calling onFilter correctly', () => {
expect(onFilter.callCount).toBe(1); expect(onFilter.calledOnce).toBeTruthy();
expect(onFilter.calledWith(column, event.target.value, FILTER_TYPE.SELECT)).toBeTruthy(); expect(onFilter.calledWith(column, FILTER_TYPE.SELECT)).toBeTruthy();
expect(onFilterFirstReturn.calledOnce).toBeTruthy();
expect(onFilterFirstReturn.calledWith(event.target.value)).toBeTruthy();
}); });
}); });
}); });

View File

@@ -9,7 +9,11 @@ jest.useFakeTimers();
describe('Text Filter', () => { describe('Text Filter', () => {
let wrapper; let wrapper;
let instance; let instance;
// onFilter(x)(y) = filter result
const onFilter = sinon.stub(); const onFilter = sinon.stub();
const onFilterFirstReturn = sinon.stub();
const column = { const column = {
dataField: 'price', dataField: 'price',
text: 'Price' text: 'Price'
@@ -17,6 +21,9 @@ describe('Text Filter', () => {
afterEach(() => { afterEach(() => {
onFilter.reset(); onFilter.reset();
onFilterFirstReturn.reset();
onFilter.returns(onFilterFirstReturn);
}); });
describe('initialization', () => { describe('initialization', () => {
@@ -58,7 +65,39 @@ describe('Text Filter', () => {
it('should calling onFilter on componentDidMount', () => { it('should calling onFilter on componentDidMount', () => {
expect(onFilter.calledOnce).toBeTruthy(); expect(onFilter.calledOnce).toBeTruthy();
expect(onFilter.calledWith(column, defaultValue, FILTER_TYPE.TEXT)).toBeTruthy(); expect(onFilter.calledWith(column, FILTER_TYPE.TEXT)).toBeTruthy();
expect(onFilterFirstReturn.calledOnce).toBeTruthy();
expect(onFilterFirstReturn.calledWith(defaultValue)).toBeTruthy();
});
});
describe('when props.getFilter is defined', () => {
let programmaticallyFilter;
const filterValue = 'foo';
const getFilter = (filter) => {
programmaticallyFilter = filter;
};
beforeEach(() => {
wrapper = mount(
<TextFilter onFilter={ onFilter } column={ column } getFilter={ getFilter } />
);
instance = wrapper.instance();
programmaticallyFilter(filterValue);
});
it('should do onFilter correctly when exported function was executed', () => {
expect(onFilter.calledOnce).toBeTruthy();
expect(onFilter.calledWith(column, FILTER_TYPE.TEXT)).toBeTruthy();
expect(onFilterFirstReturn.calledOnce).toBeTruthy();
expect(onFilterFirstReturn.calledWith(filterValue)).toBeTruthy();
});
it('should setState correctly when exported function was executed', () => {
expect(instance.state.value).toEqual(filterValue);
}); });
}); });
@@ -114,7 +153,9 @@ describe('Text Filter', () => {
it('should calling onFilter correctly when props.defaultValue is changed', () => { it('should calling onFilter correctly when props.defaultValue is changed', () => {
expect(onFilter.calledOnce).toBeTruthy(); expect(onFilter.calledOnce).toBeTruthy();
expect(onFilter.calledWith(column, nextDefaultValue, FILTER_TYPE.TEXT)).toBeTruthy(); expect(onFilter.calledWith(column, FILTER_TYPE.TEXT)).toBeTruthy();
expect(onFilterFirstReturn.calledOnce).toBeTruthy();
expect(onFilterFirstReturn.calledWith(nextDefaultValue)).toBeTruthy();
}); });
}); });
@@ -133,8 +174,9 @@ describe('Text Filter', () => {
it('should calling onFilter correctly', () => { it('should calling onFilter correctly', () => {
expect(onFilter.calledOnce).toBeTruthy(); expect(onFilter.calledOnce).toBeTruthy();
expect(onFilter.calledWith( expect(onFilter.calledWith(column, FILTER_TYPE.TEXT)).toBeTruthy();
column, instance.props.defaultValue, FILTER_TYPE.TEXT)).toBeTruthy(); expect(onFilterFirstReturn.calledOnce).toBeTruthy();
expect(onFilterFirstReturn.calledWith(instance.props.defaultValue)).toBeTruthy();
}); });
}); });
@@ -154,7 +196,9 @@ describe('Text Filter', () => {
it('should calling onFilter correctly', () => { it('should calling onFilter correctly', () => {
expect(onFilter.calledOnce).toBeTruthy(); expect(onFilter.calledOnce).toBeTruthy();
expect(onFilter.calledWith(column, filterText, FILTER_TYPE.TEXT)).toBeTruthy(); expect(onFilter.calledWith(column, FILTER_TYPE.TEXT)).toBeTruthy();
expect(onFilterFirstReturn.calledOnce).toBeTruthy();
expect(onFilterFirstReturn.calledWith(filterText)).toBeTruthy();
}); });
}); });

View File

@@ -1,4 +1,3 @@
import sinon from 'sinon';
import _ from 'react-bootstrap-table-next/src/utils'; import _ from 'react-bootstrap-table-next/src/utils';
import Store from 'react-bootstrap-table-next/src/store'; import Store from 'react-bootstrap-table-next/src/store';
@@ -11,7 +10,8 @@ for (let i = 0; i < 20; i += 1) {
data.push({ data.push({
id: i, id: i,
name: `itme name ${i}`, name: `itme name ${i}`,
price: 200 + i price: 200 + i,
date: new Date(2017, i, 1)
}); });
} }
@@ -34,6 +34,9 @@ describe('filter', () => {
}, { }, {
dataField: 'price', dataField: 'price',
text: 'Price' text: 'Price'
}, {
dataField: 'date',
text: 'Date'
}]; }];
}); });
@@ -42,6 +45,19 @@ describe('filter', () => {
filterFn = filters(store, columns, _); filterFn = filters(store, columns, _);
}); });
describe('when filter value is not a String', () => {
it('should transform to string and do the filter', () => {
currFilters.name = {
filterVal: 3,
filterType: FILTER_TYPE.TEXT
};
const result = filterFn(currFilters);
expect(result).toBeDefined();
expect(result).toHaveLength(2);
});
});
describe(`when default comparator is ${LIKE}`, () => { describe(`when default comparator is ${LIKE}`, () => {
it('should returning correct result', () => { it('should returning correct result', () => {
currFilters.name = { currFilters.name = {
@@ -85,7 +101,7 @@ describe('filter', () => {
describe('column.filterValue is defined', () => { describe('column.filterValue is defined', () => {
beforeEach(() => { beforeEach(() => {
columns[1].filterValue = sinon.stub(); columns[1].filterValue = jest.fn();
filterFn = filters(store, columns, _); filterFn = filters(store, columns, _);
}); });
@@ -97,11 +113,12 @@ describe('filter', () => {
const result = filterFn(currFilters); const result = filterFn(currFilters);
expect(result).toBeDefined(); expect(result).toBeDefined();
expect(columns[1].filterValue.callCount).toBe(data.length); expect(columns[1].filterValue).toHaveBeenCalledTimes(data.length);
const calls = columns[1].filterValue.getCalls(); // const calls = columns[1].filterValue.mock.calls;
calls.forEach((call, i) => { // calls.forEach((call, i) => {
expect(call.calledWith(data[i].name, data[i])).toBeTruthy(); // expect(call).toEqual([data[i].name, data[i]]);
}); // expect(call.calledWith(data[i].name, data[i])).toBeTruthy();
// });
}); });
}); });
}); });
@@ -215,4 +232,40 @@ describe('filter', () => {
}); });
}); });
}); });
describe('filterByDate', () => {
beforeEach(() => {
filterFn = filters(store, columns, _);
});
describe('when currFilters.filterVal.comparator is empty', () => {
it('should returning correct result', () => {
currFilters.price = {
filterVal: { comparator: '', date: new Date() },
filterType: FILTER_TYPE.DATE
};
let result = filterFn(currFilters);
expect(result).toHaveLength(data.length);
currFilters.price.filterVal.comparator = undefined;
result = filterFn(currFilters);
expect(result).toHaveLength(data.length);
});
});
describe('when currFilters.filterVal.date is empty', () => {
it('should returning correct result', () => {
currFilters.price = {
filterVal: { comparator: EQ, date: '' },
filterType: FILTER_TYPE.DATE
};
const result = filterFn(currFilters);
expect(result).toHaveLength(data.length);
});
});
// TODO....
});
}); });

View File

@@ -167,14 +167,14 @@ describe('Wrapper', () => {
it('should setting store object correctly', () => { it('should setting store object correctly', () => {
filterVals.forEach((filterVal) => { filterVals.forEach((filterVal) => {
instance.onFilter(props.columns[1], filterVal, FILTER_TYPE.TEXT); instance.onFilter(props.columns[1], FILTER_TYPE.TEXT)(filterVal);
expect(props.store.filtering).toBeFalsy(); expect(props.store.filtering).toBeFalsy();
}); });
}); });
it('should setting state correctly', () => { it('should setting state correctly', () => {
filterVals.forEach((filterVal) => { filterVals.forEach((filterVal) => {
instance.onFilter(props.columns[1], filterVal, FILTER_TYPE.TEXT); instance.onFilter(props.columns[1], FILTER_TYPE.TEXT)(filterVal);
expect(instance.state.isDataChanged).toBeTruthy(); expect(instance.state.isDataChanged).toBeTruthy();
expect(Object.keys(instance.state.currFilters)).toHaveLength(0); expect(Object.keys(instance.state.currFilters)).toHaveLength(0);
}); });
@@ -185,12 +185,12 @@ describe('Wrapper', () => {
const filterVal = '3'; const filterVal = '3';
it('should setting store object correctly', () => { it('should setting store object correctly', () => {
instance.onFilter(props.columns[1], filterVal, FILTER_TYPE.TEXT); instance.onFilter(props.columns[1], FILTER_TYPE.TEXT)(filterVal);
expect(props.store.filters).toEqual(instance.state.currFilters); expect(props.store.filters).toEqual(instance.state.currFilters);
}); });
it('should setting state correctly', () => { it('should setting state correctly', () => {
instance.onFilter(props.columns[1], filterVal, FILTER_TYPE.TEXT); instance.onFilter(props.columns[1], FILTER_TYPE.TEXT)(filterVal);
expect(instance.state.isDataChanged).toBeTruthy(); expect(instance.state.isDataChanged).toBeTruthy();
expect(Object.keys(instance.state.currFilters)).toHaveLength(1); expect(Object.keys(instance.state.currFilters)).toHaveLength(1);
}); });
@@ -203,7 +203,7 @@ describe('Wrapper', () => {
props = createTableProps(); props = createTableProps();
props.remote = { filter: true }; props.remote = { filter: true };
createFilterWrapper(props); createFilterWrapper(props);
instance.onFilter(props.columns[1], filterVal, FILTER_TYPE.TEXT); instance.onFilter(props.columns[1], FILTER_TYPE.TEXT)(filterVal);
}); });
it('should not setting store object correctly', () => { it('should not setting store object correctly', () => {
@@ -222,27 +222,27 @@ describe('Wrapper', () => {
describe('combination', () => { describe('combination', () => {
it('should setting store object correctly', () => { it('should setting store object correctly', () => {
instance.onFilter(props.columns[1], '3', FILTER_TYPE.TEXT); instance.onFilter(props.columns[1], FILTER_TYPE.TEXT)('3');
expect(props.store.filters).toEqual(instance.state.currFilters); expect(props.store.filters).toEqual(instance.state.currFilters);
expect(instance.state.isDataChanged).toBeTruthy(); expect(instance.state.isDataChanged).toBeTruthy();
expect(Object.keys(instance.state.currFilters)).toHaveLength(1); expect(Object.keys(instance.state.currFilters)).toHaveLength(1);
instance.onFilter(props.columns[1], '2', FILTER_TYPE.TEXT); instance.onFilter(props.columns[1], FILTER_TYPE.TEXT)('2');
expect(props.store.filters).toEqual(instance.state.currFilters); expect(props.store.filters).toEqual(instance.state.currFilters);
expect(instance.state.isDataChanged).toBeTruthy(); expect(instance.state.isDataChanged).toBeTruthy();
expect(Object.keys(instance.state.currFilters)).toHaveLength(1); expect(Object.keys(instance.state.currFilters)).toHaveLength(1);
instance.onFilter(props.columns[2], '2', FILTER_TYPE.TEXT); instance.onFilter(props.columns[2], FILTER_TYPE.TEXT)('2');
expect(props.store.filters).toEqual(instance.state.currFilters); expect(props.store.filters).toEqual(instance.state.currFilters);
expect(instance.state.isDataChanged).toBeTruthy(); expect(instance.state.isDataChanged).toBeTruthy();
expect(Object.keys(instance.state.currFilters)).toHaveLength(2); expect(Object.keys(instance.state.currFilters)).toHaveLength(2);
instance.onFilter(props.columns[2], '', FILTER_TYPE.TEXT); instance.onFilter(props.columns[2], FILTER_TYPE.TEXT)('');
expect(props.store.filters).toEqual(instance.state.currFilters); expect(props.store.filters).toEqual(instance.state.currFilters);
expect(instance.state.isDataChanged).toBeTruthy(); expect(instance.state.isDataChanged).toBeTruthy();
expect(Object.keys(instance.state.currFilters)).toHaveLength(1); expect(Object.keys(instance.state.currFilters)).toHaveLength(1);
instance.onFilter(props.columns[1], '', FILTER_TYPE.TEXT); instance.onFilter(props.columns[1], FILTER_TYPE.TEXT)('');
expect(props.store.filters).toEqual(instance.state.currFilters); expect(props.store.filters).toEqual(instance.state.currFilters);
expect(instance.state.isDataChanged).toBeTruthy(); expect(instance.state.isDataChanged).toBeTruthy();
expect(Object.keys(instance.state.currFilters)).toHaveLength(0); expect(Object.keys(instance.state.currFilters)).toHaveLength(0);

View File

@@ -1,16 +1,29 @@
/* eslint no-return-assign: 0 */ /* eslint no-return-assign: 0 */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import LoadingOverlay from 'react-loading-overlay'; import LoadingOverlay from 'react-loading-overlay';
export default options => (element, loading) => export default options => loading =>
class TableLoadingOverlayWrapper extends React.Component { class TableLoadingOverlayWrapper extends React.Component {
static propTypes = {
children: PropTypes.element.isRequired
}
componentDidMount() { componentDidMount() {
if (loading) { if (loading) {
const { wrapper } = this.overlay; const { wrapper } = this.overlay;
const masker = wrapper.firstChild; const masker = wrapper.firstChild;
const headerDOM = wrapper.parentElement.querySelector('thead'); const headerDOM = wrapper.parentElement.querySelector('thead');
const bodyDOM = wrapper.parentElement.querySelector('tbody'); const bodyDOM = wrapper.parentElement.querySelector('tbody');
masker.style.marginTop = window.getComputedStyle(headerDOM).height; const captionDOM = wrapper.parentElement.querySelector('caption');
let marginTop = window.getComputedStyle(headerDOM).height;
if (captionDOM) {
marginTop = parseFloat(marginTop.replace('px', ''));
marginTop += parseFloat(window.getComputedStyle(captionDOM).height.replace('px', ''));
marginTop = `${marginTop}px`;
}
masker.style.marginTop = marginTop;
masker.style.height = window.getComputedStyle(bodyDOM).height; masker.style.height = window.getComputedStyle(bodyDOM).height;
} }
} }
@@ -22,7 +35,7 @@ export default options => (element, loading) =>
{ ...options } { ...options }
active={ loading } active={ loading }
> >
{ element } { this.props.children }
</LoadingOverlay> </LoadingOverlay>
); );
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "react-bootstrap-table2-overlay", "name": "react-bootstrap-table2-overlay",
"version": "0.1.1", "version": "0.1.2",
"description": "it's a loading overlay addons for react-bootstrap-table2", "description": "it's a loading overlay addons for react-bootstrap-table2",
"main": "./lib/index.js", "main": "./lib/index.js",
"repository": { "repository": {

View File

@@ -1,12 +1,11 @@
import React from 'react'; import React from 'react';
import { shallow, render } from 'enzyme'; import { render, shallow } from 'enzyme';
import LoadingOverlay from 'react-loading-overlay'; import LoadingOverlay from 'react-loading-overlay';
import overlayFactory from '..'; import overlayFactory from '../index.js';
describe('overlayFactory', () => { describe('overlayFactory', () => {
let wrapper; let wrapper;
// let instance;
const createTable = () => ( const createTable = () => (
<table> <table>
@@ -27,8 +26,8 @@ describe('overlayFactory', () => {
describe('when loading is false', () => { describe('when loading is false', () => {
beforeEach(() => { beforeEach(() => {
const tableElm = createTable(); const tableElm = createTable();
const Overlay = overlayFactory()(tableElm, false); const Overlay = overlayFactory()(false);
wrapper = shallow(<Overlay />); wrapper = shallow(<Overlay>{ tableElm }</Overlay>);
}); });
it('should rendering Overlay component correctly', () => { it('should rendering Overlay component correctly', () => {
@@ -42,14 +41,12 @@ describe('overlayFactory', () => {
describe('when loading is true', () => { describe('when loading is true', () => {
beforeEach(() => { beforeEach(() => {
const tableElm = createTable(); const tableElm = createTable();
const Overlay = overlayFactory()(tableElm, true); const Overlay = overlayFactory()(true);
wrapper = render(<Overlay />); wrapper = render(<Overlay>{ tableElm }</Overlay>);
}); });
it('should rendering Overlay component correctly', () => { it('should rendering Overlay component correctly', () => {
const overlay = wrapper.find(LoadingOverlay);
expect(wrapper.length).toBe(1); expect(wrapper.length).toBe(1);
expect(overlay.length).toBe(0);
}); });
}); });
@@ -60,8 +57,8 @@ describe('overlayFactory', () => {
}; };
beforeEach(() => { beforeEach(() => {
const tableElm = createTable(); const tableElm = createTable();
const Overlay = overlayFactory(options)(tableElm, false); const Overlay = overlayFactory(options)(false);
wrapper = shallow(<Overlay />); wrapper = shallow(<Overlay>{ tableElm }</Overlay>);
}); });
it('should rendering Overlay component with options correctly', () => { it('should rendering Overlay component with options correctly', () => {

View File

@@ -1,6 +1,6 @@
{ {
"name": "react-bootstrap-table2-paginator", "name": "react-bootstrap-table2-paginator",
"version": "0.1.1", "version": "0.1.4",
"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

@@ -3,6 +3,8 @@ export default {
PAGE_START_INDEX: 1, PAGE_START_INDEX: 1,
With_FIRST_AND_LAST: true, With_FIRST_AND_LAST: true,
SHOW_ALL_PAGE_BTNS: false, SHOW_ALL_PAGE_BTNS: false,
SHOW_TOTAL: false,
PAGINATION_TOTAL: null,
FIRST_PAGE_TEXT: '<<', FIRST_PAGE_TEXT: '<<',
PRE_PAGE_TEXT: '<', PRE_PAGE_TEXT: '<',
NEXT_PAGE_TEXT: '>', NEXT_PAGE_TEXT: '>',

View File

@@ -1,4 +1,6 @@
/* eslint no-mixed-operators: 0 */ /* eslint no-mixed-operators: 0 */
import Const from './const';
export default ExtendBase => export default ExtendBase =>
class PageResolver extends ExtendBase { class PageResolver extends ExtendBase {
backToPrevPage() { backToPrevPage() {
@@ -27,6 +29,23 @@ export default ExtendBase =>
return pageStartIndex + totalPages - 1; return pageStartIndex + totalPages - 1;
} }
calculateFromTo() {
const {
dataSize,
currPage,
currSizePerPage,
pageStartIndex
} = this.props;
const offset = Math.abs(Const.PAGE_START_INDEX - pageStartIndex);
let from = ((currPage - pageStartIndex) * currSizePerPage);
from = dataSize === 0 ? 0 : from + 1;
let to = Math.min((currSizePerPage * (currPage + offset) - 1), dataSize);
if (to >= dataSize) to -= 1;
return [from, to];
}
calculatePages( calculatePages(
totalPages = this.state.totalPages, totalPages = this.state.totalPages,
lastPage = this.state.lastPage) { lastPage = this.state.lastPage) {

View File

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

View File

@@ -6,6 +6,7 @@ import PropTypes from 'prop-types';
import pageResolver from './page-resolver'; import pageResolver from './page-resolver';
import SizePerPageDropDown from './size-per-page-dropdown'; import SizePerPageDropDown from './size-per-page-dropdown';
import PaginationList from './pagination-list'; import PaginationList from './pagination-list';
import PaginationTotal from './pagination-total';
import Const from './const'; import Const from './const';
class Pagination extends pageResolver(Component) { class Pagination extends pageResolver(Component) {
@@ -86,16 +87,35 @@ 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 { totalPages, lastPage, dropdownOpen: open } = this.state; const { totalPages, lastPage, dropdownOpen: open } = this.state;
const { const {
showTotal,
dataSize,
paginationTotalRenderer,
sizePerPageList, sizePerPageList,
currSizePerPage, currSizePerPage,
hideSizePerPage, hideSizePerPage,
hidePageListOnlyOnePage hidePageListOnlyOnePage
} = this.props; } = this.props;
const pages = this.calculatePageStatus(this.calculatePages(totalPages), lastPage); const pages = this.calculatePageStatus(this.calculatePages(totalPages), 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', {
@@ -117,6 +137,15 @@ class Pagination extends pageResolver(Component) {
/> />
) : null ) : null
} }
{
showTotal ?
this.setTotal(
from,
to,
dataSize,
paginationTotalRenderer
) : null
}
</div> </div>
<div className={ pageListClass }> <div className={ pageListClass }>
<PaginationList pages={ pages } onPageChange={ this.handleChangePage } /> <PaginationList pages={ pages } onPageChange={ this.handleChangePage } />
@@ -135,6 +164,8 @@ Pagination.propTypes = {
onSizePerPageChange: PropTypes.func.isRequired, onSizePerPageChange: PropTypes.func.isRequired,
pageStartIndex: PropTypes.number, pageStartIndex: PropTypes.number,
paginationSize: PropTypes.number, paginationSize: PropTypes.number,
showTotal: PropTypes.bool,
paginationTotalRenderer: PropTypes.func,
firstPageText: PropTypes.string, firstPageText: PropTypes.string,
prePageText: PropTypes.string, prePageText: PropTypes.string,
nextPageText: PropTypes.string, nextPageText: PropTypes.string,
@@ -154,6 +185,8 @@ Pagination.defaultProps = {
paginationSize: Const.PAGINATION_SIZE, paginationSize: Const.PAGINATION_SIZE,
withFirstAndLast: Const.With_FIRST_AND_LAST, withFirstAndLast: Const.With_FIRST_AND_LAST,
alwaysShowAllBtns: Const.SHOW_ALL_PAGE_BTNS, alwaysShowAllBtns: Const.SHOW_ALL_PAGE_BTNS,
showTotal: Const.SHOW_TOTAL,
paginationTotalRenderer: Const.PAGINATION_TOTAL,
firstPageText: Const.FIRST_PAGE_TEXT, firstPageText: Const.FIRST_PAGE_TEXT,
prePageText: Const.PRE_PAGE_TEXT, prePageText: Const.PRE_PAGE_TEXT,
nextPageText: Const.NEXT_PAGE_TEXT, nextPageText: Const.NEXT_PAGE_TEXT,

View File

@@ -145,6 +145,8 @@ export default (Base, {
alwaysShowAllBtns={ alwaysShowAllBtns } alwaysShowAllBtns={ alwaysShowAllBtns }
hideSizePerPage={ hideSizePerPage } hideSizePerPage={ hideSizePerPage }
hidePageListOnlyOnePage={ hidePageListOnlyOnePage } hidePageListOnlyOnePage={ hidePageListOnlyOnePage }
showTotal={ options.showTotal }
paginationTotalRenderer={ options.paginationTotalRenderer }
firstPageText={ options.firstPageText || Const.FIRST_PAGE_TEXT } firstPageText={ options.firstPageText || Const.FIRST_PAGE_TEXT }
prePageText={ options.prePageText || Const.PRE_PAGE_TEXT } prePageText={ options.prePageText || Const.PRE_PAGE_TEXT }
nextPageText={ options.nextPageText || Const.NEXT_PAGE_TEXT } nextPageText={ options.nextPageText || Const.NEXT_PAGE_TEXT }

View File

@@ -110,6 +110,19 @@ describe('PageResolver', () => {
}); });
}); });
describe('calculateFromTo', () => {
const props = createMockProps();
beforeEach(() => {
const mockElement = React.createElement(MockComponent, props, null);
wrapper = shallow(mockElement);
});
it('should return correct array with from and to value', () => {
const instance = wrapper.instance();
expect(instance.calculateFromTo()).toEqual([1, props.currSizePerPage - 1]);
});
});
describe('calculateTotalPage', () => { describe('calculateTotalPage', () => {
const props = createMockProps(); const props = createMockProps();

View File

@@ -15,7 +15,7 @@ const data = [];
for (let i = 0; i < 100; i += 1) { for (let i = 0; i < 100; i += 1) {
data.push({ data.push({
id: i, id: i,
name: `itme name ${i}` name: `item name ${i}`
}); });
} }
@@ -67,29 +67,29 @@ describe('Wrapper', () => {
createPaginationWrapper(props); createPaginationWrapper(props);
}); });
it('should rendering correctly', () => { it('should render correctly', () => {
expect(wrapper.length).toBe(1); expect(wrapper.length).toBe(1);
}); });
it('should initializing state correctly', () => { it('should initialize state correctly', () => {
expect(instance.state.currPage).toBeDefined(); expect(instance.state.currPage).toBeDefined();
expect(instance.state.currPage).toEqual(Const.PAGE_START_INDEX); expect(instance.state.currPage).toEqual(Const.PAGE_START_INDEX);
expect(instance.state.currSizePerPage).toBeDefined(); expect(instance.state.currSizePerPage).toBeDefined();
expect(instance.state.currSizePerPage).toEqual(Const.SIZE_PER_PAGE_LIST[0]); expect(instance.state.currSizePerPage).toEqual(Const.SIZE_PER_PAGE_LIST[0]);
}); });
it('should saving page and sizePerPage to store correctly', () => { it('should save page and sizePerPage to the store correctly', () => {
expect(props.store.page).toBe(instance.state.currPage); expect(props.store.page).toBe(instance.state.currPage);
expect(props.store.sizePerPage).toBe(instance.state.currSizePerPage); expect(props.store.sizePerPage).toBe(instance.state.currSizePerPage);
}); });
it('should rendering BootstraTable correctly', () => { it('should render BootstrapTable correctly', () => {
const table = wrapper.find(BootstrapTable); const table = wrapper.find(BootstrapTable);
expect(table.length).toBe(1); expect(table.length).toBe(1);
expect(table.prop('data').length).toEqual(instance.state.currSizePerPage); expect(table.prop('data').length).toEqual(instance.state.currSizePerPage);
}); });
it('should rendering Pagination correctly', () => { it('should render Pagination correctly', () => {
const pagination = wrapper.find(Pagination); const pagination = wrapper.find(Pagination);
expect(pagination.length).toBe(1); expect(pagination.length).toBe(1);
expect(pagination.prop('dataSize')).toEqual(props.store.data.length); expect(pagination.prop('dataSize')).toEqual(props.store.data.length);
@@ -111,6 +111,7 @@ describe('Wrapper', () => {
expect(pagination.prop('nextPageTitle')).toEqual(Const.NEXT_PAGE_TITLE); expect(pagination.prop('nextPageTitle')).toEqual(Const.NEXT_PAGE_TITLE);
expect(pagination.prop('lastPageTitle')).toEqual(Const.LAST_PAGE_TITLE); expect(pagination.prop('lastPageTitle')).toEqual(Const.LAST_PAGE_TITLE);
expect(pagination.prop('hideSizePerPage')).toEqual(Const.HIDE_SIZE_PER_PAGE); expect(pagination.prop('hideSizePerPage')).toEqual(Const.HIDE_SIZE_PER_PAGE);
expect(pagination.prop('showTotal')).toBeFalsy();
}); });
describe('componentWillReceiveProps', () => { describe('componentWillReceiveProps', () => {
@@ -247,6 +248,20 @@ describe('Wrapper', () => {
}); });
}); });
describe('when options.showTotal is defined', () => {
const props = createTableProps({ options: { showTotal: true } });
beforeEach(() => {
createPaginationWrapper(props);
});
it('should render Pagination correctly', () => {
const pagination = wrapper.find(Pagination);
expect(wrapper.length).toBe(1);
expect(pagination.length).toBe(1);
expect(pagination.prop('showTotal')).toBeTruthy();
});
});
describe('when options.pageStartIndex is defined', () => { describe('when options.pageStartIndex is defined', () => {
const pageStartIndex = -1; const pageStartIndex = -1;
const props = createTableProps({ options: { pageStartIndex } }); const props = createTableProps({ options: { pageStartIndex } });

View File

@@ -1,6 +1,6 @@
{ {
"name": "react-bootstrap-table-next", "name": "react-bootstrap-table-next",
"version": "0.1.5", "version": "0.1.13",
"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

@@ -35,6 +35,9 @@ const Body = (props) => {
if (isEmpty) { if (isEmpty) {
const indication = _.isFunction(noDataIndication) ? noDataIndication() : noDataIndication; const indication = _.isFunction(noDataIndication) ? noDataIndication() : noDataIndication;
if (!indication) {
return null;
}
content = <RowSection content={ indication } colSpan={ visibleColumnSize } />; content = <RowSection content={ indication } colSpan={ visibleColumnSize } />;
} else { } else {
const nonEditableRows = cellEdit.nonEditableRows || []; const nonEditableRows = cellEdit.nonEditableRows || [];

View File

@@ -29,12 +29,15 @@ class BootstrapTable extends PropsBaseResolver(Component) {
render() { render() {
const { loading, overlay } = this.props; const { loading, overlay } = this.props;
const table = this.renderTable(); if (overlay) {
if (loading && overlay) { const LoadingOverlay = overlay(loading);
const LoadingOverlay = overlay(table, loading); return (
return <LoadingOverlay />; <LoadingOverlay>
{ this.renderTable() }
</LoadingOverlay>
);
} }
return table; return this.renderTable();
} }
renderTable() { renderTable() {
@@ -52,9 +55,12 @@ class BootstrapTable extends PropsBaseResolver(Component) {
caption, caption,
rowStyle, rowStyle,
rowClasses, rowClasses,
wrapperClasses,
rowEvents rowEvents
} = this.props; } = this.props;
const tableWrapperClass = cs('react-bootstrap-table', wrapperClasses);
const tableClass = cs('table', { const tableClass = cs('table', {
'table-striped': striped, 'table-striped': striped,
'table-hover': hover, 'table-hover': hover,
@@ -75,7 +81,7 @@ class BootstrapTable extends PropsBaseResolver(Component) {
const tableCaption = (caption && <Caption>{ caption }</Caption>); const tableCaption = (caption && <Caption>{ caption }</Caption>);
return ( return (
<div className="react-bootstrap-table"> <div className={ tableWrapperClass }>
<table id={ id } className={ tableClass }> <table id={ id } className={ tableClass }>
{ tableCaption } { tableCaption }
<Header <Header
@@ -120,6 +126,7 @@ BootstrapTable.propTypes = {
hover: PropTypes.bool, hover: PropTypes.bool,
id: PropTypes.string, id: PropTypes.string,
classes: PropTypes.string, classes: PropTypes.string,
wrapperClasses: PropTypes.string,
condensed: PropTypes.bool, condensed: PropTypes.bool,
caption: PropTypes.oneOfType([ caption: PropTypes.oneOfType([
PropTypes.node, PropTypes.node,
@@ -138,7 +145,9 @@ BootstrapTable.propTypes = {
classes: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), classes: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
nonSelectable: PropTypes.array, nonSelectable: PropTypes.array,
bgColor: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), bgColor: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
hideSelectColumn: PropTypes.bool hideSelectColumn: PropTypes.bool,
selectionRenderer: PropTypes.func,
selectionHeaderRenderer: PropTypes.func
}), }),
onRowSelect: PropTypes.func, onRowSelect: PropTypes.func,
onAllRowsSelect: PropTypes.func, onAllRowsSelect: PropTypes.func,
@@ -149,6 +158,7 @@ BootstrapTable.propTypes = {
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
})), })),
defaultSortDirection: PropTypes.oneOf([Const.SORT_DESC, Const.SORT_ASC]),
overlay: PropTypes.func, overlay: PropTypes.func,
onTableChange: PropTypes.func, onTableChange: PropTypes.func,
onSort: PropTypes.func, onSort: PropTypes.func,

View File

@@ -88,7 +88,9 @@ class Cell extends Component {
cellAttrs.onDoubleClick = this.handleEditingCell; cellAttrs.onDoubleClick = this.handleEditingCell;
} }
return ( return (
<td { ...cellAttrs }>{ content }</td> <td { ...cellAttrs }>
{ typeof content === 'boolean' ? `${content}` : content }
</td>
); );
} }
} }

View File

@@ -127,9 +127,13 @@ HeaderCell.propTypes = {
sort: PropTypes.bool, sort: PropTypes.bool,
sortFunc: PropTypes.func, sortFunc: PropTypes.func,
onSort: PropTypes.func, onSort: PropTypes.func,
editor: PropTypes.object,
editable: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]), editable: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
editCellStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), editCellStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
editCellClasses: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), editCellClasses: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
editorStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
editorClasses: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
editorRenderer: PropTypes.func,
validator: PropTypes.func, validator: PropTypes.func,
filter: PropTypes.object, filter: PropTypes.object,
filterValue: PropTypes.func filterValue: PropTypes.func

View File

@@ -1,6 +1,11 @@
export default ExtendBase => export default ExtendBase =>
class ColumnResolver extends ExtendBase { class ColumnResolver extends ExtendBase {
visibleColumnSize() { visibleColumnSize(includeSelectColumn = true) {
return this.props.columns.filter(c => !c.hidden).length; const columnLen = this.props.columns.filter(c => !c.hidden).length;
if (!includeSelectColumn) return columnLen;
if (this.props.selectRow && !this.props.selectRow.hideSelectColumn) {
return columnLen + 1;
}
return columnLen;
} }
}; };

View File

@@ -5,12 +5,12 @@ import _ from '../utils';
export default ExtendBase => export default ExtendBase =>
class TableResolver extends ColumnResolver(ExtendBase) { class TableResolver extends ColumnResolver(ExtendBase) {
validateProps() { validateProps() {
const { columns, keyField } = this.props; const { keyField } = this.props;
if (!keyField) { if (!keyField) {
throw new Error('Please specify a field as key via keyField'); throw new Error('Please specify a field as key via keyField');
} }
if (this.visibleColumnSize(columns) <= 0) { if (this.visibleColumnSize(false) <= 0) {
throw new Error('No any visible columns detect'); throw new Error('No visible columns detected');
} }
} }

View File

@@ -2,6 +2,7 @@ import _ from './utils';
const events = [ const events = [
'onClick', 'onClick',
'onDoubleClick',
'onMouseEnter', 'onMouseEnter',
'onMouseLeave' 'onMouseLeave'
]; ];
@@ -47,7 +48,7 @@ export default ExtendBase =>
} }
if (selectable) { if (selectable) {
const key = _.get(row, keyField); const key = _.get(row, keyField);
onRowSelect(key, !selected, rowIndex); onRowSelect(key, !selected, rowIndex, e);
} }
}; };

View File

@@ -14,7 +14,8 @@ export default class SelectionCell extends Component {
onRowSelect: PropTypes.func, onRowSelect: PropTypes.func,
disabled: PropTypes.bool, disabled: PropTypes.bool,
rowIndex: PropTypes.number, rowIndex: PropTypes.number,
clickToSelect: PropTypes.bool clickToSelect: PropTypes.bool,
selectionRenderer: PropTypes.func
} }
constructor() { constructor() {
@@ -28,7 +29,7 @@ export default class SelectionCell extends Component {
return nextProps.selected !== selected; return nextProps.selected !== selected;
} }
handleClick() { handleClick(e) {
const { const {
mode: inputType, mode: inputType,
rowKey, rowKey,
@@ -46,23 +47,32 @@ export default class SelectionCell extends Component {
? true ? true
: !selected; : !selected;
onRowSelect(rowKey, checked, rowIndex); onRowSelect(rowKey, checked, rowIndex, e);
} }
render() { render() {
const { const {
mode: inputType, mode: inputType,
selected, selected,
disabled disabled,
selectionRenderer
} = this.props; } = this.props;
return ( return (
<td onClick={ this.handleClick }> <td onClick={ this.handleClick }>
<input {
type={ inputType } selectionRenderer ? selectionRenderer({
checked={ selected } mode: inputType,
disabled={ disabled } checked: selected,
/> disabled
}) : (
<input
type={ inputType }
checked={ selected }
disabled={ disabled }
/>
)
}
</td> </td>
); );
} }

View File

@@ -22,7 +22,8 @@ export default class SelectionHeaderCell extends Component {
static propTypes = { static propTypes = {
mode: PropTypes.string.isRequired, mode: PropTypes.string.isRequired,
checkedStatus: PropTypes.string, checkedStatus: PropTypes.string,
onAllRowsSelect: PropTypes.func onAllRowsSelect: PropTypes.func,
selectionHeaderRenderer: PropTypes.func
} }
constructor() { constructor() {
@@ -44,33 +45,45 @@ export default class SelectionHeaderCell extends Component {
return nextProps.checkedStatus !== checkedStatus; return nextProps.checkedStatus !== checkedStatus;
} }
handleCheckBoxClick() { handleCheckBoxClick(e) {
const { onAllRowsSelect } = this.props; const { onAllRowsSelect } = this.props;
onAllRowsSelect(); onAllRowsSelect(e);
} }
render() { render() {
const { const {
CHECKBOX_STATUS_CHECKED, CHECKBOX_STATUS_INDETERMINATE, ROW_SELECT_SINGLE CHECKBOX_STATUS_CHECKED, CHECKBOX_STATUS_INDETERMINATE, ROW_SELECT_MULTIPLE
} = Const; } = Const;
const { mode, checkedStatus } = this.props; const { mode, checkedStatus, selectionHeaderRenderer } = this.props;
const checked = checkedStatus === CHECKBOX_STATUS_CHECKED; const checked = checkedStatus === CHECKBOX_STATUS_CHECKED;
const indeterminate = checkedStatus === CHECKBOX_STATUS_INDETERMINATE; const indeterminate = checkedStatus === CHECKBOX_STATUS_INDETERMINATE;
return mode === ROW_SELECT_SINGLE const attrs = {};
? <th data-row-selection /> let content;
: ( if (selectionHeaderRenderer) {
<th data-row-selection onClick={ this.handleCheckBoxClick }> content = selectionHeaderRenderer({
<CheckBox mode,
{ ...this.props } checked,
checked={ checked } indeterminate
indeterminate={ indeterminate } });
/> attrs.onClick = this.handleCheckBoxClick;
</th> } else if (mode === ROW_SELECT_MULTIPLE) {
content = (
<CheckBox
{ ...this.props }
checked={ checked }
indeterminate={ indeterminate }
/>
); );
attrs.onClick = this.handleCheckBoxClick;
}
return (
<th data-row-selection { ...attrs }>{ content }</th>
);
} }
} }

View File

@@ -41,7 +41,7 @@ export default Base =>
* @param {String} rowKey - row key of what was selected. * @param {String} rowKey - row key of what was selected.
* @param {Boolean} checked - next checked status of input button. * @param {Boolean} checked - next checked status of input button.
*/ */
handleRowSelect(rowKey, checked, rowIndex) { handleRowSelect(rowKey, checked, rowIndex, e) {
const { selectRow: { mode, onSelect }, store } = this.props; const { selectRow: { mode, onSelect }, store } = this.props;
const { ROW_SELECT_SINGLE } = Const; const { ROW_SELECT_SINGLE } = Const;
@@ -59,7 +59,7 @@ export default Base =>
if (onSelect) { if (onSelect) {
const row = getRowByRowId(store)(rowKey); const row = getRowByRowId(store)(rowKey);
onSelect(row, checked, rowIndex); onSelect(row, checked, rowIndex, e);
} }
this.setState(() => ({ this.setState(() => ({
@@ -68,18 +68,16 @@ export default Base =>
} }
/** /**
* handle all rows selection on header cell by store.selected or given specific result. * handle all rows selection on header cell by store.selected
* @param {Boolean} option - customized result for all rows selection
*/ */
handleAllRowsSelect(option) { handleAllRowsSelect(e) {
const { store, selectRow: { const { store, selectRow: {
onSelectAll, onSelectAll,
nonSelectable nonSelectable
} } = this.props; } } = this.props;
const selected = isAnySelectedRow(store)(nonSelectable); const selected = isAnySelectedRow(store)(nonSelectable);
// set next status of all row selected by store.selected or customizing by user. const result = !selected;
const result = option || !selected;
const currSelected = result ? const currSelected = result ?
selectableKeys(store)(nonSelectable) : selectableKeys(store)(nonSelectable) :
@@ -89,7 +87,7 @@ export default Base =>
store.selected = currSelected; store.selected = currSelected;
if (onSelectAll) { if (onSelectAll) {
onSelectAll(result, getSelectedRows(store)); onSelectAll(result, getSelectedRows(store), e);
} }
this.setState(() => ({ this.setState(() => ({

View File

@@ -79,7 +79,9 @@ class Row extends eventDelegater(Component) {
<EditingCell <EditingCell
key={ `${content}-${index}` } key={ `${content}-${index}` }
row={ row } row={ row }
rowIndex={ rowIndex }
column={ column } column={ column }
columnIndex={ index }
className={ editCellclasses } className={ editCellclasses }
style={ editCellstyle } style={ editCellstyle }
{ ...rest } { ...rest }

View File

@@ -15,7 +15,7 @@ export default Base =>
} }
componentWillMount() { componentWillMount() {
const { columns, defaultSorted, store } = this.props; const { columns, defaultSorted, defaultSortDirection, store } = this.props;
// defaultSorted is an array, it's ready to use as multi / single sort // defaultSorted is an array, it's ready to use as multi / single sort
// when we start to support multi sort, please update following code to use array.forEach // when we start to support multi sort, please update following code to use array.forEach
if (defaultSorted && defaultSorted.length > 0) { if (defaultSorted && defaultSorted.length > 0) {
@@ -23,7 +23,7 @@ export default Base =>
const order = defaultSorted[0].order; const order = defaultSorted[0].order;
const column = columns.filter(col => col.dataField === dataField); const column = columns.filter(col => col.dataField === dataField);
if (column.length > 0) { if (column.length > 0) {
store.setSort(column[0], order); store.setSort(column[0], order, defaultSortDirection);
if (column[0].onSort) { if (column[0].onSort) {
column[0].onSort(store.sortField, store.sortOrder); column[0].onSort(store.sortField, store.sortOrder);
@@ -39,16 +39,23 @@ export default Base =>
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
const sortedColumn = nextProps.columns.find( if (!this.isRemoteSort() && !this.isRemotePagination()) {
column => column.dataField === nextProps.store.sortField); let sortedColumn;
if (sortedColumn && sortedColumn.sort) { for (let i = 0; i < nextProps.columns.length; i += 1) {
nextProps.store.sortBy(sortedColumn); if (nextProps.columns[i].dataField === nextProps.store.sortField) {
sortedColumn = nextProps.columns[i];
break;
}
}
if (sortedColumn && sortedColumn.sort) {
nextProps.store.sortBy(sortedColumn);
}
} }
} }
handleSort(column) { handleSort(column) {
const { store } = this.props; const { store } = this.props;
store.setSort(column); store.setSort(column, undefined, this.props.defaultSortDirection);
if (column.onSort) { if (column.onSort) {
column.onSort(store.sortField, store.sortOrder); column.onSort(store.sortField, store.sortOrder);

View File

@@ -21,8 +21,8 @@ export default class Store {
if (row) _.set(row, dataField, newValue); if (row) _.set(row, dataField, newValue);
} }
setSort({ dataField }, order) { setSort({ dataField }, order, defaultOrder) {
this.sortOrder = nextOrder(this)(dataField, order); this.sortOrder = nextOrder(this)(dataField, order, defaultOrder);
this.sortField = dataField; this.sortField = dataField;
} }

View File

@@ -37,11 +37,11 @@ export const sort = ({ data, sortOrder, sortField }) => (sortFunc) => {
return _data; return _data;
}; };
export const nextOrder = store => (field, order) => { export const nextOrder = store => (field, order, defaultOrder = Const.SORT_DESC) => {
if (order) return order; if (order) return order;
if (field !== store.sortField) { if (field !== store.sortField) {
return Const.SORT_DESC; return defaultOrder;
} }
return store.sortOrder === Const.SORT_DESC ? Const.SORT_ASC : Const.SORT_DESC; return store.sortOrder === Const.SORT_DESC ? Const.SORT_ASC : Const.SORT_DESC;
}; };

View File

@@ -53,12 +53,10 @@ describe('Body', () => {
/>); />);
}); });
it('should render successfully', () => { it('should not render', () => {
expect(wrapper.length).toBe(1); expect(wrapper.length).toBe(1);
expect(wrapper.find('tbody').length).toBe(1); expect(wrapper.find('tbody').length).toBe(0);
expect(wrapper.find(RowSection).length).toBe(1); expect(wrapper.find(RowSection).length).toBe(0);
expect(wrapper.find(RowSection).prop('colSpan')).toBe(columns.length);
expect(wrapper.find(RowSection).prop('content')).toBe(null);
}); });
describe('when noDataIndication props is defined', () => { describe('when noDataIndication props is defined', () => {

View File

@@ -74,6 +74,25 @@ describe('BootstrapTable', () => {
}); });
}); });
describe('when props.wrapperClasses was defined', () => {
const classes = 'foo';
beforeEach(() => {
wrapper = shallow(
<BootstrapTable
keyField="id"
columns={ columns }
data={ data }
store={ store }
wrapperClasses={ classes }
/>);
});
it('should display customized classes correctly', () => {
expect(wrapper.find(`.${classes}`).length).toBe(1);
});
});
describe('when props.id was defined', () => { describe('when props.id was defined', () => {
const id = 'foo'; const id = 'foo';

View File

@@ -27,6 +27,25 @@ describe('Cell', () => {
}); });
}); });
describe('when content is bool value', () => {
const column = {
dataField: 'col1',
text: 'column 1'
};
const aRowWithBoolValue = { col1: true };
beforeEach(() => {
wrapper = shallow(
<Cell row={ aRowWithBoolValue } columnIndex={ 1 } rowIndex={ 1 } column={ column } />
);
});
it('should render successfully', () => {
expect(wrapper.length).toBe(1);
expect(wrapper.text()).toEqual(aRowWithBoolValue[column.dataField].toString());
});
});
describe('when column.formatter prop is defined', () => { describe('when column.formatter prop is defined', () => {
const rowIndex = 1; const rowIndex = 1;
const column = { const column = {

View File

@@ -56,7 +56,7 @@ describe('TableResolver', () => {
}); });
}); });
describe('if columns is all unvisible', () => { describe('if no columns are visible', () => {
beforeEach(() => { beforeEach(() => {
const mockElement = React.createElement(BootstrapTableMock, { const mockElement = React.createElement(BootstrapTableMock, {
data, keyField, columns: [] data, keyField, columns: []
@@ -67,7 +67,7 @@ describe('TableResolver', () => {
it('should throw error', () => { it('should throw error', () => {
expect(() => expect(() =>
wrapper.instance().validateProps() wrapper.instance().validateProps()
).toThrow(new Error('No any visible columns detect')); ).toThrow(new Error('No visible columns detected'));
}); });
}); });
}); });

View File

@@ -193,5 +193,36 @@ describe('<SelectionCell />', () => {
expect(wrapper.find('input').get(0).props.disabled).toBeTruthy(); expect(wrapper.find('input').get(0).props.disabled).toBeTruthy();
}); });
}); });
describe('when selectionRenderer prop is defined', () => {
const DummySelection = () => <div className="dummy" />;
const selectionRenderer = jest.fn().mockReturnValue(<DummySelection />);
beforeEach(() => {
selectionRenderer.mockClear();
wrapper = shallow(
<SelectionCell
rowKey={ 1 }
mode={ mode }
rowIndex={ rowIndex }
selected={ selected }
selectionRenderer={ selectionRenderer }
/>
);
});
it('should render component correctly', () => {
expect(wrapper.find(DummySelection)).toHaveLength(1);
});
it('should call props.selectionRenderer correctly', () => {
expect(selectionRenderer).toHaveBeenCalledTimes(1);
expect(selectionRenderer).toHaveBeenCalledWith({
mode,
checked: selected,
disabled: wrapper.prop('disabled')
});
});
});
}); });
}); });

View File

@@ -126,6 +126,36 @@ describe('<SelectionHeaderCell />', () => {
expect(wrapper.find(CheckBox).get(0).props.indeterminate).toBe(indeterminate); expect(wrapper.find(CheckBox).get(0).props.indeterminate).toBe(indeterminate);
}); });
}); });
describe('when props.selectionHeaderRenderer is defined', () => {
const checkedStatus = Const.CHECKBOX_STATUS_CHECKED;
const DummySelection = () => <div className="dummy" />;
const selectionHeaderRenderer = jest.fn().mockReturnValue(<DummySelection />);
beforeEach(() => {
selectionHeaderRenderer.mockClear();
wrapper = shallow(
<SelectionHeaderCell
mode="checkbox"
checkedStatus={ checkedStatus }
selectionHeaderRenderer={ selectionHeaderRenderer }
/>
);
});
it('should render correctly', () => {
expect(wrapper.find(DummySelection)).toHaveLength(1);
});
it('should call props.selectionHeaderRenderer correctly', () => {
expect(selectionHeaderRenderer).toHaveBeenCalledTimes(1);
expect(selectionHeaderRenderer).toHaveBeenCalledWith({
mode: 'checkbox',
checked: checkedStatus === Const.CHECKBOX_STATUS_CHECKED,
indeterminate: checkedStatus === Const.CHECKBOX_STATUS_INDETERMINATE
});
});
});
}); });
}); });

View File

@@ -162,14 +162,6 @@ describe('RowSelectionWrapper', () => {
wrapper.instance().handleAllRowsSelect(); wrapper.instance().handleAllRowsSelect();
expect(wrapper.state('selectedRowKeys')).toEqual([]); expect(wrapper.state('selectedRowKeys')).toEqual([]);
}); });
it('call handleAllRowsSelect function with a bool args should setting correct state.selectedRowKeys', () => {
wrapper.instance().handleAllRowsSelect(true);
expect(wrapper.state('selectedRowKeys')).toEqual(expect.arrayContaining([firstSelectedRow, secondSelectedRow]));
wrapper.instance().handleAllRowsSelect(false);
expect(wrapper.state('selectedRowKeys')).toEqual([]);
});
}); });
describe('when selectRow.onSelect is defined', () => { describe('when selectRow.onSelect is defined', () => {
@@ -219,13 +211,14 @@ describe('RowSelectionWrapper', () => {
}); });
it('selectRow.onSelect callback should be called correctly when calling handleRowSelect function', () => { it('selectRow.onSelect callback should be called correctly when calling handleRowSelect function', () => {
wrapper.instance().handleAllRowsSelect(); const e = {};
wrapper.instance().handleAllRowsSelect(e);
expect(onSelectAllCallBack.callCount).toEqual(1); expect(onSelectAllCallBack.callCount).toEqual(1);
expect(onSelectAllCallBack.calledWith(true, data)).toBeTruthy(); expect(onSelectAllCallBack.calledWith(true, data, e)).toBeTruthy();
wrapper.instance().handleAllRowsSelect(); wrapper.instance().handleAllRowsSelect(e);
expect(onSelectAllCallBack.callCount).toEqual(2); expect(onSelectAllCallBack.callCount).toEqual(2);
expect(onSelectAllCallBack.calledWith(false, [])).toBeTruthy(); expect(onSelectAllCallBack.calledWith(false, [], e)).toBeTruthy();
}); });
}); });
}); });

View File

@@ -56,10 +56,15 @@ describe('Store Base', () => {
expect(store.sortOrder).toEqual(Const.SORT_DESC); expect(store.sortOrder).toEqual(Const.SORT_DESC);
}); });
it('should force assign sortOrder correctly if second argument is passed', () => { it('should force assign sortOrder correctly if second argument is given', () => {
store.setSort({ dataField }, Const.SORT_DESC); store.setSort({ dataField }, Const.SORT_DESC);
expect(store.sortOrder).toEqual(Const.SORT_DESC); expect(store.sortOrder).toEqual(Const.SORT_DESC);
}); });
it('should force assign sortOrder correctly if third argument is given', () => {
store.setSort({ dataField }, undefined, Const.SORT_ASC);
expect(store.sortOrder).toEqual(Const.SORT_ASC);
});
}); });
describe('sortBy', () => { describe('sortBy', () => {

View File

@@ -63,6 +63,10 @@ describe('Sort Function', () => {
expect(nextOrder(store)('name')).toBe(Const.SORT_DESC); expect(nextOrder(store)('name')).toBe(Const.SORT_DESC);
}); });
it('should return correcly order when store.sortField is not eq next sort field and default sort direction is given', () => {
expect(nextOrder(store)('name', undefined, Const.SORT_ASC)).toBe(Const.SORT_ASC);
});
it('should return correcly order when store.sortField is eq next sort field', () => { it('should return correcly order when store.sortField is eq next sort field', () => {
store.sortField = 'name'; store.sortField = 'name';
store.sortOrder = Const.SORT_DESC; store.sortOrder = Const.SORT_DESC;

View File

@@ -1,64 +0,0 @@
import * as path from 'path';
import webpack from 'webpack';
module.exports = {
entry: {
'react-bootstrap-table2/dist/react-bootstrap-table2': './packages/react-bootstrap-table2/index.js',
'react-bootstrap-table2/dist/react-bootstrap-table2.min': './packages/react-bootstrap-table2/index.js',
'react-bootstrap-table2-editor/dist/react-bootstrap-table2-editor': './packages/react-bootstrap-table2-editor/index.js',
'react-bootstrap-table2-editor/dist/react-bootstrap-table2-editor.min': './packages/react-bootstrap-table2-editor/index.js',
'react-bootstrap-table2-filter/dist/react-bootstrap-table2-filter': './packages/react-bootstrap-table2-filter/index.js',
'react-bootstrap-table2-filter/dist/react-bootstrap-table2-filter.min': './packages/react-bootstrap-table2-filter/index.js',
'react-bootstrap-table2-overlay/dist/react-bootstrap-table2-overlay': './packages/react-bootstrap-table2-overlay/index.js',
'react-bootstrap-table2-overlay/dist/react-bootstrap-table2-overlay.min': './packages/react-bootstrap-table2-overlay/index.js',
'react-bootstrap-table2-paginator/dist/react-bootstrap-table2-paginator': './packages/react-bootstrap-table2-paginator/index.js',
'react-bootstrap-table2-paginator/dist/react-bootstrap-table2-paginator.min': './packages/react-bootstrap-table2-paginator/index.js'
},
devtool: 'source-map',
output: {
path: path.join(__dirname, 'packages'),
filename: '[name].js',
library: 'ReactBootstrapTable',
libraryTarget: 'umd'
},
externals: [{
'react': {
root: 'React',
commonjs2: 'react',
commonjs: 'react',
amd: 'react'
}
}, {
'react-dom': {
root: 'ReactDOM',
commonjs2: 'react-dom',
commonjs: 'react-dom',
amd: 'react-dom'
}
}],
module: {
rules: [{
enforce: 'pre',
test: /\.js?$/,
exclude: /node_modules/,
loader: 'eslint-loader'
}, {
test: /\.js?$/,
use: ['babel-loader'],
exclude: /node_modules/
}]
},
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
}),
new webpack.SourceMapDevToolPlugin(),
new webpack.optimize.DedupePlugin(),
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.optimize.AggressiveMergingPlugin(),
new webpack.optimize.UglifyJsPlugin({
include: /\.min\.js$/,
compress: { warnings: false }
})
]
};

View File

@@ -0,0 +1,16 @@
import * as path from 'path';
import umdConfig from './webpack.umd.babel';
module.exports = {
...umdConfig,
entry: {
'react-bootstrap-table2-editor/dist/react-bootstrap-table2-editor': './packages/react-bootstrap-table2-editor/index.js',
'react-bootstrap-table2-editor/dist/react-bootstrap-table2-editor.min': './packages/react-bootstrap-table2-editor/index.js'
},
output: {
path: path.join(__dirname, '../packages'),
filename: '[name].js',
library: 'ReactBootstrapTable2Editor',
libraryTarget: 'umd'
}
};

View File

@@ -0,0 +1,16 @@
import * as path from 'path';
import umdConfig from './webpack.umd.babel';
module.exports = {
...umdConfig,
entry: {
'react-bootstrap-table2-filter/dist/react-bootstrap-table2-filter': './packages/react-bootstrap-table2-filter/index.js',
'react-bootstrap-table2-filter/dist/react-bootstrap-table2-filter.min': './packages/react-bootstrap-table2-filter/index.js'
},
output: {
path: path.join(__dirname, '../packages'),
filename: '[name].js',
library: 'ReactBootstrapTable2Filter',
libraryTarget: 'umd'
}
};

16
webpack/next.umd.babel.js Normal file
View File

@@ -0,0 +1,16 @@
import * as path from 'path';
import umdConfig from './webpack.umd.babel';
module.exports = {
...umdConfig,
entry: {
'react-bootstrap-table2/dist/react-bootstrap-table-next': './packages/react-bootstrap-table2/index.js',
'react-bootstrap-table2/dist/react-bootstrap-table-next.min': './packages/react-bootstrap-table2/index.js'
},
output: {
path: path.join(__dirname, '../packages'),
filename: '[name].js',
library: 'ReactBootstrapTable2',
libraryTarget: 'umd'
}
};

View File

@@ -0,0 +1,16 @@
import * as path from 'path';
import umdConfig from './webpack.umd.babel';
module.exports = {
...umdConfig,
entry: {
'react-bootstrap-table2-overlay/dist/react-bootstrap-table2-overlay': './packages/react-bootstrap-table2-overlay/index.js',
'react-bootstrap-table2-overlay/dist/react-bootstrap-table2-overlay.min': './packages/react-bootstrap-table2-overlay/index.js'
},
output: {
path: path.join(__dirname, '../packages'),
filename: '[name].js',
library: 'ReactBootstrapTable2Overlay',
libraryTarget: 'umd'
}
};

View File

@@ -0,0 +1,16 @@
import * as path from 'path';
import umdConfig from './webpack.umd.babel';
module.exports = {
...umdConfig,
entry: {
'react-bootstrap-table2-paginator/dist/react-bootstrap-table2-paginator': './packages/react-bootstrap-table2-paginator/index.js',
'react-bootstrap-table2-paginator/dist/react-bootstrap-table2-paginator.min': './packages/react-bootstrap-table2-paginator/index.js'
},
output: {
path: path.join(__dirname, '../packages'),
filename: '[name].js',
library: 'ReactBootstrapTable2Paginator',
libraryTarget: 'umd'
}
};

Some files were not shown because too many files have changed in this diff Show More