Merge pull request #798 from react-bootstrap-table/develop

20190216 release
This commit is contained in:
Allen
2019-02-16 17:09:40 +08:00
committed by GitHub
11 changed files with 525 additions and 34 deletions

View File

@@ -0,0 +1,70 @@
/* eslint react/prop-types: 0 */
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, { textFilter } from 'react-bootstrap-table2-filter';
import { productsGenerator } from 'utils/common';
const ProductList = (props) => {
const columns = [
{
dataField: 'id',
text: 'Product ID'
},
{
dataField: 'name',
text: 'Product Name',
filter: textFilter({
defaultValue: '1'
})
},
{
dataField: 'price',
text: 'Product Price',
filter: textFilter()
}
];
return (
<div style={ { paddingTop: '20px' } }>
<h1 className="h2">Products</h1>
<BootstrapTable
keyField="id"
data={ props.products }
columns={ columns }
filter={ filterFactory() }
/>
</div>
);
};
export default class DataContainer extends React.Component {
state = {
products: productsGenerator(3)
};
loadData = () => {
this.setState({
products: productsGenerator(14)
});
}
render() {
return (
<div>
<button
onClick={ this.loadData }
style={ {
fontSize: '20px',
position: 'absolute',
left: '200px',
top: '40px'
} }
>
Load Data
</button>
<ProductList products={ this.state.products } />
</div>
);
}
}

View File

@@ -0,0 +1,80 @@
/* eslint react/prop-types: 0 */
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit';
import { productsGenerator } from 'utils/common';
const { SearchBar } = Search;
const ProductList = (props) => {
const columns = [
{
dataField: 'id',
text: 'Product ID'
},
{
dataField: 'name',
text: 'Product Name'
},
{
dataField: 'price',
text: 'Product Price'
}
];
return (
<div style={ { paddingTop: '20px' } }>
<h1 className="h2">Products</h1>
<ToolkitProvider
keyField="id"
data={ props.products }
columns={ columns }
search={ { defaultSearch: '2101' } }
>
{
toolkitprops => (
<div>
<SearchBar { ...toolkitprops.searchProps } />
<BootstrapTable
striped
hover
{ ...toolkitprops.baseProps }
/>
</div>
)
}
</ToolkitProvider>
</div>
);
};
export default class DataContainer extends React.Component {
state = {
products: productsGenerator(3)
};
loadData = () => {
this.setState({
products: productsGenerator(14)
});
}
render() {
return (
<div>
<button
onClick={ this.loadData }
style={ {
fontSize: '20px',
position: 'absolute',
left: '200px',
top: '40px'
} }
>
Load Data
</button>
<ProductList products={ this.state.products } />
</div>
);
}
}

View File

@@ -0,0 +1,68 @@
/* eslint react/prop-types: 0 */
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, { textFilter } from 'react-bootstrap-table2-filter';
import { productsGenerator } from 'utils/common';
const ProductList = (props) => {
const columns = [
{
dataField: 'id',
text: 'Product ID'
},
{
dataField: 'name',
text: 'Product Name',
filter: textFilter()
},
{
dataField: 'price',
text: 'Product Price',
filter: textFilter()
}
];
return (
<div style={ { paddingTop: '20px' } }>
<h1 className="h2">Products</h1>
<BootstrapTable
keyField="id"
data={ props.products }
columns={ columns }
filter={ filterFactory() }
/>
</div>
);
};
export default class DataContainer extends React.Component {
state = {
products: []
};
loadData = () => {
this.setState({
products: productsGenerator()
});
}
render() {
return (
<div>
<button
onClick={ this.loadData }
style={ {
fontSize: '20px',
position: 'absolute',
left: '200px',
top: '40px'
} }
>
Load Data
</button>
<ProductList products={ this.state.products } />
</div>
);
}
}

View File

