Compare commits

...

26 Commits

Author SHA1 Message Date
AllenFang
4db4f4fb2d Publish
- react-bootstrap-table2-example@0.1.6
 - react-bootstrap-table2-filter@0.1.6
 - react-bootstrap-table-next@0.1.8
2018-04-15 21:25:09 +08:00
Allen
1d7df6819e 2018/04/01 release
2018/04/01 release
2018-04-15 21:21:50 +08:00
Allen
e4b6993692 fix #302 (#304) 2018-04-15 17:29:17 +08:00
Allen
b15d7a3412 Merge pull request #287 from react-bootstrap-table/feature/pogrammatically-filter
Feature/programmatically filter
2018-04-15 15:46:54 +08:00
Allen
b172c6801c fix #228 2018-04-08 21:17:24 +08:00
Chun-MingChen
dc1f4dcc38 update travis.yml 2018-04-05 16:35:26 +08:00
Chun-MingChen
a82e611358 [test] add test for pogrammatically filter 2018-04-04 17:58:19 +08:00
Chun-MingChen
c64951fd6f [test] correct tests for filter components
* <Text />, <Select /> and <Numbner />
2018-04-04 17:58:19 +08:00
Chun-MingChen
a35701fabf [test] correct test for filter wrapper 2018-04-04 17:58:19 +08:00
Chun-MingChen
f54c1f77b4 display filter condition correctly and make sure text filter to be String 2018-04-04 17:58:19 +08:00
Chun-MingChen
377534512a rename props and variable in samples for better readability 2018-04-04 17:45:56 +08:00
Chun-MingChen
09032349d0 [example] example for programmatically filter by text, number and select 2018-04-04 17:45:56 +08:00
Chun-MingChen
4dd39aeed8 export onFilter function to allow user to access 2018-04-04 17:45:56 +08:00
Chun-MingChen
a1477e2ad3 filter column by new onFilter 2018-04-04 17:45:56 +08:00
Chun-MingChen
f34cb4bf63 allow user to filter column without inputField
* wrap onFilter to HOF to allow filter dynamically
2018-04-04 17:45:56 +08:00
AllenFang
3dc9cd3941 Publish
- react-bootstrap-table2-editor@0.1.5
 - react-bootstrap-table2-filter@0.1.5
 - react-bootstrap-table-next@0.1.7
2018-04-02 23:46:59 +08:00
AllenFang
e8458b4b63 Publish
- react-bootstrap-table2-editor@0.1.4
 - react-bootstrap-table2-example@0.1.5
 - react-bootstrap-table2-filter@0.1.4
 - react-bootstrap-table-next@0.1.6
