Compare commits

..

70 Commits

Author SHA1 Message Date
AllenFang
7d7688582b Publish
- react-bootstrap-table2-filter@0.3.1
2018-07-30 23:27:23 +08:00
Allen
e26065b116 Merge pull request #440 from react-bootstrap-table/develop
20180730 release
2018-07-30 23:16:26 +08:00
Benny Johnson
6522f6d964 Commented out a check which caused the date filter to not update the (#425)
table when the date or date comparator were cleared

Modified applyFilter in date.js so that it doesn't try to parse an
Invalid Date. It was parsing an empty string, which caused it to pass
through an invalid date to onFilter, and this wasn't being checked for
properly by the onFilter function. It now checks for the empty string
and passes in null, which is what the onFilter function was actually
checking for
2018-07-25 11:20:35 +08:00
Allen
ecaf439e66 Merge pull request #420 from nthgol/patch-1
import numberFilter in numberFilter example
2018-07-19 10:15:57 +08:00
Nathan
90d03676ad import numberFilter in numberFilter example 2018-07-16 18:06:51 -04:00
AllenFang
2585a62697 Publish
- react-bootstrap-table2-example@0.1.11
 - react-bootstrap-table2-paginator@0.1.6
 - react-bootstrap-table-next@0.1.15
2018-07-15 16:08:59 +08:00
Allen
6afe58a081 Merge pull request #415 from react-bootstrap-table/develop
20180715 release
2018-07-15 16:02:47 +08:00
Allen
6f5bd1a13d fix #394 (#414) 2018-07-15 14:43:29 +08:00
Allen
85a9ab72af fix #402 (#412) 2018-07-15 14:18:21 +08:00
AllenFang
258ea43225 Publish
- react-bootstrap-table2-example@0.1.10
 - react-bootstrap-table2-filter@0.3.0
 - react-bootstrap-table2-paginator@0.1.5
 - react-bootstrap-table-next@0.1.14
2018-06-24 22:42:50 +08:00
Allen
7a7b708029 Merge pull request #390 from react-bootstrap-table/develop
20180624 release
2018-06-24 22:40:15 +08:00
Allen
0cf89861af Merge pull request #389 from react-bootstrap-table/feat/custom-filter
Implement custom filter
2018-06-24 15:33:22 +08:00
AllenFang
eb74625835 patch docs for custom filter 2018-06-24 15:15:57 +08:00
AllenFang
cbaec4c655 add stories for custom filter 2018-06-24 15:04:59 +08:00
AllenFang
04c21cb63d implement custom filter 2018-06-24 15:03:46 +08:00
AllenFang
7d72002b6e fix wrong eslint rule 2018-06-24 13:16:48 +08:00
AllenFang
279cc25da0 Merge branch 'develop' of https://github.com/react-bootstrap-table/react-bootstrap-table2 into develop 2018-06-24 13:16:07 +08:00
AllenFang
1152bb8440 fix #380 2018-06-23 13:49:38 +08:00
AllenFang
88befb8136 fix #380 2018-06-23 13:47:53 +08:00
Allen
42c6bc0337 Merge pull request #371 from sean-ww/fix/remote-filtered-pagination
Prevent remote pagination from setting the page incorrectly
2018-06-23 12:59:02 +08:00
sean
6e753bb955 Prevent remote pagination from setting the page incorrectly 2018-06-06 15:11:54 +02:00
AllenFang
64df3e1fae Publish
- react-bootstrap-table2-example@0.1.9
 - react-bootstrap-table2-filter@0.2.0
 - react-bootstrap-table2-overlay@0.1.2
 - react-bootstrap-table2-paginator@0.1.4
 - react-bootstrap-table-next@0.1.13
2018-06-04 13:53:02 +08:00
Allen
5cdd1ad093 20180604 release #366 from react-bootstrap-table/develop
20180604 release
2018-06-04 13:49:03 +08:00
AllenFang
36e754b6bc patch docs 2018-06-03 22:58:03 +08:00
AllenFang
6730dcf60d fix overlay test bugs 2018-06-03 22:52:15 +08:00
AllenFang
fb54809dc9 fix docs 2018-06-03 21:36:09 +08:00
AllenFang
d43c622fdb fix docs 2018-06-03 21:31:13 +08:00
AllenFang
7253d7a1d7 fix overlay tests 2018-06-03 20:40:31 +08:00
AllenFang
a6daa50417 improve overlay wrapping 2018-06-03 15:50:10 +08:00
AllenFang
b11019ce20 fix #358 2018-06-03 14:49:58 +08:00
Allen
dda47f7b7d Merge pull request #365 from react-bootstrap-table/feat/349
Feat/349
2018-06-03 14:39:22 +08:00
AllenFang
4da8ba7ecc patch docs for date filter 2018-06-03 14:24:20 +08:00
Allen
2ff0b27747 Merge pull request #359 from sean-ww/feature/pagination-total
Adding custom pagination total
2018-06-03 14:18:50 +08:00
AllenFang
c3f279fb0c patch tests for date filter 2018-06-03 14:04:06 +08:00
AllenFang
06bcf1edca add stories for date filter 2018-06-03 14:03:38 +08:00
AllenFang
fc1f75cfac implement date filter 2018-06-03 14:02:39 +08:00
sean
1cf12ab707 paginationTotal renamed to paginationTotalRenderer 2018-06-02 10:43:56 +02:00
Allen
288ccc1049 Merge pull request #364 from react-bootstrap-table/feat/351
Fix #351
2018-06-02 15:37:51 +08:00
AllenFang
f13c139f63 patch docs for custom selection 2018-06-02 15:27:10 +08:00
AllenFang
e72ad0586e add story for custom selection column 2018-06-02 15:20:57 +08:00
AllenFang
c2044fe8b5 patch test for selection box 2018-06-02 15:20:40 +08:00
AllenFang
a7b3690a7c custom selection box 2018-06-02 15:20:19 +08:00
Amol Udage
68afc348db fixes sorting issue (#354)
+ when remote sort is true then disable client side sorting
2018-06-02 13:21:25 +08:00
sean-ww88
5404124a78 Added missing commas on the custom pagination example 2018-05-30 17:35:27 +02:00
sean-ww88
d592871c0d Adding custom pagination total 2018-05-30 17:29:58 +02:00
AllenFang
6019e550fd Merge branch 'develop' of https://github.com/react-bootstrap-table/react-bootstrap-table2 into develop 2018-05-28 18:07:31 +08:00
AllenFang
765a49fb07 Publish
- react-bootstrap-table-next@0.1.12
2018-05-23 22:44:40 +08:00
Allen
fe2fd93c20 fix the bool rendering issues in React (#340) (#348) 2018-05-23 22:42:46 +08:00
Allen
19ba336e32 gix the bool rendering issues in React (#340) 2018-05-19 13:07:57 +08:00
AllenFang
a50148fe85 Publish
- react-bootstrap-table2-example@0.1.8
 - react-bootstrap-table2-filter@0.1.7
 - react-bootstrap-table-next@0.1.11
2018-05-14 23:09:49 +08:00
Allen
c96156503f Merge pull request #336 from react-bootstrap-table/develop
20180513 release
2018-05-14 23:02:15 +08:00
AllenFang
ed2ba2a5c5 fix #334 2018-05-14 22:44:28 +08:00
AllenFang
f87fe3e544 patch docs, example and test for wrapperClasses 2018-05-12 13:54:40 +08:00
MikeSha
43e73313e6 implement wrapperClasses(#325) 2018-05-12 13:40:24 +08:00
Allen
888aa1d08b fix #303 (#330) 2018-05-10 14:07:24 +08:00
AllenFang
028834da8b add release command 2018-05-08 22:33:46 +08:00
AllenFang
8f3b989b00 Publish
- react-bootstrap-table2-editor@0.2.1
2018-05-08 22:23:23 +08:00
AllenFang
fe8761427d Publish
- react-bootstrap-table2-paginator@0.1.3
2018-05-08 22:22:47 +08:00
AllenFang
27a09de008 Publish
- react-bootstrap-table-next@0.1.10
2018-05-08 22:22:10 +08:00
AllenFang
20ba8cc24e Publish
- react-bootstrap-table2-editor@0.2.0
 - react-bootstrap-table2-example@0.1.7
 - react-bootstrap-table2-paginator@0.1.2
 - react-bootstrap-table-next@0.1.9
2018-05-06 16:59:28 +08:00
Allen
b8b52e7fc0 20180507 release #326
20180507 release
2018-05-06 16:57:29 +08:00
Allen
05a8c3be5f fix #309 (#324) 2018-05-06 15:50:18 +08:00
Allen
2f9bedbeeb Merge pull request #323 from react-bootstrap-table/feat/pagination-total
Implement pagination total
2018-05-06 15:16:49 +08:00
AllenFang
01be6fc275 example for #238 2018-05-06 15:08:43 +08:00
AllenFang
c20a4bb220 fix #238 2018-05-06 15:08:20 +08:00
Allen
ed21b3cb65 Merge pull request #322 from react-bootstrap-table/feat/rich-editor
Rich Editors
2018-05-06 14:05:08 +08:00
AllenFang
f2a44c976d patch docs 2018-05-06 13:50:59 +08:00
AllenFang
ca5189d8ad patch tests for rich editors 2018-05-05 18:18:52 +08:00
AllenFang
03f51c36ac add example for rich cell editor 2018-05-05 18:18:52 +08:00
AllenFang
607202b4e9 implement rich cell editor 2018-05-05 18:18:52 +08:00
76 changed files with 3303 additions and 168 deletions

View File

@@ -17,6 +17,8 @@
* [condensed](#condensed)
* [id](#id)
* [classes](#classes)
* [wrapperClasses](#wrapperClasses)
* [headerClasses](#headerClasses)
* [cellEdit](#cellEdit)
* [selectRow](#selectRow)
* [rowStyle](#rowStyle)
@@ -107,6 +109,13 @@ Same as bootstrap `.table-condensed` class for making a table more compact by cu
Customize id on `table` element.
### <a name='classes'>classes - [String]</a>
Customize class on `table` element.
### <a name='wrapperClasses'>wrapperClasses - [String]</a>
Customize class on the outer element which wrap up the `table` element.
### <a name='headerClasses'>headerClasses - [String]</a>
Customize class on the header row(`tr`).
### <a name='cellEdit'>cellEdit - [Object]</a>
Makes table cells editable, please see [cellEdit definition](./cell-edit.md) for more detail.
@@ -198,6 +207,7 @@ paginator({
totalSize, // Total data size. It's necessary when remote is enabled
pageStartIndex: 0, // first page will be 0, default is 1
paginationSize: 3, // the pagination bar size, default is 5
showTotal: true, // display pagination information
sizePerPageList: [ {
text: '5', value: 5
}, {
@@ -219,6 +229,7 @@ paginator({
hidePageListOnlyOnePage: true, // hide pagination bar when only one page, default is false
onPageChange: (page, sizePerPage) => {}, // callback function when page was changing
onSizePerPageChange: (sizePerPage, page) => {}, // callback function when page size was changing
paginationTotalRenderer: (from, to, size) => { ... } // custom the pagination total
})
```

View File

@@ -34,6 +34,8 @@ Available properties in a column object:
* [editCellClasses](#editCellClasses)
* [editorStyle](#editorStyle)
* [editorClasses](#editorClasses)
* [editor](#editor)
* [editorRenderer](#editorRenderer)
* [filter](#filter)
* [filterValue](#filterValue)
@@ -560,11 +562,94 @@ This is almost same as [`column.editCellStyle`](#editCellStyle), but `column.edi
## <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>
Configure `column.filter` will able to setup a column level filter on the header column. Currently, `react-bootstrap-table2` support following filters:
* Text(`textFilter`)
* Select(`selectFilter`)
* Number(`numberFilter`)
* Date(`dateFilter`)
We have a quick example to show you how to use `column.filter`:

View File

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

View File

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

View File

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

View File

@@ -48,6 +48,9 @@ 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)
* 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)
## Validation
[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
@@ -58,6 +61,169 @@ How user save their new editings? We offer two ways:
* 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)
## Validation
## Rich Editors
`react-bootstrap-table2` have following predefined editor:
[`column.validator`](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columnvalidator-function) will help you to work on it!
* 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 editingCellFactory from './src/editing-cell';
import {
EDITTYPE,
CLICK_TO_CELL_EDIT,
DBCLICK_TO_CELL_EDIT,
DELAY_FOR_DBCLICK
@@ -14,3 +15,5 @@ export default (options = {}) => ({
DELAY_FOR_DBCLICK,
options
});
export const Type = EDITTYPE;

View File

@@ -1,6 +1,6 @@
{
"name": "react-bootstrap-table2-editor",
"version": "0.1.5",
"version": "0.2.1",
"description": "it's the editor addon for react-bootstrap-table2",
"main": "./lib/index.js",
"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 CLICK_TO_CELL_EDIT = 'click';
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,9 +6,13 @@ import React, { Component } from 'react';
import cs from 'classnames';
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 EditorIndicator from './editor-indicator';
import { TIME_TO_CLOSE_MESSAGE } from './const';
import { TIME_TO_CLOSE_MESSAGE, EDITTYPE } from './const';
export default _ =>
class EditingCell extends Component {
@@ -73,8 +77,8 @@ export default _ =>
}, timeToCloseMessage);
}
beforeComplete(row, column, newValue) {
const { onUpdate } = this.props;
beforeComplete(newValue) {
const { onUpdate, row, column } = this.props;
if (_.isFunction(column.validator)) {
const validateForm = column.validator(newValue, row, column);
if (_.isObject(validateForm) && !validateForm.valid) {
@@ -89,28 +93,20 @@ export default _ =>
}
handleBlur() {
const { onEscape, blurToSave, row, column } = this.props;
const { onEscape, blurToSave } = this.props;
if (blurToSave) {
const value = this.editor.text.value;
if (!_.isDefined(value)) {
// TODO: for other custom or embed editor
}
this.beforeComplete(row, column, value);
this.beforeComplete(this.editor.getValue());
} else {
onEscape();
}
}
handleKeyDown(e) {
const { onEscape, row, column } = this.props;
const { onEscape } = this.props;
if (e.keyCode === 27) { // ESC
onEscape();
} else if (e.keyCode === 13) { // ENTER
const value = e.currentTarget.value;
if (!_.isDefined(value)) {
// TODO: for other custom or embed editor
}
this.beforeComplete(row, column, value);
this.beforeComplete(this.editor.getValue());
}
}
@@ -124,17 +120,13 @@ export default _ =>
}
render() {
const { invalidMessage } = this.state;
let editor;
const { row, column, className, style, rowIndex, columnIndex } = this.props;
const { dataField } = column;
const value = _.get(row, dataField);
const editorAttrs = {
onKeyDown: this.handleKeyDown,
onBlur: this.handleBlur
};
const hasError = _.isDefined(this.state.invalidMessage);
const hasError = _.isDefined(invalidMessage);
let customEditorClass = column.editorClasses || '';
if (_.isFunction(column.editorClasses)) {
customEditorClass = column.editorClasses(value, row, rowIndex, columnIndex);
@@ -150,20 +142,51 @@ export default _ =>
shake: hasError
}, customEditorClass);
let editorProps = {
ref: node => this.editor = node,
defaultValue: value,
style: editorStyle,
className: editorClass,
onKeyDown: this.handleKeyDown,
onBlur: this.handleBlur
};
const isDefaultEditorDefined = _.isObject(column.editor);
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 (
<td
className={ cs('react-bootstrap-table-editing-cell', className) }
style={ style }
onClick={ this.handleClick }
>
<TextEditor
ref={ node => this.editor = node }
defaultValue={ value }
style={ editorStyle }
className={ editorClass }
{ ...editorAttrs }
/>
{ hasError ? <EditorIndicator invalidMessage={ invalidMessage } /> : null }
{ editor }
{ hasError ? <EditorIndicator invalidMessage={ this.state.invalidMessage } /> : null }
</td>
);
}

View File

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

View File

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

View File

@@ -6,7 +6,12 @@ import { shallow, mount } from 'enzyme';
import _ from 'react-bootstrap-table-next/src/utils';
import editingCellFactory from '../src/editing-cell';
import * as constants from '../src/const';
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';
const EditingCell = editingCellFactory(_);
@@ -39,7 +44,7 @@ describe('EditingCell', () => {
beforeEach(() => {
onEscape = sinon.stub();
onUpdate = sinon.stub();
wrapper = shallow(
wrapper = mount(
<EditingCell
row={ row }
rowIndex={ rowIndex }
@@ -74,7 +79,8 @@ describe('EditingCell', () => {
it('when press ENTER on TextEditor should call onUpdate correctly', () => {
const newValue = 'test';
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.calledWith(row, column, newValue)).toBe(true);
});
@@ -311,7 +317,7 @@ describe('EditingCell', () => {
onEscape={ onEscape }
/>
);
wrapper.instance().beforeComplete(row, column, newValue);
wrapper.instance().beforeComplete(newValue);
});
it('should call column.validator successfully', () => {
@@ -357,7 +363,17 @@ describe('EditingCell', () => {
text: 'ID',
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', () => {
@@ -370,4 +386,156 @@ describe('EditingCell', () => {
});
});
});
describe('if column.editorRenderer is defined', () => {
const TestEditor = () => <input type="text" />;
beforeEach(() => {
column = {
dataField: 'id',
text: 'ID',
editorRenderer: sinon.stub().returns(<TestEditor />)
};
wrapper = mount(
<EditingCell
row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ column }
onUpdate={ onUpdate }
onEscape={ onEscape }
/>
);
});
it('should call column.editorRenderer correctly', () => {
expect(column.editorRenderer.callCount).toBe(1);
});
it('should render correctly', () => {
expect(wrapper.find(TestEditor)).toHaveLength(1);
});
});
describe('if column.editor is select', () => {
beforeEach(() => {
column = {
dataField: 'id',
text: 'ID',
editor: {
type: constants.EDITTYPE.SELECT,
options: [{
value: 1,
label: 'A'
}]
}
};
wrapper = mount(
<EditingCell
row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ column }
onUpdate={ onUpdate }
onEscape={ onEscape }
/>
);
});
it('should render dropdown editor successfully', () => {
const editor = wrapper.find(DropDownEditor);
expect(wrapper.length).toBe(1);
expect(editor.length).toBe(1);
expect(editor.props().options).toEqual(column.editor.options);
});
});
describe('if column.editor is textarea', () => {
beforeEach(() => {
column = {
dataField: 'id',
text: 'ID',
editor: {
type: constants.EDITTYPE.TEXTAREA
}
};
wrapper = mount(
<EditingCell
row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ column }
onUpdate={ onUpdate }
onEscape={ onEscape }
/>
);
});
it('should render textarea editor successfully', () => {
const editor = wrapper.find(TextAreaEditor);
expect(wrapper.length).toBe(1);
expect(editor.length).toBe(1);
});
});
describe('if column.editor is checkbox', () => {
beforeEach(() => {
column = {
dataField: 'id',
text: 'ID',
editor: {
type: constants.EDITTYPE.CHECKBOX
}
};
wrapper = mount(
<EditingCell
row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ column }
onUpdate={ onUpdate }
onEscape={ onEscape }
/>
);
});
it('should render checkbox editor successfully', () => {
const editor = wrapper.find(CheckBoxEditor);
expect(wrapper.length).toBe(1);
expect(editor.length).toBe(1);
});
});
describe('if column.editor is date', () => {
beforeEach(() => {
column = {
dataField: 'id',
text: 'ID',
editor: {
type: constants.EDITTYPE.DATE
}
};
wrapper = mount(
<EditingCell
row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ column }
onUpdate={ onUpdate }
onEscape={ onEscape }
/>
);
});
it('should render date editor successfully', () => {
const editor = wrapper.find(DateEditor);
expect(wrapper.length).toBe(1);
expect(editor.length).toBe(1);
});
});
});

View File

@@ -33,6 +33,7 @@ const columns = [{
<BootstrapTable id="bar" keyField='id' data={ products } columns={ columns } />
<BootstrapTable classes="foo" keyField='id' data={ products } columns={ columns } />
<BootstrapTable wrapperClasses="boo" keyField="id" data={ products } columns={ columns } />
`;
export default () => (
@@ -43,6 +44,9 @@ export default () => (
<h4> Customized table className </h4>
<BootstrapTable classes="foo" keyField="id" data={ products } columns={ columns } />
<h4> Customized wrapper className </h4>
<BootstrapTable wrapperClasses="boo" keyField="id" data={ products } columns={ columns } />
<Code>{ sourceCode }</Code>
</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,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,184 @@
/* eslint no-return-assign: 0 */
import React from 'react';
import PropTypes from 'prop-types';
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, { textFilter, customFilter, Comparator, FILTER_TYPES } from 'react-bootstrap-table2-filter';
import Code from 'components/common/code-block';
import { productsGenerator } from 'utils/common';
const products = productsGenerator(8);
class PriceFilter extends React.Component {
static propTypes = {
column: PropTypes.object.isRequired,
onFilter: PropTypes.func.isRequired
}
constructor(props) {
super(props);
this.filter = this.filter.bind(this);
this.getValue = this.getValue.bind(this);
this.onChange = this.onChange.bind(this);
this.state = { value: 2100 };
}
onChange(e) {
this.setState({ value: e.target.value });
}
getValue() {
return parseInt(this.range.value, 10);
}
filter() {
this.props.onFilter({
number: this.getValue(),
comparator: this.select.value
});
}
render() {
return [
<input
key="range"
ref={ node => this.range = node }
type="range"
min="2100"
max="2110"
onChange={ this.onChange }
/>,
<p
key="show"
ref={ node => this.showValue = node }
style={ { textAlign: 'center' } }
>
{ this.state.value }
</p>,
<select
key="select"
ref={ node => this.select = node }
className="form-control"
>
<option value={ Comparator.GT }>&gt;</option>
<option value={ Comparator.EQ }>=</option>
<option value={ Comparator.LT }>&lt;</option>
</select>,
<button
key="submit"
className="btn btn-warning"
onClick={ this.filter }
>
{ `Filter ${this.props.column.text}` }
</button>
];
}
}
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name',
filter: textFilter()
}, {
dataField: 'price',
text: 'Product Price',
filter: customFilter({
type: FILTER_TYPES.NUMBER // ask react-bootstrap-table to filter data as number
}),
filterRenderer: (onFilter, column) =>
<PriceFilter onFilter={ onFilter } column={ column } />
}];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, { textFilter, customFilter, Comparator, FILTER_TYPES } from 'react-bootstrap-table2-filter';
class PriceFilter extends React.Component {
static propTypes = {
column: PropTypes.object.isRequired,
onFilter: PropTypes.func.isRequired
}
constructor(props) {
super(props);
this.filter = this.filter.bind(this);
this.getValue = this.getValue.bind(this);
this.onChange = this.onChange.bind(this);
this.state = { value: 2100 };
}
onChange(e) {
this.setState({ value: e.target.value });
}
getValue() {
return parseInt(this.range.value, 10);
}
filter() {
this.props.onFilter({
number: this.getValue(),
comparator: this.select.value
});
}
render() {
return [
<input
key="range"
ref={ node => this.range = node }
type="range"
min="2100"
max="2110"
onChange={ this.onChange }
/>,
<p
key="show"
ref={ node => this.showValue = node }
style={ { textAlign: 'center' } }
>
{ this.state.value }
</p>,
<select
key="select"
ref={ node => this.select = node }
className="form-control"
>
<option value={ Comparator.GT }>&gt;</option>
<option value={ Comparator.EQ }>=</option>
<option value={ Comparator.LT }>&lt;</option>
</select>,
<button
key="submit"
className="btn btn-warning"
onClick={ this.filter }
>
{ \`Filter $\{this.props.column.text}\` }
</button>
];
}
}
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name',
filter: textFilter()
}, {
dataField: 'price',
text: 'Product Price',
filter: customFilter({
type: FILTER_TYPES.NUMBER // ask react-bootstrap-table to filter data as number
}),
filterRenderer: (onFilter, column) =>
<PriceFilter onFilter={ onFilter } column={ column } />
}];
<BootstrapTable keyField='id' data={ products } columns={ columns } filter={ filterFactory() } />
`;
export default () => (
<div>
<BootstrapTable
keyField="id"
data={ products }
columns={ columns }
filter={ filterFactory() }
/>
<Code>{ sourceCode }</Code>
</div>
);

View File

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

View File

@@ -0,0 +1,128 @@
/* eslint no-return-assign: 0 */
import React from 'react';
import PropTypes from 'prop-types';
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, { textFilter, customFilter } from 'react-bootstrap-table2-filter';
import Code from 'components/common/code-block';
import { productsGenerator } from 'utils/common';
const products = productsGenerator(8);
class PriceFilter extends React.Component {
static propTypes = {
column: PropTypes.object.isRequired,
onFilter: PropTypes.func.isRequired
}
constructor(props) {
super(props);
this.filter = this.filter.bind(this);
this.getValue = this.getValue.bind(this);
}
getValue() {
return this.input.value;
}
filter() {
this.props.onFilter(this.getValue());
}
render() {
return [
<input
key="input"
ref={ node => this.input = node }
type="text"
placeholder="Input price"
/>,
<button
key="submit"
className="btn btn-warning"
onClick={ this.filter }
>
{ `Find ${this.props.column.text}` }
</button>
];
}
}
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name',
filter: textFilter()
}, {
dataField: 'price',
text: 'Product Price',
filter: customFilter(),
filterRenderer: (onFilter, column) =>
<PriceFilter onFilter={ onFilter } column={ column } />
}];
const sourceCode = `\
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, { textFilter, customFilter } from 'react-bootstrap-table2-filter';
class PriceFilter extends React.Component {
static propTypes = {
column: PropTypes.object.isRequired,
onFilter: PropTypes.func.isRequired
}
constructor(props) {
super(props);
this.filter = this.filter.bind(this);
this.getValue = this.getValue.bind(this);
}
getValue() {
return this.input.value;
}
filter() {
this.props.onFilter(this.getValue());
}
render() {
return [
<input
key="input"
ref={ node => this.input = node }
type="text"
placeholder="Input price"
/>,
<button
key="submit"
className="btn btn-warning"
onClick={ this.filter }
>
{ \`Filter $\{this.props.column.text}\` }
</button>
];
}
}
const columns = [{
dataField: 'id',
text: 'Product ID'
}, {
dataField: 'name',
text: 'Product Name',
filter: textFilter()
}, {
dataField: 'price',
text: 'Product Price',
filter: customFilter(),
filterRenderer: (onFilter, column) =>
<PriceFilter onFilter={ onFilter } column={ column } />
}];
<BootstrapTable keyField='id' data={ products } columns={ columns } filter={ filterFactory() } />
`;
export default () => (
<div>
<BootstrapTable
keyField="id"
data={ products }
columns={ columns }
filter={ filterFactory() }
/>
<Code>{ sourceCode }</Code>
</div>
);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,5 @@
/* eslint no-mixed-operators: 0 */
/**
* products generator for stories
*
@@ -27,12 +29,34 @@ export const productsQualityGenerator = (quantity = 5) =>
quality: index % 3
}));
const jobType = ['A', 'B', 'C', 'D', 'E'];
const jobOwner = ['Allen', 'Bob', 'Cindy'];
export const jobsGenerator = (quantity = 5) =>
Array.from({ length: quantity }, (value, index) => ({
id: index,
name: `Job name ${index}`,
owner: Math.floor(Math.random() * 3),
type: Math.floor(Math.random() * 5)
owner: jobOwner[Math.floor((Math.random() * 2) + 1)],
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));

View File

@@ -33,6 +33,7 @@ import HeaderColumnEventTable from 'examples/header-columns/column-event-table';
import HeaderColumnClassTable from 'examples/header-columns/column-class-table';
import HeaderColumnStyleTable from 'examples/header-columns/column-style-table';
import HeaderColumnAttrsTable from 'examples/header-columns/column-attrs-table';
import HeaderClassTable from 'examples/header-columns/header-class-table';
// column filter
import TextFilter from 'examples/column-filter/text-filter';
@@ -48,9 +49,15 @@ 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 DateFilter from 'examples/column-filter/date-filter';
import DateFilterWithDefaultValue from 'examples/column-filter/date-filter-default-value';
import CustomDateFilter from 'examples/column-filter/custom-date-filter';
import ProgrammaticallyTextFilter from 'examples/column-filter/programmatically-text-filter';
import ProgrammaticallySelectFilter from 'examples/column-filter/programmatically-select-filter';
import ProgrammaticallyNumberFilter from 'examples/column-filter/programmatically-number-filter';
import ProgrammaticallyDateFilter from 'examples/column-filter/programmatically-date-filter';
import CustomFilter from 'examples/column-filter/custom-filter';
import AdvanceCustomFilter from 'examples/column-filter/advance-custom-filter';
// work on rows
import RowStyleTable from 'examples/rows/row-style';
@@ -79,6 +86,11 @@ import CellEditStyleTable from 'examples/cell-edit/cell-edit-style-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
import SingleSelectionTable from 'examples/row-selection/single-selection';
@@ -90,6 +102,7 @@ import ClickToSelectWithCellEditTable from 'examples/row-selection/click-to-sele
import SelectionNoDataTable from 'examples/row-selection/selection-no-data';
import SelectionStyleTable from 'examples/row-selection/selection-style';
import SelectionClassTable from 'examples/row-selection/selection-class';
import CustomSelectionTable from 'examples/row-selection/custom-selection';
import NonSelectableRowsTable from 'examples/row-selection/non-selectable-rows';
import SelectionBgColorTable from 'examples/row-selection/selection-bgcolor';
import SelectionHooks from 'examples/row-selection/selection-hooks';
@@ -153,7 +166,8 @@ storiesOf('Work on Header Columns', module)
.add('Column Event', () => <HeaderColumnEventTable />)
.add('Customize Column Class', () => <HeaderColumnClassTable />)
.add('Customize Column Style', () => <HeaderColumnStyleTable />)
.add('Customize Column HTML attribute', () => <HeaderColumnAttrsTable />);
.add('Customize Column HTML attribute', () => <HeaderColumnAttrsTable />)
.add('Header Class', () => <HeaderClassTable />);
storiesOf('Column Filter', module)
.add('Text Filter', () => <TextFilter />)
@@ -166,13 +180,19 @@ storiesOf('Column Filter', module)
.add('Select Filter with Comparator', () => <SelectFilterComparator />)
.add('Number Filter', () => <NumberFilter />)
.add('Number Filter with Default Value', () => <NumberFilterWithDefaultValue />)
.add('Date Filter', () => <DateFilter />)
.add('Date Filter with Default Value', () => <DateFilterWithDefaultValue />)
.add('Custom Text Filter', () => <CustomTextFilter />)
.add('Custom Select Filter', () => <CustomSelectFilter />)
.add('Custom Number Filter', () => <CustomNumberFilter />)
.add('Custom Date Filter', () => <CustomDateFilter />)
.add('Custom Filter Value', () => <CustomFilterValue />)
.add('Programmatically Text Filter ', () => <ProgrammaticallyTextFilter />)
.add('Programmatically Select Filter ', () => <ProgrammaticallySelectFilter />)
.add('Programmatically Number Filter ', () => <ProgrammaticallyNumberFilter />);
.add('Programmatically Text Filter', () => <ProgrammaticallyTextFilter />)
.add('Programmatically Select Filter', () => <ProgrammaticallySelectFilter />)
.add('Programmatically Number Filter', () => <ProgrammaticallyNumberFilter />)
.add('Programmatically Date Filter', () => <ProgrammaticallyDateFilter />)
.add('Custom Filter', () => <CustomFilter />)
.add('Advance Custom Filter', () => <AdvanceCustomFilter />);
storiesOf('Work on Rows', module)
.add('Customize Row Style', () => <RowStyleTable />)
@@ -200,7 +220,12 @@ storiesOf('Cell Editing', module)
.add('Custom Cell Style', () => <CellEditStyleTable />)
.add('Custom Cell Classes', () => <CellEditClassTable />)
.add('Custom Editor Classes', () => <EditorClassTable />)
.add('Custom Editor Style', () => <EditorStyleTable />);
.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)
.add('Single Selection', () => <SingleSelectionTable />)
@@ -212,6 +237,7 @@ storiesOf('Row Selection', module)
.add('Selection without Data', () => <SelectionNoDataTable />)
.add('Selection Style', () => <SelectionStyleTable />)
.add('Selection Class', () => <SelectionClassTable />)
.add('Custom Selection', () => <CustomSelectionTable />)
.add('Selection Background Color', () => <SelectionBgColorTable />)
.add('Not Selectabled Rows', () => <NonSelectableRowsTable />)
.add('Selection Hooks', () => <SelectionHooks />)

View File

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

View File

@@ -9,4 +9,8 @@
.demo-row-odd {
background-color: $green-lighten-4;
}
.header-class {
background-color: $green-lighten-4;
}

View File

@@ -19,6 +19,8 @@ You can get all types of filters via import and these filters are a factory func
* TextFilter
* SelectFilter
* NumberFilter
* DateFilter
* CustomFilter
* **Coming soon!**
## Add CSS
@@ -129,7 +131,7 @@ const columns = [..., {
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';
import filterFactory, { selectFilter, Comparator, numberFilter } from 'react-bootstrap-table2-filter';
// omit...
const numberFilter = numberFilter({
@@ -149,4 +151,93 @@ const numberFilter = numberFilter({
})
// omit...
```
```
## Date Filter
```js
import filterFactory, { dateFilter } from 'react-bootstrap-table2-filter';
const columns = [..., {
dataField: 'date',
text: 'Product date',
filter: dateFilter()
}];
<BootstrapTable keyField='id' data={ products } columns={ columns } filter={ filterFactory() } />
```
> **Notes:** date filter accept a Javascript Date object in your raw data.
Date filter is same as other filter, you can custom the date filter via `dateFilter` factory function:
```js
import filterFactory, { selectFilter, Comparator } from 'react-bootstrap-table2-filter';
// omit...
const dateFilter = dateFilter({
delay: 600, // how long will trigger filtering after user typing, default is 500 ms
placeholder: 'custom placeholder', // placeholder for date input
withoutEmptyComparatorOption: true, // dont render empty option for comparator
comparators: [Comparator.EQ, Comparator.GT, Comparator.LT], // Custom the comparators
style: { display: 'inline-grid' }, // custom the style on date filter
className: 'custom-dateFilter-class', // custom the class on date filter
comparatorStyle: { backgroundColor: 'antiquewhite' }, // custom the style on comparator select
comparatorClassName: 'custom-comparator-class', // custom the class on comparator select
dateStyle: { backgroundColor: 'cadetblue', margin: '0px' }, // custom the style on date input
dateClassName: 'custom-date-class', // custom the class on date input
defaultValue: { date: new Date(2018, 0, 1), comparator: Comparator.GT } // default value
})
// omit...
```
## Custom Filter
```js
import filterFactory, { customFilter } from 'react-bootstrap-table2-filter';
const columns = [..., {
dataField: 'date',
text: 'Product Name',
filter: customFilter(),
filterRenderer: (onFilter, column) => .....
}];
<BootstrapTable keyField='id' data={ products } columns={ columns } filter={ filterFactory() } />
```
In custom filter case, you are suppose to finish following two steps:
1. Call `customFilter` and pass to `column.filter`
2. Give `column.filterRenderer` as a callback function and return your custom filter element.
### column.filterRenderer
This function will pass two argument to you:
1. `onFilter`: call it to trigger filter when you need.
2. `column`: Just the column object!
In the end, please remember to return your custom filter element!
### customFilter
`customFilter` function just same as `textFilter`, `selectFilter` etc, it is for customization reason. However, in the custom filter case, there's only one props is valid: `type`
```js
import filterFactory, { FILTER_TYPES } from 'react-bootstrap-table2-filter';
const customFilter = customFilter({
type: FILTER_TYPES.NUMBER, // default is FILTER_TYPES.TEXT
})
```
`type` is a way to ask `react-bootstrap-table` to filter you data as number, select, date or normal text.
### FILTER_TYPES
Following properties is valid in `FILTER_TYPES`:
* TEXT
* SELECT
* NUMBER
* DATE

View File

@@ -1,14 +1,18 @@
import TextFilter from './src/components/text';
import SelectFilter from './src/components/select';
import NumberFilter from './src/components/number';
import DateFilter from './src/components/date';
import wrapperFactory from './src/wrapper';
import * as Comparison from './src/comparison';
import { FILTER_TYPE } from './src/const';
export default (options = {}) => ({
wrapperFactory,
options
});
export const FILTER_TYPES = FILTER_TYPE;
export const Comparator = Comparison;
export const textFilter = (props = {}) => ({
@@ -25,3 +29,12 @@ export const numberFilter = (props = {}) => ({
Filter: NumberFilter,
props
});
export const dateFilter = (props = {}) => ({
Filter: DateFilter,
props
});
export const customFilter = (props = {}) => ({
props
});

View File

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

View File

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

View File

@@ -1,3 +1,4 @@
/* eslint jsx-a11y/no-static-element-interactions: 0 */
/* eslint react/require-default-props: 0 */
/* eslint no-return-assign: 0 */
@@ -167,7 +168,11 @@ class NumberFilter extends Component {
`;
return (
<div className={ `filter number-filter ${className}` } style={ style }>
<div
onClick={ e => e.stopPropagation() }
className={ `filter number-filter ${className}` }
style={ style }
>
<select
ref={ n => this.numberFilterComparator = n }
style={ comparatorStyle }

View File

@@ -116,6 +116,7 @@ class SelectFilter extends Component {
style={ style }
className={ selectClass }
onChange={ this.filter }
onClick={ e => e.stopPropagation() }
defaultValue={ defaultValue !== undefined ? defaultValue : '' }
>
{ this.getOptions() }

View File

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

View File

@@ -91,6 +91,102 @@ export const filterByNumber = _ => (
})
);
export const filterByDate = _ => (
data,
dataField,
{ filterVal: { comparator, date } },
customFilterValue
) => {
if (!date || !comparator) return data;
const filterDate = date.getDate();
const filterMonth = date.getMonth();
const filterYear = date.getFullYear();
return data.filter((row) => {
let valid = true;
let cell = _.get(row, dataField);
if (customFilterValue) {
cell = customFilterValue(cell, row);
}
if (typeof cell !== 'object') {
cell = new Date(cell);
}
const targetDate = cell.getDate();
const targetMonth = cell.getMonth();
const targetYear = cell.getFullYear();
switch (comparator) {
case EQ: {
if (
filterDate !== targetDate ||
filterMonth !== targetMonth ||
filterYear !== targetYear
) {
valid = false;
}
break;
}
case GT: {
if (cell <= date) {
valid = false;
}
break;
}
case GE: {
if (targetYear < filterYear) {
valid = false;
} else if (targetYear === filterYear &&
targetMonth < filterMonth) {
valid = false;
} else if (targetYear === filterYear &&
targetMonth === filterMonth &&
targetDate < filterDate) {
valid = false;
}
break;
}
case LT: {
if (cell >= date) {
valid = false;
}
break;
}
case LE: {
if (targetYear > filterYear) {
valid = false;
} else if (targetYear === filterYear &&
targetMonth > filterMonth) {
valid = false;
} else if (targetYear === filterYear &&
targetMonth === filterMonth &&
targetDate > filterDate) {
valid = false;
}
break;
}
case NE: {
if (
filterDate === targetDate &&
filterMonth === targetMonth &&
filterYear === targetYear
) {
valid = false;
}
break;
}
default: {
console.error('Date comparator provided is not supported');
break;
}
}
return valid;
});
};
export const filterFactory = _ => (filterType) => {
let filterFn;
switch (filterType) {
@@ -101,6 +197,9 @@ export const filterFactory = _ => (filterType) => {
case FILTER_TYPE.NUMBER:
filterFn = filterByNumber(_);
break;
case FILTER_TYPE.DATE:
filterFn = filterByDate(_);
break;
default:
filterFn = filterByText(_);
}

View File

@@ -20,6 +20,7 @@ export default (Base, {
super(props);
this.state = { currFilters: {}, isDataChanged: props.isDataChanged || false };
this.onFilter = this.onFilter.bind(this);
this.onExternalFilter = this.onExternalFilter.bind(this);
}
componentWillReceiveProps({ isDataChanged, store, columns }) {
@@ -30,13 +31,11 @@ export default (Base, {
// I think this condition only isRemoteFilter is enough
store.filteredData = store.getAllData();
this.setState(() => ({ isDataChanged: true, currFilters: store.filters }));
} else if (isDataChanged) {
if (!isRemoteFilter && Object.keys(this.state.currFilters).length > 0) {
} else {
if (Object.keys(this.state.currFilters).length > 0) {
store.filteredData = filters(store, columns, _)(this.state.currFilters);
}
this.setState(() => ({ isDataChanged }));
} else {
this.setState(() => ({ isDataChanged: false }));
}
}
@@ -50,7 +49,8 @@ export default (Base, {
onFilter(column, filterType) {
return (filterVal) => {
const { store, columns } = this.props;
const currFilters = Object.assign({}, this.state.currFilters);
// watch out here if migration to context API, #334
const currFilters = Object.assign({}, store.filters);
const { dataField, filter } = column;
if (!_.isDefined(filterVal) || filterVal === '') {
@@ -79,12 +79,19 @@ export default (Base, {
};
}
onExternalFilter(column, filterType) {
return (value) => {
this.onFilter(column, filterType)(value);
};
}
render() {
return (
<Base
{ ...this.props }
data={ this.props.store.data }
onFilter={ this.onFilter }
onExternalFilter={ this.onExternalFilter }
isDataChanged={ this.state.isDataChanged }
/>
);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,6 @@
/* eslint no-mixed-operators: 0 */
import Const from './const';
export default ExtendBase =>
class PageResolver extends ExtendBase {
backToPrevPage() {
@@ -27,6 +29,23 @@ export default ExtendBase =>
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(
totalPages = this.state.totalPages,
lastPage = this.state.lastPage) {

View File

@@ -1,12 +1,39 @@
/* eslint no-param-reassign: 0 */
const getNormalizedPage = (
page,
pageStartIndex
) => {
const offset = Math.abs(1 - pageStartIndex);
return page + offset;
};
const endIndex = (
page,
sizePerPage,
pageStartIndex
) => (getNormalizedPage(page, pageStartIndex) * sizePerPage) - 1;
const startIndex = (
end,
sizePerPage,
) => end - (sizePerPage - 1);
export const alignPage = (store, pageStartIndex, sizePerPage) => {
const end = endIndex(store.page, sizePerPage, pageStartIndex);
const dataSize = store.data.length;
if (end - 1 > dataSize) {
return pageStartIndex;
}
return store.page;
};
export const getByCurrPage = (store, pageStartIndex) => {
const dataSize = store.data.length;
if (!dataSize) return [];
const getNormalizedPage = () => {
const offset = Math.abs(1 - pageStartIndex);
return store.page + offset;
};
const end = (getNormalizedPage() * store.sizePerPage) - 1;
const start = end - (store.sizePerPage - 1);
const end = endIndex(store.page, store.sizePerPage, pageStartIndex);
const start = startIndex(end, store.sizePerPage);
const result = [];
for (let i = start; i <= end; i += 1) {

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import Const from './const';
import Pagination from './pagination';
import { getByCurrPage } from './page';
import { getByCurrPage, alignPage } from './page';
export default (Base, {
remoteResolver
@@ -49,16 +49,23 @@ export default (Base, {
componentWillReceiveProps(nextProps) {
let needNewState = false;
let { currPage, currSizePerPage } = this.state;
const { page, sizePerPage, pageStartIndex, onPageChange } = nextProps.pagination.options;
const { page, sizePerPage, onPageChange } = nextProps.pagination.options;
const pageStartIndex = typeof nextProps.pagination.options.pageStartIndex !== 'undefined' ?
nextProps.pagination.options.pageStartIndex : Const.PAGE_START_INDEX;
if (typeof page !== 'undefined' && currPage !== page) { // user defined page
currPage = page;
needNewState = true;
} else if (nextProps.isDataChanged) { // user didn't defined page but data change
currPage = typeof pageStartIndex !== 'undefined' ? pageStartIndex : Const.PAGE_START_INDEX;
} else if (nextProps.isDataChanged) {
currPage = alignPage(this.props.store, pageStartIndex, currSizePerPage);
needNewState = true;
}
if (typeof currPage === 'undefined') {
currPage = pageStartIndex;
}
if (typeof sizePerPage !== 'undefined') {
currSizePerPage = sizePerPage;
needNewState = true;
@@ -145,6 +152,8 @@ export default (Base, {
alwaysShowAllBtns={ alwaysShowAllBtns }
hideSizePerPage={ hideSizePerPage }
hidePageListOnlyOnePage={ hidePageListOnlyOnePage }
showTotal={ options.showTotal }
paginationTotalRenderer={ options.paginationTotalRenderer }
firstPageText={ options.firstPageText || Const.FIRST_PAGE_TEXT }
prePageText={ options.prePageText || Const.PRE_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', () => {
const props = createMockProps();

View File

@@ -1,5 +1,5 @@
import Store from 'react-bootstrap-table-next/src/store';
import { getByCurrPage } from '../src/page';
import { getByCurrPage, alignPage } from '../src/page';
describe('Page Functions', () => {
let data;
@@ -48,4 +48,40 @@ describe('Page Functions', () => {
});
});
});
describe('alignPage', () => {
const pageStartIndex = 1;
const sizePerPage = 10;
describe('if the length of store.data is less than the end page index', () => {
beforeEach(() => {
data = [];
for (let i = 0; i < 15; i += 1) {
data.push({ id: i, name: `test_name${i}` });
}
store = new Store('id');
store.data = data;
store.page = 2;
});
it('should return pageStartIndex argument', () => {
expect(alignPage(store, pageStartIndex, sizePerPage)).toEqual(pageStartIndex);
});
});
describe('if the length of store.data is large than the end page index', () => {
beforeEach(() => {
data = [];
for (let i = 0; i < 30; i += 1) {
data.push({ id: i, name: `test_name${i}` });
}
store = new Store('id');
store.data = data;
store.page = 2;
});
it('should return current page', () => {
expect(alignPage(store, pageStartIndex, sizePerPage)).toEqual(store.page);
});
});
});
});

View File

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

View File

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

View File

@@ -29,12 +29,15 @@ class BootstrapTable extends PropsBaseResolver(Component) {
render() {
const { loading, overlay } = this.props;
const table = this.renderTable();
if (loading && overlay) {
const LoadingOverlay = overlay(table, loading);
return <LoadingOverlay />;
if (overlay) {
const LoadingOverlay = overlay(loading);
return (
<LoadingOverlay>
{ this.renderTable() }
</LoadingOverlay>
);
}
return table;
return this.renderTable();
}
renderTable() {
@@ -52,9 +55,12 @@ class BootstrapTable extends PropsBaseResolver(Component) {
caption,
rowStyle,
rowClasses,
wrapperClasses,
rowEvents
} = this.props;
const tableWrapperClass = cs('react-bootstrap-table', wrapperClasses);
const tableClass = cs('table', {
'table-striped': striped,
'table-hover': hover,
@@ -75,15 +81,17 @@ class BootstrapTable extends PropsBaseResolver(Component) {
const tableCaption = (caption && <Caption>{ caption }</Caption>);
return (
<div className="react-bootstrap-table">
<div className={ tableWrapperClass }>
<table id={ id } className={ tableClass }>
{ tableCaption }
<Header
columns={ columns }
className={ this.props.headerClasses }
sortField={ store.sortField }
sortOrder={ store.sortOrder }
onSort={ this.props.onSort }
onFilter={ this.props.onFilter }
onExternalFilter={ this.props.onExternalFilter }
selectRow={ headerCellSelectionInfo }
/>
<Body
@@ -120,6 +128,7 @@ BootstrapTable.propTypes = {
hover: PropTypes.bool,
id: PropTypes.string,
classes: PropTypes.string,
wrapperClasses: PropTypes.string,
condensed: PropTypes.bool,
caption: PropTypes.oneOfType([
PropTypes.node,
@@ -138,13 +147,16 @@ BootstrapTable.propTypes = {
classes: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
nonSelectable: PropTypes.array,
bgColor: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
hideSelectColumn: PropTypes.bool
hideSelectColumn: PropTypes.bool,
selectionRenderer: PropTypes.func,
selectionHeaderRenderer: PropTypes.func
}),
onRowSelect: PropTypes.func,
onAllRowsSelect: PropTypes.func,
rowStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
rowEvents: PropTypes.object,
rowClasses: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
headerClasses: PropTypes.string,
defaultSorted: PropTypes.arrayOf(PropTypes.shape({
dataField: PropTypes.string.isRequired,
order: PropTypes.oneOf([Const.SORT_DESC, Const.SORT_ASC]).isRequired
@@ -153,7 +165,8 @@ BootstrapTable.propTypes = {
overlay: PropTypes.func,
onTableChange: PropTypes.func,
onSort: PropTypes.func,
onFilter: PropTypes.func
onFilter: PropTypes.func,
onExternalFilter: PropTypes.func
};
BootstrapTable.defaultProps = {

View File

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

View File

@@ -17,13 +17,15 @@ const HeaderCell = (props) => {
sorting,
sortOrder,
isLastSorting,
onFilter
onFilter,
onExternalFilter
} = props;
const {
text,
sort,
filter,
filterRenderer,
headerTitle,
headerAlign,
headerFormatter,
@@ -89,7 +91,11 @@ const HeaderCell = (props) => {
if (cellClasses) cellAttrs.className = cs(cellAttrs.className, cellClasses);
if (!_.isEmptyObject(cellStyle)) cellAttrs.style = cellStyle;
if (filter) {
if (filterRenderer) {
const onCustomFilter = onExternalFilter(column, filter.props.type);
filterElm = filterRenderer(onCustomFilter, column);
} else if (filter) {
filterElm = <filter.Filter { ...filter.props } onFilter={ onFilter } column={ column } />;
}
@@ -127,13 +133,16 @@ HeaderCell.propTypes = {
sort: PropTypes.bool,
sortFunc: PropTypes.func,
onSort: PropTypes.func,
editor: PropTypes.object,
editable: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
editCellStyle: PropTypes.oneOfType([PropTypes.object, 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,
filter: PropTypes.object,
filterRenderer: PropTypes.func,
filterValue: PropTypes.func
}).isRequired,
index: PropTypes.number.isRequired,
@@ -141,7 +150,8 @@ HeaderCell.propTypes = {
sorting: PropTypes.bool,
sortOrder: PropTypes.oneOf([Const.SORT_ASC, Const.SORT_DESC]),
isLastSorting: PropTypes.bool,
onFilter: PropTypes.func
onFilter: PropTypes.func,
onExternalFilter: PropTypes.func
};
export default HeaderCell;

View File

@@ -10,17 +10,19 @@ const Header = (props) => {
const { ROW_SELECT_DISABLED } = Const;
const {
className,
columns,
onSort,
onFilter,
sortField,
sortOrder,
selectRow
selectRow,
onExternalFilter
} = props;
return (
<thead>
<tr>
<tr className={ className }>
{
(selectRow.mode !== ROW_SELECT_DISABLED && !selectRow.hideSelectColumn)
? <SelectionHeaderCell { ...selectRow } /> : null
@@ -39,6 +41,7 @@ const Header = (props) => {
onSort={ onSort }
sorting={ currSort }
onFilter={ onFilter }
onExternalFilter={ onExternalFilter }
sortOrder={ sortOrder }
isLastSorting={ isLastSorting }
/>);
@@ -57,7 +60,9 @@ Header.propTypes = {
onFilter: PropTypes.func,
sortField: PropTypes.string,
sortOrder: PropTypes.string,
selectRow: PropTypes.object
selectRow: PropTypes.object,
onExternalFilter: PropTypes.func,
className: PropTypes.string
};
export default Header;

View File

@@ -2,6 +2,7 @@ import _ from './utils';
const events = [
'onClick',
'onDoubleClick',
'onMouseEnter',
'onMouseLeave'
];

View File

@@ -14,7 +14,8 @@ export default class SelectionCell extends Component {
onRowSelect: PropTypes.func,
disabled: PropTypes.bool,
rowIndex: PropTypes.number,
clickToSelect: PropTypes.bool
clickToSelect: PropTypes.bool,
selectionRenderer: PropTypes.func
}
constructor() {
@@ -53,16 +54,25 @@ export default class SelectionCell extends Component {
const {
mode: inputType,
selected,
disabled
disabled,
selectionRenderer
} = this.props;
return (
<td onClick={ this.handleClick }>
<input
type={ inputType }
checked={ selected }
disabled={ disabled }
/>
{
selectionRenderer ? selectionRenderer({
mode: inputType,
checked: selected,
disabled
}) : (
<input
type={ inputType }
checked={ selected }
disabled={ disabled }
/>
)
}
</td>
);
}

View File

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

View File

@@ -39,15 +39,17 @@ export default Base =>
}
componentWillReceiveProps(nextProps) {
let sortedColumn;
for (let i = 0; i < nextProps.columns.length; i += 1) {
if (nextProps.columns[i].dataField === nextProps.store.sortField) {
sortedColumn = nextProps.columns[i];
break;
if (!this.isRemoteSort() && !this.isRemotePagination()) {
let sortedColumn;
for (let i = 0; i < nextProps.columns.length; i += 1) {
if (nextProps.columns[i].dataField === nextProps.store.sortField) {
sortedColumn = nextProps.columns[i];
break;
}
}
if (sortedColumn && sortedColumn.sort) {
nextProps.store.sortBy(sortedColumn);
}
}
if (sortedColumn && sortedColumn.sort) {
nextProps.store.sortBy(sortedColumn);
}
}

View File

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

View File

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

View File

@@ -669,4 +669,74 @@ describe('HeaderCell', () => {
});
});
});
describe('when column.filter is defined', () => {
const onFilter = jest.fn();
const filterProps = { a: 123 };
const Filter = () => <div>test</div>;
let column;
beforeEach(() => {
onFilter.mockClear();
column = {
dataField: 'id',
text: 'ID',
filter: {
props: filterProps,
Filter
}
};
wrapper = shallow(<HeaderCell column={ column } index={ index } onFilter={ onFilter } />);
});
it('should render successfully', () => {
expect(wrapper.length).toBe(1);
expect(wrapper.find('th').length).toBe(1);
});
it('should render filter correctly', () => {
expect(wrapper.find(Filter).length).toBe(1);
expect(wrapper.find(Filter).props()).toEqual({
column,
onFilter,
...filterProps
});
});
});
describe('when column.filter and column.filterRenderer is defined', () => {
const onExternalFilter = jest.fn();
const filterProps = { a: 123 };
const Filter = () => <div>test</div>;
const filterRenderer = jest.fn().mockReturnValue(<Filter />);
let column;
beforeEach(() => {
onExternalFilter.mockClear();
filterRenderer.mockClear();
column = {
dataField: 'id',
text: 'ID',
filter: {
props: filterProps
},
filterRenderer
};
wrapper = shallow(
<HeaderCell column={ column } index={ index } onExternalFilter={ onExternalFilter } />);
});
it('should render successfully', () => {
expect(wrapper.length).toBe(1);
expect(wrapper.find('th').length).toBe(1);
});
it('should render filter correctly', () => {
expect(wrapper.find(Filter).length).toBe(1);
});
it('should call filterRenderer function correctly', () => {
expect(filterRenderer).toHaveBeenCalledTimes(1);
});
});
});

View File

@@ -29,6 +29,25 @@ describe('Header', () => {
});
});
describe('className prop is exists', () => {
const className = 'test-class';
beforeEach(() => {
wrapper = shallow(
<Header
{ ...mockHeaderResolvedProps }
columns={ columns }
className={ className }
/>
);
});
it('should render successfully', () => {
expect(wrapper.length).toBe(1);
expect(wrapper.find(`.${className}`).length).toBe(1);
});
});
describe('header with columns enable sort', () => {
const sortField = columns[1].dataField;

View File

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

View File

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