@@ -0,0 +1,72 @@
/* eslint react/prop-types: 0 */
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import filterFactory, { textFilter } from 'react-bootstrap-table2-filter';
import paginationFactory from 'react-bootstrap-table2-paginator';
import { productsGenerator } from 'utils/common';
const ProductList = (props) => {
const columns = [
{
dataField: 'id',
text: 'Product ID'
},
{
dataField: 'name',
text: 'Product Name',
filter: textFilter({
defaultValue: '6'
})
},
{
dataField: 'price',
text: 'Product Price',
filter: textFilter()
}
];
return (
<div style={ { paddingTop: '20px' } }>
<h1 className="h2">Products</h1>
<BootstrapTable
keyField="id"
data={ props.products }
columns={ columns }
filter={ filterFactory() }
pagination={ paginationFactory() }
/>
</div>
);
};
export default class DataContainer extends React.Component {
state = {
products: productsGenerator(60)
};
loadData = () => {
this.setState({
products: productsGenerator(14)
});
}
render() {
return (
<div>
<button
onClick={ this.loadData }
style={ {
fontSize: '20px',
position: 'absolute',
left: '200px',
top: '40px'
} }
>
Load Data
</button>
<ProductList products={ this.state.products } />
</div>
);
}
}

View File

@@ -0,0 +1,80 @@
/* eslint react/prop-types: 0 */
import React from 'react';
import BootstrapTable from 'react-bootstrap-table-next';
import ToolkitProvider, { Search } from 'react-bootstrap-table2-toolkit';
import { productsGenerator } from 'utils/common';
const { SearchBar } = Search;
const ProductList = (props) => {
const columns = [
{
dataField: 'id',
text: 'Product ID'
},
{
dataField: 'name',
text: 'Product Name'
},
{
dataField: 'price',
text: 'Product Price'
}
];
return (
<div style={ { paddingTop: '20px' } }>
<h1 className="h2">Products</h1>
<ToolkitProvider
keyField="id"
data={ props.products }
columns={ columns }
search
>
{
toolkitprops => (
<div>
<SearchBar { ...toolkitprops.searchProps } />
<BootstrapTable
striped
hover
{ ...toolkitprops.baseProps }
/>
</div>
)
}
</ToolkitProvider>
</div>
);
};
export default class DataContainer extends React.Component {
state = {
products: []
};
loadData = () => {
this.setState({
products: productsGenerator()
});
}
render() {
return (
<div>
<button
onClick={ this.loadData }
style={ {
fontSize: '20px',
position: 'absolute',
left: '200px',
top: '40px'
} }
>
Load Data
</button>
<ProductList products={ this.state.products } />
</div>
);
}
}

View File

@@ -205,6 +205,13 @@ import RemoteSearch from 'examples/remote/remote-search';
import RemoteCellEdit from 'examples/remote/remote-celledit';
import RemoteAll from 'examples/remote/remote-all';
// data
import LoadDataWithFilter from 'examples/data/load-data-on-the-fly-with-filter';
import LoadDataWithDefaultFilter from 'examples/data/load-data-on-the-fly-with-default-filter';
import LoadDataWithSearch from 'examples/data/load-data-on-the-fly-with-search';
import LoadDataWithDefaultSearch from 'examples/data/load-data-on-the-fly-with-default-search';
import LoadDataWithPaginationAndFilter from 'examples/data/load-data-on-the-fly-with-pagination-and-filter';
// css style
import 'stories/stylesheet/tomorrow.min.css';
import 'stories/stylesheet/storybook.scss';
@@ -433,3 +440,11 @@ storiesOf('Remote', module)
.add('Remote Search', () => <RemoteSearch />)
.add('Remote Cell Editing', () => <RemoteCellEdit />)
.add('Remote All', () => <RemoteAll />);
storiesOf('Data', module)
.addDecorator(bootstrapStyle())
.add('Load data with Filter', () => <LoadDataWithFilter />)
.add('Load data with Default Filter', () => <LoadDataWithDefaultFilter />)
.add('Load data with Search', () => <LoadDataWithSearch />)
.add('Load data with Default Search', () => <LoadDataWithDefaultSearch />)
.add('Load data with Filter and Pagination', () => <LoadDataWithPaginationAndFilter />);