2018-04-01 15:56:53 +08:00
Allen
1d87ce9ffc 2018/04/01 release
2018/04/01 release
2018-04-01 15:55:45 +08:00
Allen
88e1a0774b fix #281 2018-04-01 14:14:32 +08:00
Patrick O'Meara
11d4f40089 noDataIndication (#276)
* use the correct amount of cells when the first row is select
* storybook added for development, not necessary in docs

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

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

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

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

* eslint complaints

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

* tests updated
2018-03-25 16:36:58 +08:00
56 changed files with 1242 additions and 222 deletions

View File

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

View File

@@ -23,6 +23,7 @@
* [rowClasses](#rowClasses) * [rowClasses](#rowClasses)
* [rowEvents](#rowEvents) * [rowEvents](#rowEvents)
* [defaultSorted](#defaultSorted) * [defaultSorted](#defaultSorted)
* [defaultSortDirection](#defaultSortDirection)
* [pagination](#pagination) * [pagination](#pagination)
* [filter](#filter) * [filter](#filter)
* [onTableChange](#onTableChange) * [onTableChange](#onTableChange)
@@ -168,6 +169,9 @@ const defaultSorted = [{
}]; }];
``` ```
### <a name='defaultSortDirection'>defaultSortDirection - [String]</a>
Default sort direction when user click on header column at first time, available value is `asc` and `desc`. Default is `desc`.
### <a name='pagination'>pagination - [Object]</a> ### <a name='pagination'>pagination - [Object]</a>
`pagination` allow user to render a pagination panel on the bottom of table. But pagination functionality is separated from core of `react-bootstrap-table2` so that you are suppose to install `react-bootstrap-table2-paginator` additionally. `pagination` allow user to render a pagination panel on the bottom of table. But pagination functionality is separated from core of `react-bootstrap-table2` so that you are suppose to install `react-bootstrap-table2-paginator` additionally.

View File

@@ -32,6 +32,8 @@ Available properties in a column object:
* [validator](#validator) * [validator](#validator)
* [editCellStyle](#editCellStyle) * [editCellStyle](#editCellStyle)
* [editCellClasses](#editCellClasses) * [editCellClasses](#editCellClasses)
* [editorStyle](#editorStyle)
* [editorClasses](#editorClasses)
* [filter](#filter) * [filter](#filter)
* [filterValue](#filterValue) * [filterValue](#filterValue)
@@ -552,6 +554,12 @@ Or take a callback function
} }
``` ```
## <a name='editorStyle'>column.editorStyle - [Object | Function]</a>
This is almost same as [`column.editCellStyle`](#editCellStyle), but `column.editorStyle` is custom the style on editor instead of cell(`td`).
## <a name='editorClasses'>column.editorClasses - [Object | Function]</a>
This is almost same as [`column.editCellClasses`](#editCellClasses), but `column.editorClasses` is custom the class on editor instead of cell(`td`).
## <a name='filter'>column.filter - [Object]</a> ## <a name='filter'>column.filter - [Object]</a>
Configure `column.filter` will able to setup a column level filter on the header column. Currently, `react-bootstrap-table2` support following filters: Configure `column.filter` will able to setup a column level filter on the header column. Currently, `react-bootstrap-table2` support following filters:

View File

@@ -158,12 +158,12 @@ const selectRow = {
### <a name='onSelect'>selectRow.onSelect - [Function]</a> ### <a name='onSelect'>selectRow.onSelect - [Function]</a>
This callback function will be called when a row is select/unselect and pass following three arguments: This callback function will be called when a row is select/unselect and pass following three arguments:
`row`, `isSelect` and `rowIndex`. `row`, `isSelect`, `rowIndex` and `e`.
```js ```js
const selectRow = { const selectRow = {
mode: 'checkbox', mode: 'checkbox',
onSelect: (row, isSelect, rowIndex) => { onSelect: (row, isSelect, rowIndex, e) => {
// ... // ...
} }
}; };
@@ -175,7 +175,7 @@ This callback function will be called when select/unselect all and it only work
```js ```js
const selectRow = { const selectRow = {
mode: 'checkbox', mode: 'checkbox',
onSelectAll: (isSelect, results) => { onSelectAll: (isSelect, results, e) => {
// ... // ...
} }
}; };

View File

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

View File

@@ -49,13 +49,15 @@ How user save their new editings? We offer two ways:
* Cell Level (Configure [column.editable](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditable-bool-function) as a callback function) * Cell Level (Configure [column.editable](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditable-bool-function) as a callback function)
## Customize Style/Class ## Customize Style/Class
Currently, we only support the editing cell style/class customization, in the future, we will offer more customizations.
### Editing Cell ### Editing Cell
* Customize the editing cell style via [column.editCellStyle](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditcellstyle-object-function) * Customize the editing cell style via [column.editCellStyle](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditcellstyle-object-function)
* Customize the editing cell classname via [column.editCellClasses](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditcellclasses-string-function) * Customize the editing cell classname via [column.editCellClasses](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditcellclasses-string-function)
### Editor
* Customize the editor style via [column.editorStyle](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditorstyle-object-function)
* Customize the editor classname via [column.editoClasses](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columneditorclasses-string-function)
## Validation ## 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! [`column.validator`](https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/column-props.html#columnvalidator-function) will help you to work on it!

View File

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

View File

@@ -14,7 +14,9 @@ export default _ =>
class EditingCell extends Component { class EditingCell extends Component {
static propTypes = { static propTypes = {
row: PropTypes.object.isRequired, row: PropTypes.object.isRequired,
rowIndex: PropTypes.number.isRequired,
column: PropTypes.object.isRequired, column: PropTypes.object.isRequired,
columnIndex: PropTypes.number.isRequired,
onUpdate: PropTypes.func.isRequired, onUpdate: PropTypes.func.isRequired,
onEscape: PropTypes.func.isRequired, onEscape: PropTypes.func.isRequired,
timeToCloseMessage: PropTypes.number, timeToCloseMessage: PropTypes.number,
@@ -123,7 +125,7 @@ export default _ =>
render() { render() {
const { invalidMessage } = this.state; const { invalidMessage } = this.state;
const { row, column, className, style } = this.props; const { row, column, className, style, rowIndex, columnIndex } = this.props;
const { dataField } = column; const { dataField } = column;
const value = _.get(row, dataField); const value = _.get(row, dataField);
@@ -133,7 +135,21 @@ export default _ =>
}; };
const hasError = _.isDefined(invalidMessage); const hasError = _.isDefined(invalidMessage);
const editorClass = hasError ? cs('animated', 'shake') : null; let customEditorClass = column.editorClasses || '';
if (_.isFunction(column.editorClasses)) {
customEditorClass = column.editorClasses(value, row, rowIndex, columnIndex);
}
let editorStyle = column.editorStyle || {};
if (_.isFunction(column.editorStyle)) {
editorStyle = column.editorStyle(value, row, rowIndex, columnIndex);
}
const editorClass = cs({
animated: hasError,
shake: hasError
}, customEditorClass);
return ( return (
<td <td
className={ cs('react-bootstrap-table-editing-cell', className) } className={ cs('react-bootstrap-table-editing-cell', className) }
@@ -143,6 +159,7 @@ export default _ =>
<TextEditor <TextEditor
ref={ node => this.editor = node } ref={ node => this.editor = node }
defaultValue={ value } defaultValue={ value }
style={ editorStyle }
className={ editorClass } className={ editorClass }
{ ...editorAttrs } { ...editorAttrs }
/> />

View File

@@ -28,6 +28,9 @@ describe('EditingCell', () => {
name: 'A' name: 'A'
}; };
const rowIndex = 1;
const columnIndex = 1;
let column = { let column = {
dataField: 'id', dataField: 'id',
text: 'ID' text: 'ID'
@@ -39,6 +42,8 @@ describe('EditingCell', () => {
wrapper = shallow( wrapper = shallow(
<EditingCell <EditingCell
row={ row } row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ column } column={ column }
onUpdate={ onUpdate } onUpdate={ onUpdate }
onEscape={ onEscape } onEscape={ onEscape }
@@ -58,7 +63,7 @@ describe('EditingCell', () => {
expect(textEditor.props().defaultValue).toEqual(row[column.dataField]); expect(textEditor.props().defaultValue).toEqual(row[column.dataField]);
expect(textEditor.props().onKeyDown).toBeDefined(); expect(textEditor.props().onKeyDown).toBeDefined();
expect(textEditor.props().onBlur).toBeDefined(); expect(textEditor.props().onBlur).toBeDefined();
expect(textEditor.props().className).toBeNull(); expect(textEditor.props().className).toEqual('');
}); });
it('should not render EditorIndicator due to state.invalidMessage is null', () => { it('should not render EditorIndicator due to state.invalidMessage is null', () => {
@@ -92,6 +97,8 @@ describe('EditingCell', () => {
wrapper = shallow( wrapper = shallow(
<EditingCell <EditingCell
row={ row } row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ column } column={ column }
onUpdate={ onUpdate } onUpdate={ onUpdate }
onEscape={ onEscape } onEscape={ onEscape }
@@ -112,6 +119,8 @@ describe('EditingCell', () => {
wrapper = shallow( wrapper = shallow(
<EditingCell <EditingCell
row={ row } row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ column } column={ column }
onUpdate={ onUpdate } onUpdate={ onUpdate }
onEscape={ onEscape } onEscape={ onEscape }
@@ -126,12 +135,140 @@ describe('EditingCell', () => {
}); });
}); });
describe('if column.editorClasses is defined', () => {
let columnWithEditorClasses;
const classes = 'test test1';
describe('and it is a function', () => {
beforeEach(() => {
columnWithEditorClasses = {
...column,
editorClasses: jest.fn(() => classes)
};
wrapper = shallow(
<EditingCell
row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ columnWithEditorClasses }
onUpdate={ onUpdate }
onEscape={ onEscape }
/>
);
});
it('should render TextEditor with correct props', () => {
const textEditor = wrapper.find(TextEditor);
expect(textEditor.props().className).toEqual(classes);
});
it('should call column.editorClasses correctly', () => {
expect(columnWithEditorClasses.editorClasses).toHaveBeenCalledTimes(1);
expect(columnWithEditorClasses.editorClasses).toHaveBeenCalledWith(
_.get(row, column.dataField),
row,
rowIndex,
columnIndex
);
});
});
describe('and it is a string', () => {
beforeEach(() => {
columnWithEditorClasses = {
...column,
editorClasses: classes
};
wrapper = shallow(
<EditingCell
row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ columnWithEditorClasses }
onUpdate={ onUpdate }
onEscape={ onEscape }
/>
);
});
it('should render TextEditor with correct props', () => {
const textEditor = wrapper.find(TextEditor);
expect(textEditor.props().className).toEqual(classes);
});
});
});
describe('if column.editorStyle is defined', () => {
let columnWithEditorStyle;
const style = { color: 'red' };
describe('and it is a function', () => {
beforeEach(() => {
columnWithEditorStyle = {
...column,
editorStyle: jest.fn(() => style)
};
wrapper = shallow(
<EditingCell
row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ columnWithEditorStyle }
onUpdate={ onUpdate }
onEscape={ onEscape }
/>
);
});
it('should render TextEditor with correct props', () => {
const textEditor = wrapper.find(TextEditor);
expect(textEditor.props().style).toEqual(style);
});
it('should call column.editorStyle correctly', () => {
expect(columnWithEditorStyle.editorStyle).toHaveBeenCalledTimes(1);
expect(columnWithEditorStyle.editorStyle).toHaveBeenCalledWith(
_.get(row, column.dataField),
row,
rowIndex,
columnIndex
);
});
});
describe('and it is an object', () => {
beforeEach(() => {
columnWithEditorStyle = {
...column,
editorStyle: style
};
wrapper = shallow(
<EditingCell
row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ columnWithEditorStyle }
onUpdate={ onUpdate }
onEscape={ onEscape }
/>
);
});
it('should render TextEditor with correct props', () => {
const textEditor = wrapper.find(TextEditor);
expect(textEditor.props().style).toEqual(style);
});
});
});
describe('if blurToSave prop is true', () => { describe('if blurToSave prop is true', () => {
beforeEach(() => { beforeEach(() => {
wrapper = mount( wrapper = mount(
<TableRowWrapper> <TableRowWrapper>
<EditingCell <EditingCell
row={ row } row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ column } column={ column }
onUpdate={ onUpdate } onUpdate={ onUpdate }
onEscape={ onEscape } onEscape={ onEscape }
@@ -167,6 +304,8 @@ describe('EditingCell', () => {
wrapper = mount( wrapper = mount(
<EditingCell <EditingCell
row={ row } row={ row }
rowIndex={ rowIndex }
columnIndex={ columnIndex }
column={ column } column={ column }
onUpdate={ onUpdate } onUpdate={ onUpdate }
onEscape={ onEscape } onEscape={ onEscape }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -48,6 +48,9 @@ import CustomSelectFilter from 'examples/column-filter/custom-select-filter';
import NumberFilter from 'examples/column-filter/number-filter'; import NumberFilter from 'examples/column-filter/number-filter';
import NumberFilterWithDefaultValue from 'examples/column-filter/number-filter-default-value'; import NumberFilterWithDefaultValue from 'examples/column-filter/number-filter-default-value';
import CustomNumberFilter from 'examples/column-filter/custom-number-filter'; import CustomNumberFilter from 'examples/column-filter/custom-number-filter';
import ProgrammaticallyTextFilter from 'examples/column-filter/programmatically-text-filter';
import ProgrammaticallySelectFilter from 'examples/column-filter/programmatically-select-filter';
import ProgrammaticallyNumberFilter from 'examples/column-filter/programmatically-number-filter';
// work on rows // work on rows
import RowStyleTable from 'examples/rows/row-style'; import RowStyleTable from 'examples/rows/row-style';
@@ -57,6 +60,7 @@ import RowEventTable from 'examples/rows/row-event';
// table sort // table sort
import EnableSortTable from 'examples/sort/enable-sort-table'; import EnableSortTable from 'examples/sort/enable-sort-table';
import DefaultSortTable from 'examples/sort/default-sort-table'; import DefaultSortTable from 'examples/sort/default-sort-table';
import DefaultSortDirectionTable from 'examples/sort/default-sort-direction';
import SortEvents from 'examples/sort/sort-events'; import SortEvents from 'examples/sort/sort-events';
import CustomSortTable from 'examples/sort/custom-sort-table'; import CustomSortTable from 'examples/sort/custom-sort-table';
import HeaderSortingClassesTable from 'examples/sort/header-sorting-classes'; import HeaderSortingClassesTable from 'examples/sort/header-sorting-classes';
@@ -73,6 +77,8 @@ import CellEditHooks from 'examples/cell-edit/cell-edit-hooks-table';
import CellEditValidator from 'examples/cell-edit/cell-edit-validator-table'; import CellEditValidator from 'examples/cell-edit/cell-edit-validator-table';
import CellEditStyleTable from 'examples/cell-edit/cell-edit-style-table'; import CellEditStyleTable from 'examples/cell-edit/cell-edit-style-table';
import CellEditClassTable from 'examples/cell-edit/cell-edit-class-table'; import CellEditClassTable from 'examples/cell-edit/cell-edit-class-table';
import EditorStyleTable from 'examples/cell-edit/editor-style-table';
import EditorClassTable from 'examples/cell-edit/editor-class-table';
// work on row selection // work on row selection
import SingleSelectionTable from 'examples/row-selection/single-selection'; import SingleSelectionTable from 'examples/row-selection/single-selection';
@@ -81,6 +87,7 @@ import ClickToSelectTable from 'examples/row-selection/click-to-select';
import DefaultSelectTable from 'examples/row-selection/default-select'; import DefaultSelectTable from 'examples/row-selection/default-select';
import SelectionManagement from 'examples/row-selection/selection-management'; import SelectionManagement from 'examples/row-selection/selection-management';
import ClickToSelectWithCellEditTable from 'examples/row-selection/click-to-select-with-cell-edit'; import ClickToSelectWithCellEditTable from 'examples/row-selection/click-to-select-with-cell-edit';
import SelectionNoDataTable from 'examples/row-selection/selection-no-data';
import SelectionStyleTable from 'examples/row-selection/selection-style'; import SelectionStyleTable from 'examples/row-selection/selection-style';
import SelectionClassTable from 'examples/row-selection/selection-class'; import SelectionClassTable from 'examples/row-selection/selection-class';
import NonSelectableRowsTable from 'examples/row-selection/non-selectable-rows'; import NonSelectableRowsTable from 'examples/row-selection/non-selectable-rows';
@@ -162,7 +169,10 @@ storiesOf('Column Filter', module)
.add('Custom Text Filter', () => <CustomTextFilter />) .add('Custom Text Filter', () => <CustomTextFilter />)
.add('Custom Select Filter', () => <CustomSelectFilter />) .add('Custom Select Filter', () => <CustomSelectFilter />)
.add('Custom Number Filter', () => <CustomNumberFilter />) .add('Custom Number Filter', () => <CustomNumberFilter />)
.add('Custom Filter Value', () => <CustomFilterValue />); .add('Custom Filter Value', () => <CustomFilterValue />)
.add('Programmatically Text Filter ', () => <ProgrammaticallyTextFilter />)
.add('Programmatically Select Filter ', () => <ProgrammaticallySelectFilter />)
.add('Programmatically Number Filter ', () => <ProgrammaticallyNumberFilter />);
storiesOf('Work on Rows', module) storiesOf('Work on Rows', module)
.add('Customize Row Style', () => <RowStyleTable />) .add('Customize Row Style', () => <RowStyleTable />)
@@ -172,6 +182,7 @@ storiesOf('Work on Rows', module)
storiesOf('Sort Table', module) storiesOf('Sort Table', module)
.add('Enable Sort', () => <EnableSortTable />) .add('Enable Sort', () => <EnableSortTable />)
.add('Default Sort Table', () => <DefaultSortTable />) .add('Default Sort Table', () => <DefaultSortTable />)
.add('Default Sort Direction Table', () => <DefaultSortDirectionTable />)
.add('Sort Events', () => <SortEvents />) .add('Sort Events', () => <SortEvents />)
.add('Custom Sort Fuction', () => <CustomSortTable />) .add('Custom Sort Fuction', () => <CustomSortTable />)
.add('Custom Classes on Sorting Header Column', () => <HeaderSortingClassesTable />) .add('Custom Classes on Sorting Header Column', () => <HeaderSortingClassesTable />)
@@ -186,8 +197,10 @@ storiesOf('Cell Editing', module)
.add('Cell Level Editable', () => <CellLevelEditable />) .add('Cell Level Editable', () => <CellLevelEditable />)
.add('Rich Hook Functions', () => <CellEditHooks />) .add('Rich Hook Functions', () => <CellEditHooks />)
.add('Validation', () => <CellEditValidator />) .add('Validation', () => <CellEditValidator />)
.add('Custom Cell Style When Editing', () => <CellEditStyleTable />) .add('Custom Cell Style', () => <CellEditStyleTable />)
.add('Custom Cell Classes When Editing', () => <CellEditClassTable />); .add('Custom Cell Classes', () => <CellEditClassTable />)
.add('Custom Editor Classes', () => <EditorClassTable />)
.add('Custom Editor Style', () => <EditorStyleTable />);
storiesOf('Row Selection', module) storiesOf('Row Selection', module)
.add('Single Selection', () => <SingleSelectionTable />) .add('Single Selection', () => <SingleSelectionTable />)
@@ -196,6 +209,7 @@ storiesOf('Row Selection', module)
.add('Default Select', () => <DefaultSelectTable />) .add('Default Select', () => <DefaultSelectTable />)
.add('Selection Management', () => <SelectionManagement />) .add('Selection Management', () => <SelectionManagement />)
.add('Click to Select and Edit Cell', () => <ClickToSelectWithCellEditTable />) .add('Click to Select and Edit Cell', () => <ClickToSelectWithCellEditTable />)
.add('Selection without Data', () => <SelectionNoDataTable />)
.add('Selection Style', () => <SelectionStyleTable />) .add('Selection Style', () => <SelectionStyleTable />)
.add('Selection Class', () => <SelectionClassTable />) .add('Selection Class', () => <SelectionClassTable />)
.add('Selection Background Color', () => <SelectionBgColorTable />) .add('Selection Background Color', () => <SelectionBgColorTable />)

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,30 +6,37 @@ import { LIKE, EQ, NE, GT, GE, LT, LE } from './comparison';
export const filterByText = _ => ( export const filterByText = _ => (
data, data,
dataField, dataField,
{ filterVal = '', comparator = LIKE, caseSensitive }, { filterVal: userInput = '', comparator = LIKE, caseSensitive },
customFilterValue customFilterValue
) => ) => {
data.filter((row) => { // make sure filter value to be a string
let cell = _.get(row, dataField); const filterVal = userInput.toString();
if (customFilterValue) {
cell = customFilterValue(cell, row); return (
} data.filter((row) => {
const cellStr = _.isDefined(cell) ? cell.toString() : ''; let cell = _.get(row, dataField);
if (comparator === EQ) { if (customFilterValue) {
return cellStr === filterVal; cell = customFilterValue(cell, row);
} }
if (caseSensitive) { const cellStr = _.isDefined(cell) ? cell.toString() : '';
return cellStr.includes(filterVal); if (comparator === EQ) {
} return cellStr === filterVal;
return cellStr.toLocaleUpperCase().includes(filterVal.toLocaleUpperCase()); }
}); if (caseSensitive) {
return cellStr.includes(filterVal);
}
return cellStr.toLocaleUpperCase().indexOf(filterVal.toLocaleUpperCase()) !== -1;
})
);
};
export const filterByNumber = _ => ( export const filterByNumber = _ => (
data, data,
dataField, dataField,
{ filterVal: { comparator, number } }, { filterVal: { comparator, number } },
customFilterValue customFilterValue
) => ) => (
data.filter((row) => { data.filter((row) => {
if (number === '' || !comparator) return true; if (number === '' || !comparator) return true;
let valid = true; let valid = true;
@@ -81,7 +88,8 @@ export const filterByNumber = _ => (
} }
} }
return valid; return valid;
}); })
);
export const filterFactory = _ => (filterType) => { export const filterFactory = _ => (filterType) => {
let filterFn; let filterFn;
@@ -106,7 +114,13 @@ export const filters = (store, columns, _) => (currFilters) => {
Object.keys(currFilters).forEach((dataField) => { Object.keys(currFilters).forEach((dataField) => {
const filterObj = currFilters[dataField]; const filterObj = currFilters[dataField];
filterFn = factory(filterObj.filterType); filterFn = factory(filterObj.filterType);
const { filterValue } = columns.find(col => col.dataField === dataField); let filterValue;
for (let i = 0; i < columns.length; i += 1) {
if (columns[i].dataField === dataField) {
filterValue = columns[i].filterValue;
break;
}
}
result = filterFn(result, dataField, filterObj, filterValue); result = filterFn(result, dataField, filterObj, filterValue);
}); });
return result; return result;

View File

@@ -40,33 +40,43 @@ export default (Base, {
} }
} }
onFilter(column, filterVal, filterType) { /**
const { store, columns } = this.props; * filter the table like below:
const currFilters = Object.assign({}, this.state.currFilters); * onFilter(column, filterType)(filterVal)
const { dataField, filter } = column; * @param {Object} column
* @param {String} filterType
* @param {String} filterVal - user input for filtering.
*/
onFilter(column, filterType) {
return (filterVal) => {
const { store, columns } = this.props;
const currFilters = Object.assign({}, this.state.currFilters);
const { dataField, filter } = column;
if (!_.isDefined(filterVal) || filterVal === '') { if (!_.isDefined(filterVal) || filterVal === '') {
delete currFilters[dataField]; delete currFilters[dataField];
} else { } else {
// select default comparator is EQ, others are LIKE // select default comparator is EQ, others are LIKE
const { const {
comparator = (filterType === FILTER_TYPE.SELECT ? EQ : LIKE), comparator = (filterType === FILTER_TYPE.SELECT ? EQ : LIKE),
caseSensitive = false caseSensitive = false
} = filter.props; } = filter.props;
currFilters[dataField] = { filterVal, filterType, comparator, caseSensitive }; currFilters[dataField] = { filterVal, filterType, comparator, caseSensitive };
} }
store.filters = currFilters;
if (this.isRemoteFiltering() || this.isRemotePagination()) { store.filters = currFilters;
this.handleRemoteFilterChange();
// when remote filtering is enable, dont set currFilters state
// in the componentWillReceiveProps,
// it's the key point that we can know the filter is changed
return;
}
store.filteredData = filters(store, columns, _)(currFilters); if (this.isRemoteFiltering() || this.isRemotePagination()) {
this.setState(() => ({ currFilters, isDataChanged: true })); this.handleRemoteFilterChange();
// when remote filtering is enable, dont set currFilters state
// in the componentWillReceiveProps,
// it's the key point that we can know the filter is changed
return;
}
store.filteredData = filters(store, columns, _)(currFilters);
this.setState(() => ({ currFilters, isDataChanged: true }));
};
} }
render() { render() {

View File

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

View File

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

View File

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

View File

@@ -42,6 +42,19 @@ describe('filter', () => {
filterFn = filters(store, columns, _); filterFn = filters(store, columns, _);
}); });
describe('when filter value is not a String', () => {
it('should transform to string and do the filter', () => {
currFilters.name = {
filterVal: 3,
filterType: FILTER_TYPE.TEXT
};
const result = filterFn(currFilters);
expect(result).toBeDefined();
expect(result).toHaveLength(2);
});
});
describe(`when default comparator is ${LIKE}`, () => { describe(`when default comparator is ${LIKE}`, () => {
it('should returning correct result', () => { it('should returning correct result', () => {
currFilters.name = { currFilters.name = {

View File

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

View File

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

View File

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

View File

@@ -149,6 +149,7 @@ BootstrapTable.propTypes = {
dataField: PropTypes.string.isRequired, dataField: PropTypes.string.isRequired,
order: PropTypes.oneOf([Const.SORT_DESC, Const.SORT_ASC]).isRequired order: PropTypes.oneOf([Const.SORT_DESC, Const.SORT_ASC]).isRequired
})), })),
defaultSortDirection: PropTypes.oneOf([Const.SORT_DESC, Const.SORT_ASC]),
overlay: PropTypes.func, overlay: PropTypes.func,
onTableChange: PropTypes.func, onTableChange: PropTypes.func,
onSort: PropTypes.func, onSort: PropTypes.func,

View File

@@ -130,6 +130,8 @@ HeaderCell.propTypes = {
editable: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]), editable: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
editCellStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), editCellStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
editCellClasses: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), editCellClasses: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
editorStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
editorClasses: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
validator: PropTypes.func, validator: PropTypes.func,
filter: PropTypes.object, filter: PropTypes.object,
filterValue: PropTypes.func filterValue: PropTypes.func

View File

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

View File

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

View File

@@ -47,7 +47,7 @@ export default ExtendBase =>
} }
if (selectable) { if (selectable) {
const key = _.get(row, keyField); const key = _.get(row, keyField);
onRowSelect(key, !selected, rowIndex); onRowSelect(key, !selected, rowIndex, e);
} }
}; };

View File

@@ -28,7 +28,7 @@ export default class SelectionCell extends Component {
return nextProps.selected !== selected; return nextProps.selected !== selected;
} }
handleClick() { handleClick(e) {
const { const {
mode: inputType, mode: inputType,
rowKey, rowKey,
@@ -46,7 +46,7 @@ export default class SelectionCell extends Component {
? true ? true
: !selected; : !selected;
onRowSelect(rowKey, checked, rowIndex); onRowSelect(rowKey, checked, rowIndex, e);
} }
render() { render() {

View File

@@ -44,10 +44,10 @@ export default class SelectionHeaderCell extends Component {
return nextProps.checkedStatus !== checkedStatus; return nextProps.checkedStatus !== checkedStatus;
} }
handleCheckBoxClick() { handleCheckBoxClick(e) {
const { onAllRowsSelect } = this.props; const { onAllRowsSelect } = this.props;
onAllRowsSelect(); onAllRowsSelect(e);
} }
render() { render() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

@@ -0,0 +1,45 @@
import webpack from 'webpack';
module.exports = {
devtool: 'source-map',
externals: [{
'react': {
root: 'React',
commonjs2: 'react',
commonjs: 'react',
amd: 'react'
}
}, {
'react-dom': {
root: 'ReactDOM',
commonjs2: 'react-dom',
commonjs: 'react-dom',
amd: 'react-dom'
}
}],
module: {
rules: [{
enforce: 'pre',
test: /\.js?$/,
exclude: /node_modules/,
loader: 'eslint-loader'
}, {
test: /\.js?$/,
use: ['babel-loader'],
exclude: /node_modules/
}]
},
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
}),
new webpack.SourceMapDevToolPlugin(),
new webpack.optimize.DedupePlugin(),
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.optimize.AggressiveMergingPlugin(),
new webpack.optimize.UglifyJsPlugin({
include: /\.min\.js$/,
compress: { warnings: false }
})
]
};