Compare commits

..

87 Commits

Author SHA1 Message Date
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
AllenFang
bd9150f88f Publish
- react-bootstrap-table2-editor@0.1.3
 - react-bootstrap-table2-example@0.1.4
 - react-bootstrap-table-next@0.1.5
2018-03-18 23:07:46 +08:00
Allen
3956fbca11 Merge pull request #263 from react-bootstrap-table/develop
2018/03/19 release
2018-03-18 23:03:55 +08:00
NickChen
240bcd75c0 Merge pull request #262 from prajapati-parth/update-readme
Add TravisCI badge to README
2018-03-18 17:48:11 +08:00
Chun-MingChen
6de57737ea allow travis to run test for master branch 2018-03-18 17:35:05 +08:00
Parth Prajapati
33a8da701b Add TravisCI badge 2018-03-18 14:28:27 +05:30
AllenFang
d5ddd8c3af add selection management example 2018-03-18 16:44:39 +08:00
Chun-MingChen
6f9361934a set state.selectedRowKeys based on store 2018-03-18 16:10:06 +08:00
Parth Prajapati
6bc81dddd0 Fixed #237 (#261)
* Fixed #237

* Solved lint errors

* Removed test cases for display: none checks
- added test cases for hidden columns check
2018-03-18 15:42:21 +08:00
AllenFang
c11539b9fb [docs] patch id and classes for BootstrapTable 2018-03-18 14:33:11 +08:00
Allen
94f1a5ee57 Merge pull request #247 from Chun-MingChen/feature/customized-class-n-id-on-table
customized classes and id on BootstrapTable (#235)
2018-03-18 14:25:55 +08:00
Allen
de27072ceb Merge pull request #260 from react-bootstrap-table/bugfix/default-selection
Bugfix/default selection
2018-03-18 14:16:49 +08:00
AllenFang
55336108a0 should recieve newest selectRow.selected 2018-03-18 14:07:44 +08:00
Chun-MingChen
923439dc81 correct typo 2018-03-17 17:29:30 +08:00
Chun-MingChen
d80ae13513 [test] test for RowSelectionWrapper#componentWillReceiveProps 2018-03-17 17:27:27 +08:00
Chun-MingChen
ceebdf5a13 refine store to set selectRow when receiving props 2018-03-17 17:27:27 +08:00
Chun-MingChen
0eda54b772 correct the typo of documents 2018-03-17 16:23:09 +08:00
Chun-MingChen
3ed4d87b29 correct attribute key of columns.headerEvent in column-event-tables 2018-03-17 15:29:23 +08:00
Chun-MingChen
8a8c2d4964 [example] add demo for customized classes and id table 2018-03-10 18:54:59 +08:00
Chun-MingChen
3cea9658c7 [test] test for customized classes and id 2018-03-10 18:54:41 +08:00
Chun-MingChen
9f9203bffa implement customized classes and id on the table 2018-03-10 18:54:26 +08:00
AllenFang
4011cae18e Publish 2018-03-06 23:59:04 +08:00
Allen
60f32f0336 Merge pull request #240 from react-bootstrap-table/develop
2018/03/06 release
2018-03-06 23:40:05 +08:00
Allen
a5cb806d98 implement default selection (#234) 2018-03-05 22:27:46 +08:00
Allen
9382ed587b implement row event delegater (#233) 2018-03-04 17:22:52 +08:00
Allen
a11913c49a fix #210 (#232) 2018-03-04 16:21:10 +08:00
AllenFang
4635b60da0 Merge branch 'develop' of https://github.com/react-bootstrap-table/react-bootstrap-table2 into develop 2018-03-04 16:10:23 +08:00
AllenFang
4d9e20e9c8 fix #221 2018-03-04 16:05:10 +08:00
Parth Prajapati
931cf80450 Fixes #186 (#219)
* Fixes #186

* Solved lint error
2018-03-04 16:05:10 +08:00
AllenFang
5dd1f1e9ea fix #221 2018-02-24 23:03:09 +08:00
Parth Prajapati
a8083ac17d Fixes #186 (#219)
* Fixes #186

* Solved lint error
2018-02-24 22:53:52 +08:00
AllenFang
096799c403 Publish
- react-bootstrap-table2-example@0.1.2
 - react-bootstrap-table2-filter@0.1.3
 - react-bootstrap-table-next@0.1.3
2018-02-14 16:33:51 +08:00
Allen
6dee718081 2018/02/14 release
2018/02/14 release
2018-02-14 16:30:02 +08:00
Allen
936a82954c fix #196
Support sort event listener
2018-02-10 21:17:00 +08:00
AllenFang
ba24990994 add story for sort event listener 2018-02-10 17:46:38 +08:00
AllenFang
e7ccd47817 implement sort events listener 2018-02-10 17:46:38 +08:00
AllenFang
a0af964d76 fix #195 2018-02-10 17:00:29 +08:00
Allen
865be93ef7 refine caseSensitive for filter (#201) 2018-02-10 16:54:01 +08:00
makenova
65a596a0e9 case insensitive text filter (#190)
* case insensitive text filter

* optional case insensitive filter
2018-02-10 16:17:45 +08:00
Allen
024bba15fa fix #192
Implement number filter
2018-02-10 16:15:37 +08:00
AllenFang
f9217930e7 patch docs for number filter 2018-02-10 16:04:46 +08:00
AllenFang
fc34ea12e6 patch test for number filter 2018-02-10 16:04:46 +08:00
AllenFang
b0f411e934 add number filter stories 2018-02-10 16:04:46 +08:00
AllenFang
28a1077bad implement number filter 2018-02-10 15:43:22 +08:00
AllenFang
ca32eee28e patch rowEvents docs 2018-02-04 21:41:52 +08:00
Allen
7030b54cbd Fix #179
Solves #179
2018-02-04 21:37:10 +08:00
Allen
4d7378e3f1 create LICENSE 2018-02-01 23:41:30 +08:00
Parth Prajapati
577973a147 Updated storybook example 2018-02-01 06:27:49 +05:30
Parth Prajapati
c4f14e2b69 Added row object to onClick
- attach onClick only if defined
2018-02-01 06:20:50 +05:30
Parth Prajapati
feedcb9f4b Solves #179 2018-01-31 07:43:05 +05:30
107 changed files with 4390 additions and 493 deletions

View File

@@ -9,9 +9,10 @@ cache:
branches: branches:
only: only:
# skip master branch when it's under development phase - 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

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 react-bootstrap-table2
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,4 +1,5 @@
# react-bootstrap-table2 # react-bootstrap-table2
[![Build Status](https://travis-ci.org/react-bootstrap-table/react-bootstrap-table2.svg?branch=master)](https://travis-ci.org/react-bootstrap-table/react-bootstrap-table2)
Rebuilt [react-bootstrap-table](https://github.com/AllenFang/react-bootstrap-table) Rebuilt [react-bootstrap-table](https://github.com/AllenFang/react-bootstrap-table)
> `react-bootstrap-table2`'s npm module name is [**`react-bootstrap-table-next`**](https://www.npmjs.com/package/react-bootstrap-table-next) due to some guys already used it > `react-bootstrap-table2`'s npm module name is [**`react-bootstrap-table-next`**](https://www.npmjs.com/package/react-bootstrap-table-next) due to some guys already used it

View File

@@ -15,12 +15,15 @@
* [bordered](#bordered) * [bordered](#bordered)
* [hover](#hover) * [hover](#hover)
* [condensed](#condensed) * [condensed](#condensed)
* [id](#id)
* [classes](#classes)
* [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)
@@ -59,14 +62,14 @@ A special case for remote pagination:
remote={ { pagination: true, filter: false, sort: false } } remote={ { pagination: true, filter: false, sort: false } }
``` ```
There is a apecial case for remote pagination, even you only specified the paignation need to handle as remote, `react-bootstrap-table2` will handle all the table changes(filter, sort etc) as remote mode, because `react-bootstrap-table2` only know the data of current page, but filtering, searching or sort need to work on overall datas. There is a special case for remote pagination, even you only specified the pagination need to handle as remote, `react-bootstrap-table2` will handle all the table changes(filter, sort etc) as remote mode, because `react-bootstrap-table2` only know the data of current page, but filtering, searching or sort need to work on overall data.
### <a name='loading'>loading - [Bool]</a> ### <a name='loading'>loading - [Bool]</a>
Telling if table is loading or not, for example: waiting data loading, filtering etc. It's **only** valid when [`remote`](#remote) is enabled. Telling if table is loading or not, for example: waiting data loading, filtering etc. It's **only** valid when [`remote`](#remote) is enabled.
When `loading` is `true`, `react-bootstrap-table2` will attend to render a overlay on table via [`overlay`](#overlay) prop, if [`overlay`](#overlay) prop is not given, `react-bootstrap-table2` will ignore the overlay rendering. When `loading` is `true`, `react-bootstrap-table2` will attend to render a overlay on table via [`overlay`](#overlay) prop, if [`overlay`](#overlay) prop is not given, `react-bootstrap-table2` will ignore the overlay rendering.
### <a name='overlay'>overlay - [Function]</a> ### <a name='overlay'>overlay - [Function]</a>
`overlay` accept a factory funtion which should returning a higher order component. By default, `react-bootstrap-table2-overlay` can be a good option for you: `overlay` accept a factory function which should returning a higher order component. By default, `react-bootstrap-table2-overlay` can be a good option for you:
```sh ```sh
$ npm install react-bootstrap-table2-overlay $ npm install react-bootstrap-table2-overlay
@@ -100,6 +103,10 @@ Same as bootstrap `.table-hover` class for adding mouse hover effect (grey backg
### <a name='condensed'>condensed - [Bool]</a> ### <a name='condensed'>condensed - [Bool]</a>
Same as bootstrap `.table-condensed` class for making a table more compact by cutting cell padding in half. Same as bootstrap `.table-condensed` class for making a table more compact by cutting cell padding in half.
### <a name='id'>id - [String]</a>
Customize id on `table` element.
### <a name='classes'>classes - [String]</a>
Customize class on `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.
@@ -145,7 +152,7 @@ Custom the events on row:
```js ```js
const rowEvents = { const rowEvents = {
onClick: (e) => { onClick: (e, row, rowIndex) => {
.... ....
} }
}; };
@@ -162,8 +169,11 @@ 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 funcitonality is separated from core of `react-bootstrap-table2` so that you are suppose to install `react-bootstrap-table2-paginator` additionaly. `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.
```sh ```sh
$ npm install react-bootstrap-table2-paginator --save $ npm install react-bootstrap-table2-paginator --save
@@ -205,7 +215,7 @@ paginator({
prePageTitle: 'Go to previous', // the title of previous page button prePageTitle: 'Go to previous', // the title of previous page button
firstPageTitle: 'Go to first', // the title of first page button firstPageTitle: 'Go to first', // the title of first page button
lastPageTitle: 'Go to last', // the title of last page button lastPageTitle: 'Go to last', // the title of last page button
hideSizePerPage: true, // hide the size per page dorpdown hideSizePerPage: true, // hide the size per page dropdown
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
@@ -213,7 +223,7 @@ paginator({
``` ```
### <a name='filter'>filter - [Object]</a> ### <a name='filter'>filter - [Object]</a>
`filter` allow user to filter data by column. However, filter funcitonality is separated from core of `react-bootstrap-table2` so that you are suppose to install `react-bootstrap-table2-filter` firstly. `filter` allow user to filter data by column. However, filter functionality is separated from core of `react-bootstrap-table2` so that you are suppose to install `react-bootstrap-table2-filter` firstly.
```sh ```sh
$ npm install react-bootstrap-table2-filter --save $ npm install react-bootstrap-table2-filter --save

View File

@@ -12,6 +12,7 @@ Available properties in a column object:
* [formatExtraData](#formatExtraData) * [formatExtraData](#formatExtraData)
* [sort](#sort) * [sort](#sort)
* [sortFunc](#sortFunc) * [sortFunc](#sortFunc)
* [onSort](#onSort)
* [classes](#classes) * [classes](#classes)
* [style](#style) * [style](#style)
* [title](#title) * [title](#title)
@@ -31,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)
@@ -122,8 +127,21 @@ Enable the column sort via a `true` value given.
``` ```
> The possible value of `order` argument is **`asc`** and **`desc`**. > The possible value of `order` argument is **`asc`** and **`desc`**.
## <a name='sortFunc'>column.onSort - [Function]</a>
`column.onSort` is an event listener for sort change event:
```js
{
// omit...
sort: true,
onSort: (field, order) => {
// ....
}
}
```
## <a name='classes'>column.classes - [String | Function]</a> ## <a name='classes'>column.classes - [String | Function]</a>
It's availabe to have custom class on table column: It's available to have custom class on table column:
```js ```js
{ {
@@ -151,7 +169,7 @@ In addition, `classes` also accept a callback function which have more power to
A new `String` will be the result as element class. A new `String` will be the result as element class.
## <a name='headerClasses'>column.headerClasses - [String | Function]</a> ## <a name='headerClasses'>column.headerClasses - [String | Function]</a>
It's similar to [`column.classes`](#classes), `headerClasses` is availabe to have customized class on table header column: It's similar to [`column.classes`](#classes), `headerClasses` is available to have customized class on table header column:
```js ```js
{ {
@@ -176,7 +194,7 @@ Furthermore, it also accept a callback function which takes 2 arguments and a `S
A new `String` will be the result of element headerClasses. A new `String` will be the result of element headerClasses.
## <a name='style'>column.style - [Object | Function]</a> ## <a name='style'>column.style - [Object | Function]</a>
It's availabe to have custom style on table column: It's available to have custom style on table column:
```js ```js
{ {
@@ -206,7 +224,7 @@ A new `Object` will be the result of element style.
## <a name='headerStyle'>column.headerStyle - [Object | Function]</a> ## <a name='headerStyle'>column.headerStyle - [Object | Function]</a>
It's availabe to have customized inline-style on table header column: It's available to have customized inline-style on table header column:
```js ```js
{ {
@@ -264,7 +282,7 @@ A new `String` will be the result of element title.
} }
``` ```
It's also availabe to custom via a callback function: It's also available to custom via a callback function:
```js ```js
{ {
headerTitle: function callback(column, colIndex) { ... } headerTitle: function callback(column, colIndex) { ... }
@@ -387,7 +405,7 @@ A new `Object` will be the result of element HTML attributes.
> Caution: > Caution:
> If `column.classes`, `column.style`, `column.title`, `column.hidden` or `column.align` was given at the same time, property `attrs` has lower priorty and it will be overwrited. > If `column.classes`, `column.style`, `column.title`, `column.hidden` or `column.align` was given at the same time, property `attrs` has lower priority and it will be overwritten.
```js ```js
{ {
@@ -398,7 +416,7 @@ A new `Object` will be the result of element HTML attributes.
``` ```
## <a name='headerAttrs'>column.headerAttrs - [Object | Function]</a> ## <a name='headerAttrs'>column.headerAttrs - [Object | Function]</a>
`headerAttrs` is similiar to [`column.attrs`](#attrs) but it works for header column. `headerAttrs` is similar to [`column.attrs`](#attrs) but it works for header column.
```js ```js
{ {
// omit... // omit...
@@ -430,7 +448,7 @@ A new `Object` will be the result of element headerAttrs.
> Caution: > Caution:
> Same as [column.attrs](#attrs), it has lower priority and will be > Same as [column.attrs](#attrs), it has lower priority and will be
> overwrited when other props related to HTML attributes were given. > overwritten when other props related to HTML attributes were given.
### <a name='headerSortingClasses'>headerSortingClasses - [String | Function]</a> ### <a name='headerSortingClasses'>headerSortingClasses - [String | Function]</a>
@@ -453,7 +471,7 @@ const headerSortingClasses = (column, sortOrder, isLastSorting, colIndex) => { .
### <a name='headerSortingStyle'>headerSortingStyle - [Object | Function]</a> ### <a name='headerSortingStyle'>headerSortingStyle - [Object | Function]</a>
It's similiar to [headerSortingClasses](#headerSortingClasses). It allows to customize the style of header cell when this column is sorting. A style `Object` and `callback` are acceptable. `callback` takes **4** arguments and an `Object` is expected to return: It's similar to [headerSortingClasses](#headerSortingClasses). It allows to customize the style of header cell when this column is sorting. A style `Object` and `callback` are acceptable. `callback` takes **4** arguments and an `Object` is expected to return:
```js ```js
const sortingHeaderStyle = { const sortingHeaderStyle = {
@@ -488,7 +506,7 @@ If a callback function given, you can control the editable level as cell level:
} }
``` ```
The return value can be a bool or an object. If your valiation is pass, return `true` explicitly. If your valiation is invalid, return following object instead: The return value can be a bool or an object. If your validation is pass, return `true` explicitly. If your validation is invalid, return following object instead:
```js ```js
{ {
valid: false, valid: false,
@@ -538,6 +556,93 @@ 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:
@@ -560,7 +665,7 @@ import { textFilter } from 'react-bootstrap-table2-filter';
For some reason of simple customization, `react-bootstrap-table2` allow you to pass some props to filter factory function. Please check [here](https://github.com/react-bootstrap-table/react-bootstrap-table2/tree/master/packages/react-bootstrap-table2-filter/README.md) for more detail tutorial. For some reason of simple customization, `react-bootstrap-table2` allow you to pass some props to filter factory function. Please check [here](https://github.com/react-bootstrap-table/react-bootstrap-table2/tree/master/packages/react-bootstrap-table2-filter/README.md) for more detail tutorial.
## <a name='filterValue'>column.filterValue - [Function]</a> ## <a name='filterValue'>column.filterValue - [Function]</a>
Sometimes, if the cell/column value that you don't want to filter on them, you can define `filterValue` to return a actual value you wanna be filterd: Sometimes, if the cell/column value that you don't want to filter on them, you can define `filterValue` to return a actual value you wanna be filtered:
**Parameters** **Parameters**
* `cell`: The value of current cell. * `cell`: The value of current cell.

View File

@@ -1,8 +1,8 @@
# Migration Guide # Migration Guide
* Please see the [CHANGELOG](https://react-bootstrap-table.github.io/react-bootstrap-table2/blog/2018/01/24/new-version-0.1.0.html) for `react-bootstrap-table2` first drop. * Please see the [CHANGELOG](https://react-bootstrap-table.github.io/react-bootstrap-table2/blog/2018/01/24/new-version-0.1.0.html) for `react-bootstrap-table2` first drop.
* Please see the [Roadmap](https://react-bootstrap-table.github.io/react-bootstrap-table2/blog/2018/01/24/release-plan.html) for `react-bootstrap-table2` in 2018/Q1. * Please see the [Road Map](https://react-bootstrap-table.github.io/react-bootstrap-table2/blog/2018/01/24/release-plan.html) for `react-bootstrap-table2` in 2018/Q1.
* Feel free to see the [offical docs](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/about.html), we list all the basic usage here!! * Feel free to see the [official docs](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/about.html), we list all the basic usage here!!
## Preface ## Preface
@@ -23,11 +23,11 @@ Currently, **I still can't implement all the mainly features in legacy `react-bo
* [`react-bootstrap-table2-overlay`](https://www.npmjs.com/package/react-bootstrap-table2-overlay) * [`react-bootstrap-table2-overlay`](https://www.npmjs.com/package/react-bootstrap-table2-overlay)
* Overlay/Loading Addons * Overlay/Loading Addons
This can help your application with less bundled size and also help `react-bootstrap-table2` have clean design to avoid handling to much logic in kernal module(SRP). Hence, which means you probably need to install above addons when you need specific features. This can help your application with less bundled size and also help `react-bootstrap-table2` have clean design to avoid handling to much logic in kernel module(SRP). Hence, which means you probably need to install above addons when you need specific features.
## Core Table Migration ## Core Table Migration
There is a big chagne is that there's no `TableHeaderColumn` in the `react-bootstrap-table2`, instead you are supposed to be define the `columns` prop on `BootstrapTable`: There is a big change is that there's no `TableHeaderColumn` in the `react-bootstrap-table2`, instead you are supposed to be define the `columns` prop on `BootstrapTable`:
```js ```js
import BootstrapTable from 'react-bootstrap-table-next'; import BootstrapTable from 'react-bootstrap-table-next';
@@ -48,8 +48,8 @@ const columns = [{
The `text` property is just same as the children for the `TableHeaderColumn`, if you want to custom the header, there's a new property is: [`headerFormatter`](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columnheaderformatter-function). The `text` property is just same as the children for the `TableHeaderColumn`, if you want to custom the header, there's a new property is: [`headerFormatter`](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columnheaderformatter-function).
* [`BootstrapTable` Definitation](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/table-props.html) * [`BootstrapTable` Definition](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/table-props.html)
* [Column Definitation](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html) * [Column Definition](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html)
## Table Sort ## Table Sort
@@ -60,11 +60,12 @@ Please see [Work with table sort](https://react-bootstrap-table.github.io/react-
- [x] Default Sort - [x] Default Sort
- [x] Remote mode - [x] Remote mode
- [x] Custom the sorting header - [x] Custom the sorting header
- [x] Sort event listener
- [ ] Custom the sort caret - [ ] Custom the sort caret
- [ ] Sort management - [ ] Sort management
- [ ] Multi sort - [ ] Multi sort
Due to no `TableHeaderColumn` so that no `dataSort` here, please add [`sort`](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columnsort-bool) property on column definitation. Due to no `TableHeaderColumn` so that no `dataSort` here, please add [`sort`](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columnsort-bool) property on column definition.
## Row Selection ## Row Selection
@@ -85,14 +86,14 @@ Please see [available filter configuration](https://react-bootstrap-table.github
- [ ] Regex Filter - [ ] Regex Filter
- [x] Select Filter - [x] Select Filter
- [x] Custom Select Filter - [x] Custom Select Filter
- [ ] Number Filter - [X] Number Filter
- [ ] Date Filter - [ ] Date Filter
- [ ] Array Filter - [ ] Array Filter
- [ ] Programmatically Filter - [ ] 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.
Due to no `TableHeaderColumn` so that no `filter` here, please add [`filter`](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columnfilter-object) property on column definitation and [`filter`](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/table-props.html#filter-object) prop on `BootstrapTable`. Due to no `TableHeaderColumn` so that no `filter` here, please add [`filter`](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columnfilter-object) property on column definition and [`filter`](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/table-props.html#filter-object) prop on `BootstrapTable`.
## Cell Edit ## Cell Edit

View File

@@ -6,6 +6,7 @@
* [mode (**required**)](#mode) * [mode (**required**)](#mode)
## Optional ## Optional
* [selected](#selected)
* [style](#style) * [style](#style)
* [classes)](#classes) * [classes)](#classes)
* [bgColor](#bgColor) * [bgColor](#bgColor)
@@ -52,6 +53,16 @@ const selectRow = {
/> />
``` ```
### <a name='selected'>selectRow.selected - [Array]</a>
`selectRow.selected` allow you have default selections on table.
```js
const selectRow = {
mode: 'checkbox',
selected: [1, 3] // should be a row keys array
};
```
### <a name='style'>selectRow.style - [Object | Function]</a> ### <a name='style'>selectRow.style - [Object | Function]</a>
`selectRow.style` allow you to have custom style on selected rows: `selectRow.style` allow you to have custom style on selected rows:
@@ -91,7 +102,7 @@ const selectRow = {
``` ```
### <a name='bgColor'>selectRow.bgColor - [String | Function]</a> ### <a name='bgColor'>selectRow.bgColor - [String | Function]</a>
The backgroud color when row is selected The background color when row is selected
```js ```js
const selectRow = { const selectRow = {
@@ -147,12 +158,12 @@ const selectRow = {
### <a name='onSelect'>selectRow.onSelect - [Function]</a> ### <a name='onSelect'>selectRow.onSelect - [Function]</a>
This callback function will be called when a row is select/unselect and pass following three arguments: This callback function will be called when a row is select/unselect and pass following three arguments:
`row`, `isSelect` and `rowIndex`. `row`, `isSelect`, `rowIndex` and `e`.
```js ```js
const selectRow = { const selectRow = {
mode: 'checkbox', mode: 'checkbox',
onSelect: (row, isSelect, rowIndex) => { onSelect: (row, isSelect, rowIndex, e) => {
// ... // ...
} }
}; };
@@ -164,7 +175,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

@@ -85,7 +85,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.1", "version": "0.2.0",
"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);
@@ -32,9 +36,10 @@ TextEditor.propTypes = {
defaultValue: PropTypes.oneOfType([ defaultValue: PropTypes.oneOfType([
PropTypes.string, PropTypes.string,
PropTypes.number PropTypes.number
]).isRequired ])
}; };
TextEditor.defaultProps = { TextEditor.defaultProps = {
className: null className: null,
defaultValue: ''
}; };
export default TextEditor; export default TextEditor;

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

@@ -0,0 +1,48 @@
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import Code from 'components/common/code-block';
import { productsGenerator } from 'utils/common';
const products = productsGenerator();
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'price',
text: 'Product Price'
}];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'price',
text: 'Product Price'
}];
<BootstrapTable id="bar" keyField='id' data={ products } columns={ columns } />
<BootstrapTable classes="foo" keyField='id' data={ products } columns={ columns } />
`;
export default () => (
<div>
<h4> Customized table ID </h4>
<BootstrapTable id="bar" keyField="id" data={ products } columns={ columns } />
<h4> Customized table className </h4>
<BootstrapTable classes="foo" keyField="id" data={ products } columns={ columns } />
<Code>{ sourceCode }</Code>
</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,74 @@
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);
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'price',
text: 'Product Price',
filter: numberFilter({
options: [2100, 2103, 2105],
delay: 600,
placeholder: 'custom placeholder',
withoutEmptyComparatorOption: true,
comparators: [Comparator.EQ, Comparator.GT, Comparator.LT],
style: { display: 'inline-grid' },
className: 'custom-numberfilter-class',
comparatorStyle: { backgroundColor: 'antiquewhite' },
comparatorClassName: 'custom-comparator-class',
numberStyle: { backgroundColor: 'cadetblue', margin: '0px' },
numberClassName: 'custom-number-class'
})
}];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, { numberFilter, Comparator } from 'react-bootstrap-table2-filter';
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'price',
text: 'Product Price',
filter: numberFilter({
options: [2100, 2103, 2105],
delay: 600,
placeholder: 'custom placeholder',
withoutEmptyComparatorOption: true,
comparators: [Comparator.EQ, Comparator.GT, Comparator.LT],
style: { display: 'inline-grid' },
className: 'custom-numberfilter-class',
comparatorStyle: { backgroundColor: 'antiquewhite' },
comparatorClassName: 'custom-comparator-class',
numberStyle: { backgroundColor: 'cadetblue', margin: '0px' },
numberClassName: 'custom-number-class'
})
}];
<BootstrapTable keyField='id' data={ products } columns={ columns } filter={ filterFactory() } />
`;
export default () => (
<div>
<BootstrapTable
keyField="id"
data={ products }
columns={ columns }
filter={ filterFactory() }
/>
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -0,0 +1,54 @@
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);
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'price',
text: 'Product Price',
filter: numberFilter({
defaultValue: { number: 2103, comparator: Comparator.GT }
})
}];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, { numberFilter, Comparator } from 'react-bootstrap-table2-filter';
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'price',
text: 'Product Price',
filter: numberFilter({
defaultValue: { number: 2103, comparator: Comparator.GT }
})
}];
<BootstrapTable keyField='id' data={ products } columns={ columns } filter={ filterFactory() } />
`;
export default () => (
<div>
<BootstrapTable
keyField="id"
data={ products }
columns={ columns }
filter={ filterFactory() }
/>
<Code>{ sourceCode }</Code>
</div>
);

View File

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

View File

@@ -0,0 +1,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

@@ -0,0 +1,51 @@
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, { textFilter } from 'react-bootstrap-table2-filter';
import Code from 'components/common/code-block';
import { productsGenerator } from 'utils/common';
const products = productsGenerator(8);
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name',
filter: textFilter({ caseSensitive: true })
}, {
dataField: 'price',
text: 'Product Price'
}];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, { textFilter } from 'react-bootstrap-table2-filter';
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name',
filter: textFilter({ caseSensitive: true })
}, {
dataField: 'price',
text: 'Product Price'
}];
<BootstrapTable keyField='id' data={ products } columns={ columns } filter={ filterFactory() } />
`;
export default () => (
<div>
<h3>Product Name is case sensitive</h3>
<BootstrapTable
keyField="id"
data={ products }
columns={ columns }
filter={ filterFactory() }
/>
<Code>{ sourceCode }</Code>
</div>
);

View File

@@ -28,7 +28,7 @@ import BootstrapTable from 'react-bootstrap-table-next';
const columns = [{ const columns = [{
dataField: 'id', dataField: 'id',
text: 'Product ID', text: 'Product ID',
events: { headerEvents: {
onClick: () => alert('Click on Product ID header column') onClick: () => alert('Click on Product ID header column')
} }
}, { }, {

View File

@@ -65,6 +65,7 @@ const options = {
prePageTitle: 'Pre page', prePageTitle: 'Pre page',
firstPageTitle: 'Next page', firstPageTitle: 'Next page',
lastPageTitle: 'Last page', lastPageTitle: 'Last page',
showTotal: true,
sizePerPageList: [{ sizePerPageList: [{
text: '5', value: 5 text: '5', value: 5
}, { }, {

View File

@@ -0,0 +1,59 @@
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 selectRow = {
mode: 'checkbox',
clickToSelect: true,
selected: [1, 3]
};
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'price',
text: 'Product Price'
}];
const selectRow = {
mode: 'checkbox',
clickToSelect: true,
selected: [1, 3]
};
<BootstrapTable
keyField='id'
data={ products }
columns={ columns }
selectRow={ selectRow }
/>
`;
export default () => (
<div>
<BootstrapTable keyField="id" data={ products } columns={ columns } selectRow={ selectRow } />
<Code>{ sourceCode }</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,144 @@
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import Code from 'components/common/code-block';
import { productsGenerator } from 'utils/common';
const products = productsGenerator();
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name'
}, {
dataField: 'price',
text: 'Product Price'
}];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
class SelectionManagment extends React.Component {
constructor(props) {
super(props);
this.state = { selected: [0, 1] };
}
handleBtnClick = () => {
if (!this.state.selected.includes(2)) {
this.setState(() => ({
selected: [...this.state.selected, 2]
}));
} else {
this.setState(() => ({
selected: this.state.selected.filter(x => x !== 2)
}));
}
}
handleOnSelect = (row, isSelect) => {
if (isSelect) {
this.setState(() => ({
selected: [...this.state.selected, row.id]
}));
} else {
this.setState(() => ({
selected: this.state.selected.filter(x => x !== row.id)
}));
}
}
handleOnSelectAll = (isSelect, rows) => {
const ids = rows.map(r => r.id);
if (isSelect) {
this.setState(() => ({
selected: ids
}));
} else {
this.setState(() => ({
selected: []
}));
}
}
render() {
const selectRow = {
mode: 'checkbox',
clickToSelect: true,
selected: this.state.selected,
onSelect: this.handleOnSelect,
onSelectAll: this.handleOnSelectAll
};
return (
<div>
<button className="btn btn-success" onClick={ this.handleBtnClick }>Select/UnSelect 3rd row</button>
<BootstrapTable keyField="id" data={ products } columns={ columns } selectRow={ selectRow } />
<Code>{ sourceCode }</Code>
</div>
);
}
}
`;
export default class SelectionManagment extends React.Component {
constructor(props) {
super(props);
this.state = { selected: [0, 1] };
}
handleBtnClick = () => {
if (!this.state.selected.includes(2)) {
this.setState(() => ({
selected: [...this.state.selected, 2]
}));
} else {
this.setState(() => ({
selected: this.state.selected.filter(x => x !== 2)
}));
}
}
handleOnSelect = (row, isSelect) => {
if (isSelect) {
this.setState(() => ({
selected: [...this.state.selected, row.id]
}));
} else {
this.setState(() => ({
selected: this.state.selected.filter(x => x !== row.id)
}));
}
}
handleOnSelectAll = (isSelect, rows) => {
const ids = rows.map(r => r.id);
if (isSelect) {
this.setState(() => ({
selected: ids
}));
} else {
this.setState(() => ({
selected: []
}));
}
}
render() {
const selectRow = {
mode: 'checkbox',
clickToSelect: true,
selected: this.state.selected,
onSelect: this.handleOnSelect,
onSelectAll: this.handleOnSelectAll
};
return (
<div>
<button className="btn btn-success" onClick={ this.handleBtnClick }>Select/UnSelect 3rd row</button>
<BootstrapTable keyField="id" data={ products } columns={ columns } selectRow={ selectRow } />
<Code>{ sourceCode }</Code>
</div>
);
}
}

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

@@ -20,8 +20,8 @@ const columns = [{
}]; }];
const rowEvents = { const rowEvents = {
onClick: (e) => { onClick: (e, row, rowIndex) => {
alert('click on row'); alert(`clicked on row with index: ${rowIndex}`);
} }
}; };
@@ -40,8 +40,8 @@ const columns = [{
}]; }];
const rowEvents = { const rowEvents = {
onClick: (e) => { onClick: (e, row, rowIndex) => {
alert('click on row'); alert(\`clicked on row with index: \${rowIndex}\`);
} }
}; };

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

@@ -38,9 +38,28 @@ const columns = [{
<BootstrapTable keyField='id' data={ products } columns={ columns } /> <BootstrapTable keyField='id' data={ products } columns={ columns } />
`; `;
export default () => ( export default class Test extends React.Component {
constructor(props) {
super(props);
this.state = { data: products };
}
handleClick = () => {
this.setState(() => {
const newProducts = productsGenerator(21);
return {
data: newProducts
};
});
}
render() {
return (
<div> <div>
<BootstrapTable keyField="id" data={ products } columns={ columns } /> <button className="btn btn-default" onClick={ this.handleClick }>Change Data</button>
<BootstrapTable keyField="id" data={ this.state.data } columns={ columns } />
<Code>{ sourceCode }</Code> <Code>{ sourceCode }</Code>
</div> </div>
); );
}
}

View File

@@ -0,0 +1,58 @@
/* eslint no-console: 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,
onSort: (field, order) => {
console.log(`Sort Field: ${field}, Sort Order: ${order}`);
}
}, {
dataField: 'price',
text: 'Product Price'
}];
const defaultSorted = [{
dataField: 'name',
order: 'desc'
}];
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,
onSort: (field, order) => {
console.log(....);
}
}, {
dataField: 'price',
text: 'Product Price'
}];
<BootstrapTable keyField='id' data={ products } columns={ columns } />
`;
export default () => (
<div>
<BootstrapTable keyField="id" data={ products } columns={ columns } defaultSorted={ defaultSorted } />
<Code>{ sourceCode }</Code>
</div>
);

View File

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

@@ -9,6 +9,7 @@ import BasicTable from 'examples/basic';
import BorderlessTable from 'examples/basic/borderless-table'; import BorderlessTable from 'examples/basic/borderless-table';
import StripHoverCondensedTable from 'examples/basic/striped-hover-condensed-table'; import StripHoverCondensedTable from 'examples/basic/striped-hover-condensed-table';
import NoDataTable from 'examples/basic/no-data-table'; import NoDataTable from 'examples/basic/no-data-table';
import CustomizedIdClassesTable from 'examples/basic/customized-id-classes';
import CaptionTable from 'examples/basic/caption-table'; import CaptionTable from 'examples/basic/caption-table';
// work on columns // work on columns
@@ -37,12 +38,19 @@ import HeaderColumnAttrsTable from 'examples/header-columns/column-attrs-table';
import TextFilter from 'examples/column-filter/text-filter'; import TextFilter from 'examples/column-filter/text-filter';
import TextFilterWithDefaultValue from 'examples/column-filter/text-filter-default-value'; import TextFilterWithDefaultValue from 'examples/column-filter/text-filter-default-value';
import TextFilterComparator from 'examples/column-filter/text-filter-eq-comparator'; import TextFilterComparator from 'examples/column-filter/text-filter-eq-comparator';
import TextFilterCaseSensitive from 'examples/column-filter/text-filter-caseSensitive';
import CustomTextFilter from 'examples/column-filter/custom-text-filter'; import CustomTextFilter from 'examples/column-filter/custom-text-filter';
import CustomFilterValue from 'examples/column-filter/custom-filter-value'; import CustomFilterValue from 'examples/column-filter/custom-filter-value';
import SelectFilter from 'examples/column-filter/select-filter'; import SelectFilter from 'examples/column-filter/select-filter';
import SelectFilterWithDefaultValue from 'examples/column-filter/select-filter-default-value'; import SelectFilterWithDefaultValue from 'examples/column-filter/select-filter-default-value';
import SelectFilterComparator from 'examples/column-filter/select-filter-like-comparator'; import SelectFilterComparator from 'examples/column-filter/select-filter-like-comparator';
import CustomSelectFilter from 'examples/column-filter/custom-select-filter'; import CustomSelectFilter from 'examples/column-filter/custom-select-filter';
import NumberFilter from 'examples/column-filter/number-filter';
import NumberFilterWithDefaultValue from 'examples/column-filter/number-filter-default-value';
import CustomNumberFilter from 'examples/column-filter/custom-number-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';
// work on rows // work on rows
import RowStyleTable from 'examples/rows/row-style'; import RowStyleTable from 'examples/rows/row-style';
@@ -52,6 +60,8 @@ 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 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';
import HeaderSortingStyleTable from 'examples/sort/header-sorting-style'; import HeaderSortingStyleTable from 'examples/sort/header-sorting-style';
@@ -67,12 +77,22 @@ 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';
import MultipleSelectionTable from 'examples/row-selection/multiple-selection'; import MultipleSelectionTable from 'examples/row-selection/multiple-selection';
import ClickToSelectTable from 'examples/row-selection/click-to-select'; import ClickToSelectTable from 'examples/row-selection/click-to-select';
import DefaultSelectTable from 'examples/row-selection/default-select';
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 NonSelectableRowsTable from 'examples/row-selection/non-selectable-rows'; import NonSelectableRowsTable from 'examples/row-selection/non-selectable-rows';
@@ -115,6 +135,7 @@ storiesOf('Basic Table', module)
.add('striped, hover, condensed table', () => <StripHoverCondensedTable />) .add('striped, hover, condensed table', () => <StripHoverCondensedTable />)
.add('borderless table', () => <BorderlessTable />) .add('borderless table', () => <BorderlessTable />)
.add('Indication For Empty Table', () => <NoDataTable />) .add('Indication For Empty Table', () => <NoDataTable />)
.add('Customized id and class table', () => <CustomizedIdClassesTable />)
.add('Table with caption', () => <CaptionTable />); .add('Table with caption', () => <CaptionTable />);
storiesOf('Work on Columns', module) storiesOf('Work on Columns', module)
@@ -143,13 +164,20 @@ storiesOf('Column Filter', module)
.add('Text Filter', () => <TextFilter />) .add('Text Filter', () => <TextFilter />)
.add('Text Filter with Default Value', () => <TextFilterWithDefaultValue />) .add('Text Filter with Default Value', () => <TextFilterWithDefaultValue />)
.add('Text Filter with Comparator', () => <TextFilterComparator />) .add('Text Filter with Comparator', () => <TextFilterComparator />)
.add('Custom Text Filter', () => <CustomTextFilter />) .add('Text Filter with Case Sensitive', () => <TextFilterCaseSensitive />)
// add another filter type example right here. // add another filter type example right here.
.add('Select Filter', () => <SelectFilter />) .add('Select Filter', () => <SelectFilter />)
.add('Select Filter with Default Value', () => <SelectFilterWithDefaultValue />) .add('Select Filter with Default Value', () => <SelectFilterWithDefaultValue />)
.add('Select Filter with Comparator', () => <SelectFilterComparator />) .add('Select Filter with Comparator', () => <SelectFilterComparator />)
.add('Number Filter', () => <NumberFilter />)
.add('Number Filter with Default Value', () => <NumberFilterWithDefaultValue />)
.add('Custom Text Filter', () => <CustomTextFilter />)
.add('Custom Select Filter', () => <CustomSelectFilter />) .add('Custom Select Filter', () => <CustomSelectFilter />)
.add('Custom Filter Value', () => <CustomFilterValue />); .add('Custom Number Filter', () => <CustomNumberFilter />)
.add('Custom Filter Value', () => <CustomFilterValue />)
.add('Programmatically Text Filter ', () => <ProgrammaticallyTextFilter />)
.add('Programmatically Select Filter ', () => <ProgrammaticallySelectFilter />)
.add('Programmatically Number Filter ', () => <ProgrammaticallyNumberFilter />);
storiesOf('Work on Rows', module) storiesOf('Work on Rows', module)
.add('Customize Row Style', () => <RowStyleTable />) .add('Customize Row Style', () => <RowStyleTable />)
@@ -159,6 +187,8 @@ 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('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 />)
.add('Custom Style on Sorting Header Column', () => <HeaderSortingStyleTable />); .add('Custom Style on Sorting Header Column', () => <HeaderSortingStyleTable />);
@@ -172,14 +202,24 @@ 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 />)
.add('Multiple Selection', () => <MultipleSelectionTable />) .add('Multiple Selection', () => <MultipleSelectionTable />)
.add('Click to Select', () => <ClickToSelectTable />) .add('Click to Select', () => <ClickToSelectTable />)
.add('Default Select', () => <DefaultSelectTable />)
.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('Selection Background Color', () => <SelectionBgColorTable />) .add('Selection Background Color', () => <SelectionBgColorTable />)

View File

@@ -0,0 +1,7 @@
table.foo {
background-color: $grey-500;
}
table#bar {
background-color: $light-blue;
}

View File

@@ -3,6 +3,7 @@
@import "base/github-corner"; @import "base/github-corner";
@import "base/code-block"; @import "base/code-block";
@import "base-table/index";
@import "welcome/index"; @import "welcome/index";
@import "columns/index"; @import "columns/index";
@import "cell-edit/index"; @import "cell-edit/index";

View File

@@ -18,6 +18,7 @@ You can get all types of filters via import and these filters are a factory func
* TextFilter * TextFilter
* SelectFilter * SelectFilter
* NumberFilter
* **Coming soon!** * **Coming soon!**
## Add CSS ## Add CSS
@@ -58,6 +59,7 @@ const priceFilter = textFilter({
className: 'my-custom-text-filter', // custom classname on input className: 'my-custom-text-filter', // custom classname on input
defaultValue: 'test', // default filtering value defaultValue: 'test', // default filtering value
comparator: Comparator.EQ, // default is Comparator.LIKE comparator: Comparator.EQ, // default is Comparator.LIKE
caseSensitive: true, // default is false, and true will only work when comparator is LIKE
style: { ... }, // your custom styles on input style: { ... }, // your custom styles on input
delay: 1000 // how long will trigger filtering after user typing, default is 500 ms delay: 1000 // how long will trigger filtering after user typing, default is 500 ms
}); });
@@ -109,3 +111,42 @@ const qualityFilter = selectFilter({
// omit... // omit...
``` ```
## Number Filter
```js
import filterFactory, { numberFilter } from 'react-bootstrap-table2-filter';
const columns = [..., {
dataField: 'price',
text: 'Product Price',
filter: numberFilter()
}];
<BootstrapTable keyField='id' data={ products } columns={ columns } filter={ filterFactory() } />
```
Numner filter is same as other filter, you can custom the number filter via `numberFilter` factory function:
```js
import filterFactory, { selectFilter, Comparator } from 'react-bootstrap-table2-filter';
// omit...
const numberFilter = numberFilter({
options: [2100, 2103, 2105], // if options defined, will render number select instead of number input
delay: 600, // how long will trigger filtering after user typing, default is 500 ms
placeholder: 'custom placeholder', // placeholder for number input
withoutEmptyComparatorOption: true, // dont render empty option for comparator
withoutEmptyNumberOption: true, // dont render empty option for numner select if it is defined
comparators: [Comparator.EQ, Comparator.GT, Comparator.LT], // Custom the comparators
style: { display: 'inline-grid' }, // custom the style on number filter
className: 'custom-numberfilter-class', // custom the class on number filter
comparatorStyle: { backgroundColor: 'antiquewhite' }, // custom the style on comparator select
comparatorClassName: 'custom-comparator-class', // custom the class on comparator select
numberStyle: { backgroundColor: 'cadetblue', margin: '0px' }, // custom the style on number input/select
numberClassName: 'custom-number-class', // custom the class on ber input/select
defaultValue: { number: 2103, comparator: Comparator.GT } // default value
})
// omit...
```

View File

@@ -1,5 +1,6 @@
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 wrapperFactory from './src/wrapper'; import wrapperFactory from './src/wrapper';
import * as Comparison from './src/comparison'; import * as Comparison from './src/comparison';
@@ -19,3 +20,8 @@ export const selectFilter = (props = {}) => ({
Filter: SelectFilter, Filter: SelectFilter,
props props
}); });
export const numberFilter = (props = {}) => ({
Filter: NumberFilter,
props
});

View File

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

@@ -1,2 +1,7 @@
export const LIKE = 'LIKE'; export const LIKE = 'LIKE';
export const EQ = '='; export const EQ = '=';
export const NE = '!=';
export const GT = '>';
export const GE = '>=';
export const LT = '<';
export const LE = '<=';

View File

@@ -0,0 +1,265 @@
/* eslint react/require-default-props: 0 */
/* eslint no-return-assign: 0 */
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import * as Comparator from '../comparison';
import { FILTER_TYPE, FILTER_DELAY } from '../const';
const legalComparators = [
Comparator.EQ,
Comparator.NE,
Comparator.GT,
Comparator.GE,
Comparator.LT,
Comparator.LE
];
class NumberFilter extends Component {
constructor(props) {
super(props);
this.comparators = props.comparators || legalComparators;
this.timeout = null;
let isSelected = props.defaultValue !== undefined && props.defaultValue.number !== undefined;
if (props.options && isSelected) {
isSelected = props.options.indexOf(props.defaultValue.number) > -1;
}
this.state = { isSelected };
this.onChangeNumber = this.onChangeNumber.bind(this);
this.onChangeNumberSet = this.onChangeNumberSet.bind(this);
this.onChangeComparator = this.onChangeComparator.bind(this);
}
componentDidMount() {
const { column, onFilter, getFilter } = this.props;
const comparator = this.numberFilterComparator.value;
const number = this.numberFilter.value;
if (comparator && 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
});
});
}
}
componentWillUnmount() {
clearTimeout(this.timeout);
}
onChangeNumber(e) {
const { delay, column, onFilter } = this.props;
const comparator = this.numberFilterComparator.value;
if (comparator === '') {
return;
}
if (this.timeout) {
clearTimeout(this.timeout);
}
const filterValue = e.target.value;
this.timeout = setTimeout(() => {
onFilter(column, FILTER_TYPE.NUMBER)({ number: filterValue, comparator });
}, delay);
}
onChangeNumberSet(e) {
const { column, onFilter } = this.props;
const comparator = this.numberFilterComparator.value;
const { value } = e.target;
this.setState(() => ({ isSelected: (value !== '') }));
// if (comparator === '') {
// return;
// }
onFilter(column, FILTER_TYPE.NUMBER)({ number: value, comparator });
}
onChangeComparator(e) {
const { column, onFilter } = this.props;
const value = this.numberFilter.value;
const comparator = e.target.value;
// if (value === '') {
// return;
// }
onFilter(column, FILTER_TYPE.NUMBER)({ number: 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;
}
getNumberOptions() {
const optionTags = [];
const { options, column, withoutEmptyNumberOption } = this.props;
if (!withoutEmptyNumberOption) {
optionTags.push(
<option key="-1" value="">
{ this.props.placeholder || `Select ${column.text}...` }
</option>
);
}
for (let i = 0; i < options.length; i += 1) {
optionTags.push(<option key={ i } value={ options[i] }>{ options[i] }</option>);
}
return optionTags;
}
applyFilter(filterObj) {
const { column, onFilter } = this.props;
const { number, comparator } = filterObj;
this.setState(() => ({ isSelected: (number !== '') }));
this.numberFilterComparator.value = comparator;
this.numberFilter.value = number;
onFilter(column, FILTER_TYPE.NUMBER)({ number, comparator });
}
cleanFiltered() {
const { column, onFilter, defaultValue } = this.props;
const value = defaultValue ? defaultValue.number : '';
const comparator = defaultValue ? defaultValue.comparator : '';
this.setState(() => ({ isSelected: (value !== '') }));
this.numberFilterComparator.value = comparator;
this.numberFilter.value = value;
onFilter(column, FILTER_TYPE.NUMBER)({ number: value, comparator });
}
render() {
const { isSelected } = this.state;
const {
defaultValue,
column,
options,
style,
className,
numberStyle,
numberClassName,
comparatorStyle,
comparatorClassName,
placeholder
} = this.props;
const selectClass = `
select-filter
number-filter-input
form-control
${numberClassName}
${!isSelected ? 'placeholder-selected' : ''}
`;
return (
<div className={ `filter number-filter ${className}` } style={ style }>
<select
ref={ n => this.numberFilterComparator = n }
style={ comparatorStyle }
className={ `number-filter-comparator form-control ${comparatorClassName}` }
onChange={ this.onChangeComparator }
defaultValue={ defaultValue ? defaultValue.comparator : '' }
>
{ this.getComparatorOptions() }
</select>
{
options ?
<select
ref={ n => this.numberFilter = n }
style={ numberStyle }
className={ selectClass }
onChange={ this.onChangeNumberSet }
defaultValue={ defaultValue ? defaultValue.number : '' }
>
{ this.getNumberOptions() }
</select> :
<input
ref={ n => this.numberFilter = n }
type="number"
style={ numberStyle }
className={ `number-filter-input form-control ${numberClassName}` }
placeholder={ placeholder || `Enter ${column.text}...` }
onChange={ this.onChangeNumber }
defaultValue={ defaultValue ? defaultValue.number : '' }
/>
}
</div>
);
}
}
NumberFilter.propTypes = {
onFilter: PropTypes.func.isRequired,
column: PropTypes.object.isRequired,
options: PropTypes.arrayOf(PropTypes.number),
defaultValue: PropTypes.shape({
number: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
comparator: PropTypes.oneOf([...legalComparators, ''])
}),
delay: PropTypes.number,
/* 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(`Number comparator provided is not supported.
Use only ${legalComparators}`);
}
}
},
placeholder: PropTypes.string,
withoutEmptyComparatorOption: PropTypes.bool,
withoutEmptyNumberOption: PropTypes.bool,
style: PropTypes.object,
className: PropTypes.string,
comparatorStyle: PropTypes.object,
comparatorClassName: PropTypes.string,
numberStyle: PropTypes.object,
numberClassName: PropTypes.string,
getFilter: PropTypes.func
};
NumberFilter.defaultProps = {
delay: FILTER_DELAY,
options: undefined,
defaultValue: {
number: undefined,
comparator: ''
},
withoutEmptyComparatorOption: false,
withoutEmptyNumberOption: false,
comparators: legalComparators,
placeholder: undefined,
style: undefined,
className: '',
comparatorStyle: undefined,
comparatorClassName: '',
numberStyle: undefined,
numberClassName: ''
};
export default NumberFilter;

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() {
@@ -89,6 +101,8 @@ class SelectFilter extends Component {
options, options,
comparator, comparator,
withoutEmptyOption, withoutEmptyOption,
caseSensitive,
getFilter,
...rest ...rest
} = this.props; } = this.props;
@@ -119,14 +133,17 @@ SelectFilter.propTypes = {
style: PropTypes.object, style: PropTypes.object,
className: PropTypes.string, className: PropTypes.string,
withoutEmptyOption: PropTypes.bool, withoutEmptyOption: PropTypes.bool,
defaultValue: PropTypes.any defaultValue: PropTypes.any,
caseSensitive: PropTypes.bool,
getFilter: PropTypes.func
}; };
SelectFilter.defaultProps = { SelectFilter.defaultProps = {
defaultValue: '', defaultValue: '',
className: '', className: '',
withoutEmptyOption: false, withoutEmptyOption: false,
comparator: EQ comparator: EQ,
caseSensitive: true
}; };
export default SelectFilter; export default SelectFilter;

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) {
@@ -69,7 +80,18 @@ class TextFilter extends Component {
} }
render() { render() {
const { placeholder, column: { text }, style, className, onFilter, ...rest } = this.props; const {
placeholder,
column: { text },
style,
className,
onFilter,
caseSensitive,
defaultValue,
getFilter,
...rest
} = 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
@@ -95,12 +117,15 @@ TextFilter.propTypes = {
delay: PropTypes.number, delay: PropTypes.number,
placeholder: PropTypes.string, placeholder: PropTypes.string,
style: PropTypes.object, style: PropTypes.object,
className: PropTypes.string className: PropTypes.string,
caseSensitive: PropTypes.bool,
getFilter: PropTypes.func
}; };
TextFilter.defaultProps = { TextFilter.defaultProps = {
delay: FILTER_DELAY, delay: FILTER_DELAY,
defaultValue: '' defaultValue: '',
caseSensitive: false
}; };

View File

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

View File

@@ -1,12 +1,18 @@
/* eslint eqeqeq: 0 */
/* eslint no-console: 0 */
import { FILTER_TYPE } from './const'; import { FILTER_TYPE } from './const';
import { LIKE, EQ } from './comparison'; import { LIKE, EQ, NE, GT, GE, LT, LE } from './comparison';
export const filterByText = _ => ( export const filterByText = _ => (
data, data,
dataField, dataField,
{ filterVal, comparator = LIKE }, { filterVal: userInput = '', comparator = LIKE, caseSensitive },
customFilterValue customFilterValue
) => ) => {
// make sure filter value to be a string
const filterVal = userInput.toString();
return (
data.filter((row) => { data.filter((row) => {
let cell = _.get(row, dataField); let cell = _.get(row, dataField);
if (customFilterValue) { if (customFilterValue) {
@@ -16,15 +22,85 @@ export const filterByText = _ => (
if (comparator === EQ) { if (comparator === EQ) {
return cellStr === filterVal; return cellStr === filterVal;
} }
return cellStr.indexOf(filterVal) > -1; if (caseSensitive) {
}); return cellStr.includes(filterVal);
}
return cellStr.toLocaleUpperCase().indexOf(filterVal.toLocaleUpperCase()) !== -1;
})
);
};
export const filterByNumber = _ => (
data,
dataField,
{ filterVal: { comparator, number } },
customFilterValue
) => (
data.filter((row) => {
if (number === '' || !comparator) return true;
let valid = true;
let cell = _.get(row, dataField);
if (customFilterValue) {
cell = customFilterValue(cell, row);
}
switch (comparator) {
case EQ: {
if (cell != number) {
valid = false;
}
break;
}
case GT: {
if (cell <= number) {
valid = false;
}
break;
}
case GE: {
if (cell < number) {
valid = false;
}
break;
}
case LT: {
if (cell >= number) {
valid = false;
}
break;
}
case LE: {
if (cell > number) {
valid = false;
}
break;
}
case NE: {
if (cell == number) {
valid = false;
}
break;
}
default: {
console.error('Number comparator provided is not supported');
break;
}
}
return valid;
})
);
export const filterFactory = _ => (filterType) => { export const filterFactory = _ => (filterType) => {
let filterFn; let filterFn;
switch (filterType) { switch (filterType) {
case FILTER_TYPE.TEXT: case FILTER_TYPE.TEXT:
case FILTER_TYPE.SELECT:
filterFn = filterByText(_); filterFn = filterByText(_);
break; break;
case FILTER_TYPE.NUMBER:
filterFn = filterByNumber(_);
break;
default: default:
filterFn = filterByText(_); filterFn = filterByText(_);
} }
@@ -38,7 +114,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

@@ -40,7 +40,15 @@ export default (Base, {
} }
} }
onFilter(column, filterVal, filterType) { /**
* filter the table like below:
* onFilter(column, filterType)(filterVal)
* @param {Object} column
* @param {String} filterType
* @param {String} filterVal - user input for filtering.
*/
onFilter(column, filterType) {
return (filterVal) => {
const { store, columns } = this.props; const { store, columns } = this.props;
const currFilters = Object.assign({}, this.state.currFilters); const currFilters = Object.assign({}, this.state.currFilters);
const { dataField, filter } = column; const { dataField, filter } = column;
@@ -49,9 +57,13 @@ export default (Base, {
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 { comparator = (filterType === FILTER_TYPE.SELECT ? EQ : LIKE) } = filter.props; const {
currFilters[dataField] = { filterVal, filterType, comparator }; comparator = (filterType === FILTER_TYPE.SELECT ? EQ : LIKE),
caseSensitive = false
} = filter.props;
currFilters[dataField] = { filterVal, filterType, comparator, caseSensitive };
} }
store.filters = currFilters; store.filters = currFilters;
if (this.isRemoteFiltering() || this.isRemotePagination()) { if (this.isRemoteFiltering() || this.isRemotePagination()) {
@@ -64,6 +76,7 @@ export default (Base, {
store.filteredData = filters(store, columns, _)(currFilters); store.filteredData = filters(store, columns, _)(currFilters);
this.setState(() => ({ currFilters, isDataChanged: true })); this.setState(() => ({ currFilters, isDataChanged: true }));
};
} }
render() { render() {

View File

@@ -3,7 +3,9 @@
} }
.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 .number-filter-input::-webkit-input-placeholder {
color: lightgrey; color: lightgrey;
font-style: italic; font-style: italic;
} }
@@ -12,3 +14,18 @@
color: initial; color: initial;
font-style: initial; font-style: initial;
} }
.react-bootstrap-table > table > thead > tr > th .number-filter {
display: flex;
}
.react-bootstrap-table > table > thead > tr > th .number-filter-input {
margin-left: 5px;
float: left;
width: calc(100% - 67px - 5px);
}
.react-bootstrap-table > table > thead > tr > th .number-filter-comparator {
width: 67px;
float: left;
}

View File

@@ -0,0 +1,348 @@
import 'jsdom-global/register';
import React from 'react';
import sinon from 'sinon';
import { mount } from 'enzyme';
import NumberFilter from '../../src/components/number';
import { FILTER_TYPE } from '../../src/const';
import * as Comparator from '../../src/comparison';
describe('Number Filter', () => {
let wrapper;
// onFilter(x)(y) = filter result
const onFilter = sinon.stub();
const onFilterFirstReturn = sinon.stub();
const column = {
dataField: 'price',
text: 'Product Price'
};
afterEach(() => {
onFilter.reset();
onFilterFirstReturn.reset();
onFilter.returns(onFilterFirstReturn);
});
describe('initialization', () => {
beforeEach(() => {
wrapper = mount(
<NumberFilter onFilter={ onFilter } column={ column } />
);
});
it('should have correct state', () => {
expect(wrapper.state().isSelected).toBeFalsy();
});
it('should rendering component successfully', () => {
expect(wrapper).toHaveLength(1);
expect(wrapper.find('select')).toHaveLength(1);
expect(wrapper.find('input[type="number"]')).toHaveLength(1);
expect(wrapper.find('.number-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(
<NumberFilter onFilter={ onFilter } column={ column } withoutEmptyComparatorOption />
);
});
it('should rendering comparator options correctly', () => {
const select = wrapper.find('select');
expect(select.find('option')).toHaveLength(wrapper.prop('comparators').length);
});
});
describe('when defaultValue.number props is defined', () => {
const number = 203;
beforeEach(() => {
wrapper = mount(
<NumberFilter onFilter={ onFilter } column={ column } defaultValue={ { number } } />
);
});
it('should rendering input successfully', () => {
expect(wrapper).toHaveLength(1);
const input = wrapper.find('input[type="number"]');
expect(input).toHaveLength(1);
expect(input.props().defaultValue).toEqual(number);
});
});
describe('when defaultValue.comparator props is defined', () => {
const comparator = Comparator.EQ;
beforeEach(() => {
wrapper = mount(
<NumberFilter onFilter={ onFilter } column={ column } defaultValue={ { comparator } } />
);
});
it('should rendering comparator select successfully', () => {
expect(wrapper).toHaveLength(1);
const select = wrapper.find('.number-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 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', () => {
const number = 203;
const comparator = Comparator.EQ;
beforeEach(() => {
wrapper = mount(
<NumberFilter
onFilter={ onFilter }
column={ column }
defaultValue={ { number, comparator } }
/>
);
});
it('should have correct state', () => {
expect(wrapper.state().isSelected).toBeTruthy();
});
it('should calling onFilter on componentDidMount', () => {
expect(onFilter.calledOnce).toBeTruthy();
expect(onFilter.calledWith(column, FILTER_TYPE.NUMBER)).toBeTruthy();
expect(onFilterFirstReturn.calledOnce).toBeTruthy();
expect(onFilterFirstReturn.calledWith({ number: `${number}`, comparator })).toBeTruthy();
});
});
describe('when options props is defined', () => {
const options = [2100, 2103, 2105];
beforeEach(() => {
wrapper = mount(
<NumberFilter
onFilter={ onFilter }
column={ column }
options={ options }
/>
);
});
it('should rendering number options instead of number input', () => {
expect(wrapper).toHaveLength(1);
const select = wrapper.find('.select-filter.placeholder-selected');
expect(select).toHaveLength(1);
expect(select.find('option')).toHaveLength(options.length + 1);
});
describe('when withoutEmptyNumberOption props is defined', () => {
beforeEach(() => {
wrapper = mount(
<NumberFilter
onFilter={ onFilter }
column={ column }
options={ options }
withoutEmptyNumberOption
/>
);
});
it('should rendering number options instead of number input', () => {
const select = wrapper.find('.select-filter.placeholder-selected');
expect(select).toHaveLength(1);
expect(select.find('option')).toHaveLength(options.length);
});
});
describe('when defaultValue.number props is defined', () => {
const number = 203;
beforeEach(() => {
wrapper = mount(
<NumberFilter
onFilter={ onFilter }
column={ column }
defaultValue={ { number } }
options={ options }
/>
);
});
it('should rendering number options successfully', () => {
const select = wrapper.find('.select-filter.placeholder-selected');
expect(select).toHaveLength(1);
expect(select.props().defaultValue).toEqual(number);
});
});
describe('when defaultValue.number and defaultValue.comparator props is defined', () => {
const number = options[1];
const comparator = Comparator.EQ;
beforeEach(() => {
wrapper = mount(
<NumberFilter
onFilter={ onFilter }
column={ column }
defaultValue={ { number, comparator } }
options={ options }
/>
);
});
it('should rendering number options successfully', () => {
let select = wrapper.find('.placeholder-selected');
expect(select).toHaveLength(0);
select = wrapper.find('.select-filter');
expect(select).toHaveLength(1);
});
});
});
describe('when style props is defined', () => {
const style = { backgroundColor: 'red' };
beforeEach(() => {
wrapper = mount(
<NumberFilter
onFilter={ onFilter }
column={ column }
style={ style }
/>
);
});
it('should rendering component successfully', () => {
expect(wrapper).toHaveLength(1);
expect(wrapper.find('.number-filter').prop('style')).toEqual(style);
});
});
describe('when numberStyle props is defined', () => {
const numberStyle = { backgroundColor: 'red' };
beforeEach(() => {
wrapper = mount(
<NumberFilter
onFilter={ onFilter }
column={ column }
numberStyle={ numberStyle }
/>
);
});
it('should rendering component successfully', () => {
expect(wrapper).toHaveLength(1);
expect(wrapper.find('.number-filter-input').prop('style')).toEqual(numberStyle);
});
});
describe('when comparatorStyle props is defined', () => {
const comparatorStyle = { backgroundColor: 'red' };
beforeEach(() => {
wrapper = mount(
<NumberFilter
onFilter={ onFilter }
column={ column }
comparatorStyle={ comparatorStyle }
/>
);
});
it('should rendering component successfully', () => {
expect(wrapper).toHaveLength(1);
expect(wrapper.find('select').prop('style')).toEqual(comparatorStyle);
});
});
describe('when className props is defined', () => {
const className = 'test';
beforeEach(() => {
wrapper = mount(
<NumberFilter
onFilter={ onFilter }
column={ column }
className={ className }
/>
);
});
it('should rendering component successfully', () => {
expect(wrapper).toHaveLength(1);
expect(wrapper.hasClass(className)).toBeTruthy();
});
});
describe('when numberClassName props is defined', () => {
const className = 'test';
beforeEach(() => {
wrapper = mount(
<NumberFilter
onFilter={ onFilter }
column={ column }
numberClassName={ className }
/>
);
});
it('should rendering component successfully', () => {
expect(wrapper).toHaveLength(1);
expect(wrapper.find('.number-filter-input').prop('className').indexOf(className) > -1).toBeTruthy();
});
});
describe('when comparatorClassName props is defined', () => {
const className = 'test';
beforeEach(() => {
wrapper = mount(
<NumberFilter
onFilter={ onFilter }
column={ column }
comparatorClassName={ className }
/>
);
});
it('should rendering component successfully', () => {
expect(wrapper).toHaveLength(1);
expect(wrapper.find('select').prop('className').indexOf(className) > -1).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

@@ -4,7 +4,7 @@ import Store from 'react-bootstrap-table-next/src/store';
import { filters } from '../src/filter'; import { filters } from '../src/filter';
import { FILTER_TYPE } from '../src/const'; import { FILTER_TYPE } from '../src/const';
import { LIKE, EQ } from '../src/comparison'; import { LIKE, EQ, GT, GE, LT, LE, NE } from '../src/comparison';
const data = []; const data = [];
for (let i = 0; i < 20; i += 1) { for (let i = 0; i < 20; i += 1) {
@@ -37,11 +37,24 @@ describe('filter', () => {
}]; }];
}); });
describe('text filter', () => { describe('filterByText', () => {
beforeEach(() => { beforeEach(() => {
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 = {
@@ -55,6 +68,20 @@ describe('filter', () => {
}); });
}); });
describe('when caseSensitive is true', () => {
it('should returning correct result', () => {
currFilters.name = {
filterVal: 'NAME',
caseSensitive: true,
filterType: FILTER_TYPE.TEXT
};
const result = filterFn(currFilters);
expect(result).toBeDefined();
expect(result).toHaveLength(0);
});
});
describe(`when default comparator is ${EQ}`, () => { describe(`when default comparator is ${EQ}`, () => {
it('should returning correct result', () => { it('should returning correct result', () => {
currFilters.name = { currFilters.name = {
@@ -91,4 +118,114 @@ describe('filter', () => {
}); });
}); });
}); });
describe('filterByNumber', () => {
beforeEach(() => {
filterFn = filters(store, columns, _);
});
describe('when currFilters.filterVal.comparator is empty', () => {
it('should returning correct result', () => {
currFilters.price = {
filterVal: { comparator: '', number: '203' },
filterType: FILTER_TYPE.NUMBER
};
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.number is empty', () => {
it('should returning correct result', () => {
currFilters.price = {
filterVal: { comparator: EQ, number: '' },
filterType: FILTER_TYPE.NUMBER
};
const result = filterFn(currFilters);
expect(result).toHaveLength(data.length);
});
});
describe(`when currFilters.filterVal.comparator is ${EQ}`, () => {
it('should returning correct result', () => {
currFilters.price = {
filterVal: { comparator: EQ, number: '203' },
filterType: FILTER_TYPE.NUMBER
};
let result = filterFn(currFilters);
expect(result).toHaveLength(1);
currFilters.price.filterVal.number = '0';
result = filterFn(currFilters);
expect(result).toHaveLength(0);
});
});
describe(`when currFilters.filterVal.comparator is ${GT}`, () => {
it('should returning correct result', () => {
currFilters.price = {
filterVal: { comparator: GT, number: '203' },
filterType: FILTER_TYPE.NUMBER
};
const result = filterFn(currFilters);
expect(result).toHaveLength(16);
});
});
describe(`when currFilters.filterVal.comparator is ${GE}`, () => {
it('should returning correct result', () => {
currFilters.price = {
filterVal: { comparator: GE, number: '203' },
filterType: FILTER_TYPE.NUMBER
};
const result = filterFn(currFilters);
expect(result).toHaveLength(17);
});
});
describe(`when currFilters.filterVal.comparator is ${LT}`, () => {
it('should returning correct result', () => {
currFilters.price = {
filterVal: { comparator: LT, number: '203' },
filterType: FILTER_TYPE.NUMBER
};
const result = filterFn(currFilters);
expect(result).toHaveLength(3);
});
});
describe(`when currFilters.filterVal.comparator is ${LE}`, () => {
it('should returning correct result', () => {
currFilters.price = {
filterVal: { comparator: LE, number: '203' },
filterType: FILTER_TYPE.NUMBER
};
const result = filterFn(currFilters);
expect(result).toHaveLength(4);
});
});
describe(`when currFilters.filterVal.comparator is ${NE}`, () => {
it('should returning correct result', () => {
currFilters.price = {
filterVal: { comparator: NE, number: '203' },
filterType: FILTER_TYPE.NUMBER
};
const result = filterFn(currFilters);
expect(result).toHaveLength(19);
});
});
});
}); });

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

@@ -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>
&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) {
@@ -89,13 +90,14 @@ class Pagination extends pageResolver(Component) {
render() { render() {
const { totalPages, lastPage, dropdownOpen: open } = this.state; const { totalPages, lastPage, dropdownOpen: open } = this.state;
const { const {
showTotal,
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 +119,14 @@ class Pagination extends pageResolver(Component) {
/> />
) : null ) : null
} }
{
showTotal ?
<PaginationTotal
from={ from }
to={ to }
dataSize={ this.props.dataSize }
/> : null
}
</div> </div>
<div className={ pageListClass }> <div className={ pageListClass }>
<PaginationList pages={ pages } onPageChange={ this.handleChangePage } /> <PaginationList pages={ pages } onPageChange={ this.handleChangePage } />

View File

@@ -145,6 +145,7 @@ export default (Base, {
alwaysShowAllBtns={ alwaysShowAllBtns } alwaysShowAllBtns={ alwaysShowAllBtns }
hideSizePerPage={ hideSizePerPage } hideSizePerPage={ hideSizePerPage }
hidePageListOnlyOnePage={ hidePageListOnlyOnePage } hidePageListOnlyOnePage={ hidePageListOnlyOnePage }
showTotal={ options.showTotal }
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

@@ -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')).toEqual(undefined);
}); });
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 rendering 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.2", "version": "0.1.10",
"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

@@ -42,6 +42,8 @@ class BootstrapTable extends PropsBaseResolver(Component) {
store, store,
columns, columns,
keyField, keyField,
id,
classes,
striped, striped,
hover, hover,
bordered, bordered,
@@ -58,7 +60,7 @@ class BootstrapTable extends PropsBaseResolver(Component) {
'table-hover': hover, 'table-hover': hover,
'table-bordered': bordered, 'table-bordered': bordered,
'table-condensed': condensed 'table-condensed': condensed
}); }, classes);
const cellSelectionInfo = this.resolveSelectRowProps({ const cellSelectionInfo = this.resolveSelectRowProps({
onRowSelect: this.props.onRowSelect onRowSelect: this.props.onRowSelect
@@ -70,10 +72,12 @@ class BootstrapTable extends PropsBaseResolver(Component) {
allRowsSelected: isSelectedAll(store) allRowsSelected: isSelectedAll(store)
}); });
const tableCaption = (caption && <Caption>{ caption }</Caption>);
return ( return (
<div className="react-bootstrap-table"> <div className="react-bootstrap-table">
<table className={ tableClass }> <table id={ id } className={ tableClass }>
<Caption>{ caption }</Caption> { tableCaption }
<Header <Header
columns={ columns } columns={ columns }
sortField={ store.sortField } sortField={ store.sortField }
@@ -114,6 +118,8 @@ BootstrapTable.propTypes = {
striped: PropTypes.bool, striped: PropTypes.bool,
bordered: PropTypes.bool, bordered: PropTypes.bool,
hover: PropTypes.bool, hover: PropTypes.bool,
id: PropTypes.string,
classes: PropTypes.string,
condensed: PropTypes.bool, condensed: PropTypes.bool,
caption: PropTypes.oneOfType([ caption: PropTypes.oneOfType([
PropTypes.node, PropTypes.node,
@@ -143,6 +149,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

@@ -39,7 +39,6 @@ class Cell extends Component {
} = this.props; } = this.props;
const { const {
dataField, dataField,
hidden,
formatter, formatter,
formatExtraData, formatExtraData,
style, style,
@@ -80,10 +79,6 @@ class Cell extends Component {
_.isFunction(align) ? align(content, row, rowIndex, columnIndex) : align; _.isFunction(align) ? align(content, row, rowIndex, columnIndex) : align;
} }
if (hidden) {
cellStyle.display = 'none';
}
if (cellClasses) cellAttrs.className = cellClasses; if (cellClasses) cellAttrs.className = cellClasses;
if (!_.isEmptyObject(cellStyle)) cellAttrs.style = cellStyle; if (!_.isEmptyObject(cellStyle)) cellAttrs.style = cellStyle;

View File

@@ -24,7 +24,6 @@ const HeaderCell = (props) => {
text, text,
sort, sort,
filter, filter,
hidden,
headerTitle, headerTitle,
headerAlign, headerAlign,
headerFormatter, headerFormatter,
@@ -58,10 +57,6 @@ const HeaderCell = (props) => {
cellStyle.textAlign = _.isFunction(headerAlign) ? headerAlign(column, index) : headerAlign; cellStyle.textAlign = _.isFunction(headerAlign) ? headerAlign(column, index) : headerAlign;
} }
if (hidden) {
cellStyle.display = 'none';
}
if (sort) { if (sort) {
const customClick = cellAttrs.onClick; const customClick = cellAttrs.onClick;
cellAttrs.onClick = (e) => { cellAttrs.onClick = (e) => {
@@ -131,9 +126,14 @@ HeaderCell.propTypes = {
attrs: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), attrs: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
sort: PropTypes.bool, sort: PropTypes.bool,
sortFunc: PropTypes.func, sortFunc: 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

@@ -27,6 +27,7 @@ const Header = (props) => {
} }
{ {
columns.map((column, i) => { columns.map((column, i) => {
if (!column.hidden) {
const currSort = column.dataField === sortField; const currSort = column.dataField === sortField;
const isLastSorting = column.dataField === sortField; const isLastSorting = column.dataField === sortField;
@@ -41,6 +42,8 @@ const Header = (props) => {
sortOrder={ sortOrder } sortOrder={ sortOrder }
isLastSorting={ isLastSorting } isLastSorting={ isLastSorting }
/>); />);
}
return false;
}) })
} }
</tr> </tr>

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

@@ -0,0 +1,85 @@
import _ from './utils';
const events = [
'onClick',
'onDoubleClick',
'onMouseEnter',
'onMouseLeave'
];
export default ExtendBase =>
class RowEventDelegater extends ExtendBase {
constructor(props) {
super(props);
this.clickNum = 0;
this.createDefaultEventHandler = this.createDefaultEventHandler.bind(this);
this.createClickEventHandler = this.createClickEventHandler.bind(this);
}
createDefaultEventHandler(cb) {
return (e) => {
const { row, rowIndex } = this.props;
cb(e, row, rowIndex);
};
}
createClickEventHandler(cb) {
return (e) => {
const {
row,
selected,
keyField,
selectable,
rowIndex,
selectRow: {
onRowSelect,
clickToEdit
},
cellEdit: {
mode,
DBCLICK_TO_CELL_EDIT,
DELAY_FOR_DBCLICK
}
} = this.props;
const clickFn = () => {
if (cb) {
cb(e, row, rowIndex);
}
if (selectable) {
const key = _.get(row, keyField);
onRowSelect(key, !selected, rowIndex, e);
}
};
if (mode === DBCLICK_TO_CELL_EDIT && clickToEdit) {
this.clickNum += 1;
_.debounce(() => {
if (this.clickNum === 1) {
clickFn();
}
this.clickNum = 0;
}, DELAY_FOR_DBCLICK)();
} else {
clickFn();
}
};
}
delegate(attrs = {}) {
const newAttrs = {};
if (this.props.selectRow && this.props.selectRow.clickToSelect) {
newAttrs.onClick = this.createClickEventHandler(attrs.onClick);
}
Object.keys(attrs).forEach((attr) => {
if (!newAttrs[attr]) {
if (events.includes(attr)) {
newAttrs[attr] = this.createDefaultEventHandler(attrs[attr]);
} else {
newAttrs[attr] = attrs[attr];
}
}
});
return newAttrs;
}
};

View File

@@ -28,7 +28,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,7 +46,7 @@ export default class SelectionCell extends Component {
? true ? true
: !selected; : !selected;
onRowSelect(rowKey, checked, rowIndex); onRowSelect(rowKey, checked, rowIndex, e);
} }
render() { render() {

View File

@@ -44,10 +44,10 @@ 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() {

View File

@@ -1,3 +1,4 @@
/* eslint no-param-reassign: 0 */
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@@ -21,17 +22,26 @@ export default Base =>
super(props); super(props);
this.handleRowSelect = this.handleRowSelect.bind(this); this.handleRowSelect = this.handleRowSelect.bind(this);
this.handleAllRowsSelect = this.handleAllRowsSelect.bind(this); this.handleAllRowsSelect = this.handleAllRowsSelect.bind(this);
props.store.selected = props.selectRow.selected || [];
this.state = { this.state = {
selectedRowKeys: props.store.selected selectedRowKeys: props.store.selected
}; };
} }
componentWillReceiveProps(nextProps) {
nextProps.store.selected = nextProps.selectRow.selected || [];
this.setState(() => ({
selectedRowKeys: nextProps.store.selected
}));
}
/** /**
* row selection handler * row selection handler
* @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;
@@ -49,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(() => ({
@@ -58,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) :
@@ -79,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

@@ -6,57 +6,10 @@ import PropTypes from 'prop-types';
import _ from './utils'; import _ from './utils';
import Cell from './cell'; import Cell from './cell';
import SelectionCell from './row-selection/selection-cell'; import SelectionCell from './row-selection/selection-cell';
import eventDelegater from './row-event-delegater';
import Const from './const'; import Const from './const';
class Row extends Component { class Row extends eventDelegater(Component) {
constructor(props) {
super(props);
this.clickNum = 0;
this.handleRowClick = this.handleRowClick.bind(this);
}
handleRowClick(e) {
const {
row,
selected,
keyField,
selectable,
rowIndex,
selectRow: {
onRowSelect,
clickToEdit
},
cellEdit: {
mode,
DBCLICK_TO_CELL_EDIT,
DELAY_FOR_DBCLICK
},
attrs
} = this.props;
const clickFn = () => {
if (attrs.onClick) {
attrs.onClick(e);
}
if (selectable) {
const key = _.get(row, keyField);
onRowSelect(key, !selected, rowIndex);
}
};
if (mode === DBCLICK_TO_CELL_EDIT && clickToEdit) {
this.clickNum += 1;
_.debounce(() => {
if (this.clickNum === 1) {
clickFn();
}
this.clickNum = 0;
}, DELAY_FOR_DBCLICK)();
} else {
clickFn();
}
}
render() { render() {
const { const {
row, row,
@@ -85,12 +38,8 @@ class Row extends Component {
} = cellEdit; } = cellEdit;
const key = _.get(row, keyField); const key = _.get(row, keyField);
const { clickToSelect, hideSelectColumn } = selectRow; const { hideSelectColumn } = selectRow;
const trAttrs = this.delegate(attrs);
const trAttrs = { ...attrs };
if (clickToSelect) {
trAttrs.onClick = this.handleRowClick;
}
return ( return (
<tr style={ style } className={ className } { ...trAttrs }> <tr style={ style } className={ className } { ...trAttrs }>
@@ -109,6 +58,7 @@ class Row extends Component {
} }
{ {
columns.map((column, index) => { columns.map((column, index) => {
if (!column.hidden) {
const { dataField } = column; const { dataField } = column;
const content = _.get(row, dataField); const content = _.get(row, dataField);
let editable = _.isDefined(column.editable) ? column.editable : true; let editable = _.isDefined(column.editable) ? column.editable : true;
@@ -129,7 +79,9 @@ class Row extends 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 }
@@ -149,6 +101,8 @@ class Row extends Component {
dbclickToEdit={ mode === DBCLICK_TO_CELL_EDIT } dbclickToEdit={ mode === DBCLICK_TO_CELL_EDIT }
/> />
); );
}
return false;
}) })
} }
</tr> </tr>

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,11 @@ 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) {
column[0].onSort(store.sortField, store.sortOrder);
}
if (this.isRemoteSort() || this.isRemotePagination()) { if (this.isRemoteSort() || this.isRemotePagination()) {
this.handleSortChange(); this.handleSortChange();
@@ -35,18 +39,25 @@ export default Base =>
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if (nextProps.isDataChanged) { let sortedColumn;
const sortedColumn = nextProps.columns.find( for (let i = 0; i < nextProps.columns.length; i += 1) {
column => column.dataField === nextProps.store.sortField); if (nextProps.columns[i].dataField === nextProps.store.sortField) {
if (sortedColumn) { sortedColumn = nextProps.columns[i];
nextProps.store.sortBy(sortedColumn); 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) {
column.onSort(store.sortField, store.sortOrder);
}
if (this.isRemoteSort() || this.isRemotePagination()) { if (this.isRemoteSort() || this.isRemotePagination()) {
this.handleSortChange(); this.handleSortChange();

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

@@ -46,8 +46,50 @@ describe('BootstrapTable', () => {
expect(wrapper.state().data).toEqual(store.data); expect(wrapper.state().data).toEqual(store.data);
}); });
it('should have table-bordered class as default', () => { it("should only have classes 'table' and 'table-bordered' as default", () => {
expect(wrapper.find('table.table-bordered').length).toBe(1); expect(wrapper.find('table').prop('className')).toBe('table table-bordered');
});
it('should not have customized id as default', () => {
expect(wrapper.find('table').prop('id')).toBeUndefined();
});
});
describe('when props.classes was defined', () => {
const classes = 'foo';
beforeEach(() => {
wrapper = shallow(
<BootstrapTable
keyField="id"
columns={ columns }
data={ data }
store={ store }
classes={ classes }
/>);
});
it('should display customized classes correctly', () => {
expect(wrapper.find(`table.${classes}`).length).toBe(1);
});
});
describe('when props.id was defined', () => {
const id = 'foo';
beforeEach(() => {
wrapper = shallow(
<BootstrapTable
keyField="id"
columns={ columns }
data={ data }
store={ store }
id={ id }
/>);
});
it('should display customized id correctly', () => {
expect(wrapper.find(`table#${id}`).length).toBe(1);
}); });
}); });

View File

@@ -27,24 +27,6 @@ describe('Cell', () => {
}); });
}); });
describe('when column.hidden prop is true', () => {
const column = {
dataField: 'id',
text: 'ID',
hidden: true
};
beforeEach(() => {
wrapper = shallow(<Cell row={ row } columnIndex={ 1 } rowIndex={ 1 } column={ column } />);
});
it('should have \'none\' value for style.display', () => {
const style = wrapper.find('td').prop('style');
expect(style).toBeDefined();
expect(style.display).toEqual('none');
});
});
describe('when column.formatter prop is defined', () => { describe('when column.formatter prop is defined', () => {
const rowIndex = 1; const rowIndex = 1;
const column = { const column = {
@@ -390,20 +372,6 @@ describe('Cell', () => {
}); });
}); });
describe('when column.hidden prop is defined', () => {
it('attrs.style.hidden should be overwrited', () => {
column.hidden = true;
column.attrs = { style: { hidden: true } };
wrapper = shallow(
<Cell row={ row } columnIndex={ columnIndex } rowIndex={ 1 } column={ column } />);
const style = wrapper.find('td').prop('style');
expect(style).toBeDefined();
expect(style.display).toEqual('none');
});
});
describe('when column.align prop is defined', () => { describe('when column.align prop is defined', () => {
it('attrs.style.textAlign should be overwrited', () => { it('attrs.style.textAlign should be overwrited', () => {
column.align = 'center'; column.align = 'center';

View File

@@ -33,24 +33,6 @@ describe('HeaderCell', () => {
}); });
}); });
describe('when column.hidden props is true', () => {
const column = {
dataField: 'id',
text: 'ID',
hidden: true
};
beforeEach(() => {
wrapper = shallow(<HeaderCell column={ column } index={ index } />);
});
it('should have \'none\' value for style.display', () => {
const style = wrapper.find('th').prop('style');
expect(style).toBeDefined();
expect(style.display).toEqual('none');
});
});
describe('when column.headerTitle prop is defined', () => { describe('when column.headerTitle prop is defined', () => {
let column; let column;
beforeEach(() => { beforeEach(() => {

View File

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

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

@@ -8,6 +8,7 @@ import wrapperFactory from '../../src/row-selection/wrapper';
describe('RowSelectionWrapper', () => { describe('RowSelectionWrapper', () => {
let wrapper; let wrapper;
let selectRow;
const columns = [{ const columns = [{
dataField: 'id', dataField: 'id',
@@ -25,10 +26,6 @@ describe('RowSelectionWrapper', () => {
name: 'B' name: 'B'
}]; }];
const selectRow = {
mode: 'radio'
};
const rowIndex = 1; const rowIndex = 1;
const keyField = 'id'; const keyField = 'id';
@@ -38,6 +35,9 @@ describe('RowSelectionWrapper', () => {
const RowSelectionWrapper = wrapperFactory(BootstrapTable); const RowSelectionWrapper = wrapperFactory(BootstrapTable);
beforeEach(() => { beforeEach(() => {
selectRow = {
mode: 'radio'
};
wrapper = shallow( wrapper = shallow(
<RowSelectionWrapper <RowSelectionWrapper
keyField={ keyField } keyField={ keyField }
@@ -54,6 +54,10 @@ describe('RowSelectionWrapper', () => {
expect(wrapper.find(BootstrapTable)).toBeDefined(); expect(wrapper.find(BootstrapTable)).toBeDefined();
}); });
it('should have correct store.selected value', () => {
expect(store.selected).toEqual([]);
});
it('should have correct state', () => { it('should have correct state', () => {
expect(wrapper.state().selectedRowKeys).toBeDefined(); expect(wrapper.state().selectedRowKeys).toBeDefined();
expect(wrapper.state().selectedRowKeys.length).toEqual(0); expect(wrapper.state().selectedRowKeys.length).toEqual(0);
@@ -64,11 +68,54 @@ describe('RowSelectionWrapper', () => {
expect(wrapper.props().onAllRowsSelect).toBeDefined(); expect(wrapper.props().onAllRowsSelect).toBeDefined();
}); });
describe('componentWillReceiveProps', () => {
const nextSelected = [0];
const nextProps = {
store: {
selected: nextSelected
},
selectRow: {
mode: 'checkbox',
selected: nextSelected
}
};
it('should update state.selectedRowKeys with next selected rows', () => {
wrapper.instance().componentWillReceiveProps(nextProps);
expect(nextProps.store.selected).toEqual(nextSelected);
expect(wrapper.state('selectedRowKeys')).toEqual(nextSelected);
});
});
describe('when selectRow.selected is defined', () => {
beforeEach(() => {
selectRow.mode = 'checkbox';
selectRow.selected = [1, 3];
wrapper = shallow(
<RowSelectionWrapper
keyField={ keyField }
data={ data }
columns={ columns }
selectRow={ selectRow }
store={ store }
/>
);
});
it('should have correct store.selected value', () => {
expect(store.selected).toEqual(selectRow.selected);
});
it('should have correct state', () => {
expect(wrapper.state().selectedRowKeys).toEqual(selectRow.selected);
});
});
describe('when selectRow.mode is \'radio\'', () => { describe('when selectRow.mode is \'radio\'', () => {
const firstSelectedRow = data[0][keyField]; const firstSelectedRow = data[0][keyField];
const secondSelectedRow = data[1][keyField]; const secondSelectedRow = data[1][keyField];
it('call handleRowSelect function should seting correct state.selectedRowKeys', () => { it('call handleRowSelect function should setting correct state.selectedRowKeys', () => {
wrapper.instance().handleRowSelect(firstSelectedRow, rowIndex); wrapper.instance().handleRowSelect(firstSelectedRow, rowIndex);
expect(wrapper.state('selectedRowKeys')).toEqual([firstSelectedRow]); expect(wrapper.state('selectedRowKeys')).toEqual([firstSelectedRow]);
@@ -94,7 +141,7 @@ describe('RowSelectionWrapper', () => {
); );
}); });
it('call handleRowSelect function should seting correct state.selectedRowKeys', () => { it('call handleRowSelect function should setting correct state.selectedRowKeys', () => {
wrapper.instance().handleRowSelect(firstSelectedRow, true, rowIndex); wrapper.instance().handleRowSelect(firstSelectedRow, true, rowIndex);
expect(wrapper.state('selectedRowKeys')).toEqual(expect.arrayContaining([firstSelectedRow])); expect(wrapper.state('selectedRowKeys')).toEqual(expect.arrayContaining([firstSelectedRow]));
@@ -108,21 +155,13 @@ describe('RowSelectionWrapper', () => {
expect(wrapper.state('selectedRowKeys')).toEqual([]); expect(wrapper.state('selectedRowKeys')).toEqual([]);
}); });
it('call handleAllRowsSelect function should seting correct state.selectedRowKeys', () => { it('call handleAllRowsSelect function should setting correct state.selectedRowKeys', () => {
wrapper.instance().handleAllRowsSelect(); wrapper.instance().handleAllRowsSelect();
expect(wrapper.state('selectedRowKeys')).toEqual(expect.arrayContaining([firstSelectedRow, secondSelectedRow])); expect(wrapper.state('selectedRowKeys')).toEqual(expect.arrayContaining([firstSelectedRow, secondSelectedRow]));
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 seting 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', () => {
@@ -172,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

@@ -502,6 +502,32 @@ describe('Row', () => {
}); });
}); });
describe('when cloumn.hidden is true', () => {
beforeEach(() => {
const newColumns = [{
dataField: 'id',
text: 'ID',
hidden: true
}, {
dataField: 'name',
text: 'Name'
}, {
dataField: 'price',
text: 'Price'
}];
wrapper = shallow(
<Row
{ ...mockBodyResolvedProps }
rowIndex={ rowIndex }
columns={ newColumns }
row={ row }
/>);
});
it('should not render column with hidden value true', () => {
expect(wrapper.find(Cell).length).toBe(2);
});
});
describe('selectRow', () => { describe('selectRow', () => {
let selectRow; let selectRow;
@@ -799,8 +825,10 @@ describe('Row', () => {
selected selected
selectable selectable
/>); />);
wrapper.instance().handleRowClick(); // console.log(wrapper.instance());
wrapper.instance().handleRowClick(); const rowClick = wrapper.instance().createClickEventHandler();
rowClick();
rowClick();
}); });
it('should increase clickNum as 2', () => { it('should increase clickNum as 2', () => {

View File

@@ -10,16 +10,7 @@ import wrapperFactory from '../../src/sort/wrapper';
describe('SortWrapper', () => { describe('SortWrapper', () => {
let wrapper; let wrapper;
let columns;
const columns = [{
dataField: 'id',
text: 'ID',
sort: true
}, {
dataField: 'name',
text: 'Name',
sort: true
}];
const data = [{ const data = [{
id: 1, id: 1,
@@ -37,6 +28,15 @@ describe('SortWrapper', () => {
const SortWrapper = wrapperFactory(BootstrapTable); const SortWrapper = wrapperFactory(BootstrapTable);
beforeEach(() => { beforeEach(() => {
columns = [{
dataField: 'id',
text: 'ID',
sort: true
}, {
dataField: 'name',
text: 'Name',
sort: true
}];
wrapper = shallow( wrapper = shallow(
<SortWrapper <SortWrapper
keyField={ keyField } keyField={ keyField }
@@ -58,9 +58,10 @@ describe('SortWrapper', () => {
describe('call handleSort function', () => { describe('call handleSort function', () => {
let sortBySpy; let sortBySpy;
const sortColumn = columns[0]; let sortColumn;
beforeEach(() => { beforeEach(() => {
sortColumn = columns[0];
store = new Store(keyField); store = new Store(keyField);
store.data = data; store.data = data;
sortBySpy = sinon.spy(store, 'sortBy'); sortBySpy = sinon.spy(store, 'sortBy');
@@ -130,6 +131,32 @@ describe('SortWrapper', () => {
expect(onTableChangeCB.calledOnce).toBeTruthy(); expect(onTableChangeCB.calledOnce).toBeTruthy();
}); });
}); });
describe('when column.onSort prop is defined', () => {
const onSortCB = jest.fn();
beforeEach(() => {
columns[0].onSort = onSortCB;
wrapper = shallow(
<SortWrapper
keyField={ keyField }
data={ data }
columns={ columns }
store={ store }
/>
);
wrapper.instance().handleSort(sortColumn);
});
it('should calling column.onSort function correctly', () => {
expect(onSortCB).toHaveBeenCalledTimes(1);
expect(onSortCB).toHaveBeenCalledWith(columns[0].dataField, Const.SORT_DESC);
wrapper.instance().handleSort(sortColumn);
expect(onSortCB).toHaveBeenCalledTimes(2);
expect(onSortCB).toHaveBeenCalledWith(columns[0].dataField, Const.SORT_ASC);
});
});
}); });
describe('when defaultSorted prop is defined', () => { describe('when defaultSorted prop is defined', () => {
@@ -161,6 +188,28 @@ describe('SortWrapper', () => {
it('should update store.sortOrder correctly', () => { it('should update store.sortOrder correctly', () => {
expect(store.sortOrder).toEqual(defaultSorted[0].order); expect(store.sortOrder).toEqual(defaultSorted[0].order);
}); });
describe('when column.onSort prop is defined', () => {
const onSortCB = jest.fn();
beforeEach(() => {
columns[1].onSort = onSortCB;
wrapper = shallow(
<SortWrapper
keyField={ keyField }
data={ data }
columns={ columns }
store={ store }
defaultSorted={ defaultSorted }
/>
);
});
it('should calling column.onSort function correctly', () => {
expect(onSortCB).toHaveBeenCalledTimes(1);
expect(onSortCB).toHaveBeenCalledWith(defaultSorted[0].dataField, defaultSorted[0].order);
});
});
}); });
describe('componentWillReceiveProps', () => { describe('componentWillReceiveProps', () => {
@@ -170,11 +219,6 @@ describe('SortWrapper', () => {
nextProps = { columns, store }; nextProps = { columns, store };
store.sortField = columns[1].dataField; store.sortField = columns[1].dataField;
store.sortOrder = Const.SORT_DESC; store.sortOrder = Const.SORT_DESC;
});
describe('if nextProps.isDataChanged is true', () => {
beforeEach(() => {
nextProps.isDataChanged = true;
store.sortBy = sinon.stub(); store.sortBy = sinon.stub();
}); });
@@ -183,17 +227,4 @@ describe('SortWrapper', () => {
expect(store.sortBy.calledOnce).toBeTruthy(); expect(store.sortBy.calledOnce).toBeTruthy();
}); });
}); });
describe('if nextProps.isDataChanged is false', () => {
beforeEach(() => {
nextProps.isDataChanged = false;
store.sortBy = sinon.stub();
});
it('should not sorting', () => {
wrapper.instance().componentWillReceiveProps(nextProps);
expect(store.sortBy.calledOnce).toBeFalsy();
});
});
});
}); });

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;

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