View File

@@ -25,6 +25,7 @@ export default (
super(props);
this.currFilters = {};
this.onFilter = this.onFilter.bind(this);
this.doFilter = this.doFilter.bind(this);
this.onExternalFilter = this.onExternalFilter.bind(this);
this.state = {
data: props.data
@@ -38,11 +39,13 @@ export default (
}
componentWillReceiveProps(nextProps) {
if (isRemoteFiltering()) {
this.setState({
data: nextProps.data
});
let nextData = nextProps.data;
if (!isRemoteFiltering() && !_.isEqual(nextProps.data, this.props.data)) {
nextData = this.doFilter(nextProps);
}
this.setState({
data: nextData
});
}
onFilter(column, filterType, initialize = false) {
@@ -81,11 +84,7 @@ export default (
result = filter.props.onFilter(filterVal);
}
const { dataChangeListener, data } = this.props;
result = result || filters(data, this.props.columns, _)(this.currFilters);
if (dataChangeListener) {
dataChangeListener.emit('filterChanged', result.length);
}
result = this.doFilter(this.props, result);
this.setState({ data: result });
};
}
@@ -96,6 +95,17 @@ export default (
};
}
doFilter(props, customResult) {
let result = customResult;
const { dataChangeListener, data, columns } = props;
result = result || filters(data, columns, _)(this.currFilters);
if (dataChangeListener) {
dataChangeListener.emit('filterChanged', result.length);
}
return result;
}
render() {
return (
<FilterContext.Provider value={ {

View File

@@ -2,6 +2,7 @@
/* eslint react/require-default-props: 0 */
/* eslint no-continue: 0 */
/* eslint no-lonely-if: 0 */
/* eslint class-methods-use-this: 0 */
import React from 'react';
import PropTypes from 'prop-types';
@@ -28,7 +29,7 @@ export default (options = {
if (isRemoteSearch() && this.props.searchText !== '') {
handleRemoteSearchChange(this.props.searchText);
} else {
initialData = this.search(props.searchText.toLowerCase());
initialData = this.search(props);
this.triggerListener(initialData);
}
this.state = { data: initialData };
@@ -39,7 +40,7 @@ export default (options = {
if (isRemoteSearch()) {
handleRemoteSearchChange(nextProps.searchText);
} else {
const result = this.search(nextProps.searchText.toLowerCase());
const result = this.search(nextProps);
this.triggerListener(result);
this.setState({
data: result
@@ -48,6 +49,12 @@ export default (options = {
} else {
if (isRemoteSearch()) {
this.setState({ data: nextProps.data });
} else if (!_.isEqual(nextProps.data, this.props.data)) {
const result = this.search(nextProps);
this.triggerListener(result);
this.setState({
data: result
});
}
}
}
@@ -58,8 +65,9 @@ export default (options = {
}
}
search(searchText) {
const { data, columns } = this.props;
search(props) {
const { data, columns } = props;
const searchText = props.searchText.toLowerCase();
return data.filter((row, ridx) => {
for (let cidx = 0; cidx < columns.length; cidx += 1) {
const column = columns[cidx];

View File

@@ -106,6 +106,8 @@ class BootstrapTable extends PropsBaseResolver(Component) {
<Footer
data={ this.getData() }
columns={ columns }
selectRow={ selectRow }
expandRow={ expandRow }
className={ this.props.footerClasses }
/>
)}

View File

@@ -2,31 +2,52 @@
import React from 'react';
import PropTypes from 'prop-types';
import Const from './const';
import FooterCell from './footer-cell';
import _ from './utils';
const Footer = (props) => {
const { data, className, columns } = props;
const { data, className, columns, selectRow, expandRow } = props;
const SelectionFooterCellComp = () => <th />;
const ExpansionFooterCellComp = () => <th />;
const isRenderExpandColumnInLeft = (
expandColumnPosition = Const.INDICATOR_POSITION_LEFT
) => expandColumnPosition === Const.INDICATOR_POSITION_LEFT;
const childrens = columns.map((column, i) => {
if (column.footer === undefined || column.footer === null || column.hidden) {
return false;
}
const columnData = _.pluck(data, column.dataField);
return (
<FooterCell
index={ i }
key={ column.dataField }
column={ column }
columnData={ columnData }
/>
);
});
if (selectRow && selectRow.hideSelectColumn !== true) {
childrens.unshift(<SelectionFooterCellComp key="selection" />);
}
if (expandRow.showExpandColumn) {
if (isRenderExpandColumnInLeft(expandRow.expandColumnPosition)) {
childrens.unshift(<ExpansionFooterCellComp key="expansion" />);
} else {
childrens.push(<ExpansionFooterCellComp key="expansion" />);
}
}
return (
<tfoot>
<tr className={ className }>
{columns.map((column, i) => {
if (column.footer === undefined || column.footer === null || column.hidden) {
return false;
}
const columnData = _.pluck(data, column.dataField);
return (
<FooterCell
index={ i }
key={ column.dataField }
column={ column }
columnData={ columnData }
/>
);
})}
{ childrens }
</tr>
</tfoot>
);
@@ -35,7 +56,9 @@ const Footer = (props) => {
Footer.propTypes = {
data: PropTypes.array,
className: PropTypes.string,
columns: PropTypes.array
columns: PropTypes.array,
selectRow: PropTypes.object,
expandRow: PropTypes.object
};
export default Footer;

View File

@@ -1,8 +1,9 @@
/* eslint no-unused-vars: 0 */
import 'jsdom-global/register';
import React from 'react';
import { shallow, mount } from 'enzyme';
import { shallow, render } from 'enzyme';
import Const from '../src/const';
import Footer from '../src/footer';
import FooterCell from '../src/footer-cell';
@@ -32,11 +33,29 @@ describe('Footer', () => {
}
];
const selectRow = {
mode: Const.ROW_SELECT_DISABLED,
selected: [],
hideSelectColumn: true
};
const expandRow = {
renderer: undefined,
expanded: [],
nonExpandable: []
};
const keyField = 'id';
describe('simplest footer', () => {
beforeEach(() => {
wrapper = shallow(<Footer data={ data } columns={ columns } />);
wrapper = shallow(
<Footer
data={ data }
columns={ columns }
selectRow={ selectRow }
expandRow={ expandRow }
/>
);
});
it('should render successfully', () => {
@@ -50,7 +69,15 @@ describe('Footer', () => {
const className = 'test-class';
beforeEach(() => {
wrapper = shallow(<Footer data={ data } columns={ columns } className={ className } />);
wrapper = shallow(
<Footer
data={ data }
columns={ columns }
className={ className }
selectRow={ selectRow }
expandRow={ expandRow }
/>
);
});
it('should render successfully', () => {
@@ -58,4 +85,40 @@ describe('Footer', () => {
expect(wrapper.find(`.${className}`).length).toBe(1);
});
});
describe('when selecrRow prop is enable', () => {
beforeEach(() => {
wrapper = render(
<Footer
data={ data }
columns={ columns }
selectRow={ { ...selectRow, mode: 'radio', hideSelectColumn: false } }
expandRow={ expandRow }
/>
);
});
it('should render successfully', () => {
expect(wrapper.length).toBe(1);
expect(wrapper.find('th').length).toBe(columns.length + 1);
});
});
describe('when expandRow prop is enable', () => {
beforeEach(() => {
wrapper = render(
<Footer
data={ data }
columns={ columns }
selectRow={ selectRow }
expandRow={ { expandRow, showExpandColumn: true } }
/>
);
});
it('should render successfully', () => {
expect(wrapper.length).toBe(1);
expect(wrapper.find('th').length).toBe(columns.length + 1);
});
});
});