mirror of
https://github.com/gosticks/react-bootstrap-table2.git
synced 2025-10-16 11:55:39 +00:00
Add table footer
This commit is contained in:
parent
416fcf08d4
commit
bd2ce5abf0
52
packages/react-bootstrap-table2-example/examples/footer/column-align-table.js
vendored
Normal file
52
packages/react-bootstrap-table2-example/examples/footer/column-align-table.js
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
/* eslint no-unused-vars: 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',
|
||||
footerAlign: 'center',
|
||||
footer: 'Footer 1'
|
||||
}, {
|
||||
dataField: 'name',
|
||||
text: 'Product Name',
|
||||
footerAlign: (column, colIndex) => 'right',
|
||||
footer: 'Footer 2'
|
||||
}, {
|
||||
dataField: 'price',
|
||||
text: 'Product Price',
|
||||
footer: 'Footer 3'
|
||||
}];
|
||||
|
||||
const sourceCode = `\
|
||||
import BootstrapTable from 'react-bootstrap-table-next';
|
||||
|
||||
const columns = [{
|
||||
dataField: 'id',
|
||||
text: 'Product ID',
|
||||
footerAlign: 'center',
|
||||
footer: 'Footer 1'
|
||||
}, {
|
||||
dataField: 'name',
|
||||
text: 'Product Name',
|
||||
footerAlign: (column, colIndex) => 'right',
|
||||
footer: 'Footer 2'
|
||||
}, {
|
||||
dataField: 'price',
|
||||
text: 'Product Price'
|
||||
}];
|
||||
|
||||
<BootstrapTable keyField='id' data={ products } columns={ columns } />
|
||||
`;
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<BootstrapTable keyField="id" data={ products } columns={ columns } />
|
||||
<Code>{ sourceCode }</Code>
|
||||
</div>
|
||||
);
|
||||
67
packages/react-bootstrap-table2-example/examples/footer/column-format-table.js
vendored
Normal file
67
packages/react-bootstrap-table2-example/examples/footer/column-format-table.js
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
/* eslint no-unused-vars: 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();
|
||||
|
||||
function priceFormatter(column, colIndex) {
|
||||
return (
|
||||
<h5>
|
||||
<strong>$$ {column.text} $$</strong>
|
||||
</h5>
|
||||
);
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{
|
||||
dataField: 'id',
|
||||
text: 'Product ID',
|
||||
footer: 'Footer 1'
|
||||
},
|
||||
{
|
||||
dataField: 'name',
|
||||
text: 'Product Name',
|
||||
footer: 'Footer 2'
|
||||
},
|
||||
{
|
||||
dataField: 'price',
|
||||
text: 'Product Price',
|
||||
footer: 'Footer 3',
|
||||
footerFormatter: priceFormatter
|
||||
}
|
||||
];
|
||||
|
||||
const sourceCode = `\
|
||||
import BootstrapTable from 'react-bootstrap-table-next';
|
||||
|
||||
function priceFormatter(column, colIndex) {
|
||||
return (
|
||||
<h5><strong>$$ { column.text } $$</strong></h5>
|
||||
);
|
||||
}
|
||||
|
||||
const columns = [
|
||||
// omit...
|
||||
{
|
||||
dataField: 'price',
|
||||
text: 'Product Price',
|
||||
footer: 'Footer 3',
|
||||
footerFormatter: priceFormatter
|
||||
}];
|
||||
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
/>
|
||||
`;
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<BootstrapTable keyField="id" data={ products } columns={ columns } />
|
||||
<Code>{sourceCode}</Code>
|
||||
</div>
|
||||
);
|
||||
61
packages/react-bootstrap-table2-example/examples/footer/footer-class-table.js
vendored
Normal file
61
packages/react-bootstrap-table2-example/examples/footer/footer-class-table.js
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
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',
|
||||
footer: 'Footer 1'
|
||||
},
|
||||
{
|
||||
dataField: 'name',
|
||||
text: 'Product Name',
|
||||
footer: 'Footer 2'
|
||||
},
|
||||
{
|
||||
dataField: 'price',
|
||||
text: 'Product Price',
|
||||
footer: 'Footer 3'
|
||||
}
|
||||
];
|
||||
|
||||
const sourceCode = `\
|
||||
import BootstrapTable from 'react-bootstrap-table-next';
|
||||
|
||||
const columns = [
|
||||
{
|
||||
dataField: 'id',
|
||||
text: 'Product ID',
|
||||
footer: 'Footer 1'
|
||||
},
|
||||
{
|
||||
dataField: 'name',
|
||||
text: 'Product Name',
|
||||
footer: 'Footer 2'
|
||||
},
|
||||
{
|
||||
dataField: 'price',
|
||||
text: 'Product Price',
|
||||
footer: 'Footer 3'
|
||||
}
|
||||
];
|
||||
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
footerClasses="footer-class"
|
||||
/>
|
||||
`;
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<BootstrapTable keyField="id" data={ products } columns={ columns } footerClasses="footer-class" />
|
||||
<Code>{sourceCode}</Code>
|
||||
</div>
|
||||
);
|
||||
52
packages/react-bootstrap-table2-example/examples/footer/function-footer.js
vendored
Normal file
52
packages/react-bootstrap-table2-example/examples/footer/function-footer.js
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
/* eslint no-unused-vars: 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',
|
||||
footer: 'Footer 1'
|
||||
},
|
||||
{
|
||||
dataField: 'name',
|
||||
text: 'Product Name',
|
||||
footer: 'Footer 2'
|
||||
},
|
||||
{
|
||||
dataField: 'price',
|
||||
text: 'Product Price',
|
||||
footer: columnData => columnData.reduce((acc, item) => acc + item, 0)
|
||||
}
|
||||
];
|
||||
|
||||
const sourceCode = `\
|
||||
import BootstrapTable from 'react-bootstrap-table-next';
|
||||
|
||||
const columns = [
|
||||
// omit...
|
||||
{
|
||||
dataField: 'price',
|
||||
text: 'Product Price',
|
||||
footer: columnData => columnData.reduce((acc, item) => acc + item, 0)
|
||||
}];
|
||||
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
/>
|
||||
`;
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<BootstrapTable keyField="id" data={ products } columns={ columns } />
|
||||
<Code>{sourceCode}</Code>
|
||||
</div>
|
||||
);
|
||||
52
packages/react-bootstrap-table2-example/examples/footer/simple-footer.js
vendored
Normal file
52
packages/react-bootstrap-table2-example/examples/footer/simple-footer.js
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
/* eslint no-unused-vars: 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',
|
||||
footer: 'Footer 1'
|
||||
},
|
||||
{
|
||||
dataField: 'name',
|
||||
text: 'Product Name',
|
||||
footer: 'Footer 2'
|
||||
},
|
||||
{
|
||||
dataField: 'price',
|
||||
text: 'Product Price',
|
||||
footer: 'Footer 3'
|
||||
}
|
||||
];
|
||||
|
||||
const sourceCode = `\
|
||||
import BootstrapTable from 'react-bootstrap-table-next';
|
||||
|
||||
const columns = [
|
||||
// omit...
|
||||
{
|
||||
dataField: 'price',
|
||||
text: 'Product Price',
|
||||
footer: 'Footer 3'
|
||||
}];
|
||||
|
||||
<BootstrapTable
|
||||
keyField="id"
|
||||
data={ products }
|
||||
columns={ columns }
|
||||
/>
|
||||
`;
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<BootstrapTable keyField="id" data={ products } columns={ columns } />
|
||||
<Code>{sourceCode}</Code>
|
||||
</div>
|
||||
);
|
||||
@ -45,6 +45,13 @@ import HeaderColumnStyleTable from 'examples/header-columns/column-style-table';
|
||||
import HeaderColumnAttrsTable from 'examples/header-columns/column-attrs-table';
|
||||
import HeaderClassTable from 'examples/header-columns/header-class-table';
|
||||
|
||||
// footer
|
||||
import SimpleFooter from 'examples/footer/simple-footer';
|
||||
import FunctionFooter from 'examples/footer/function-footer';
|
||||
import FooterClassTable from 'examples/footer/footer-class-table';
|
||||
import FooterColumnFormatTable from 'examples/footer/column-format-table';
|
||||
import FooterColumnAlignTable from 'examples/footer/column-align-table';
|
||||
|
||||
// column filter
|
||||
import TextFilter from 'examples/column-filter/text-filter';
|
||||
import TextFilterWithDefaultValue from 'examples/column-filter/text-filter-default-value';
|
||||
@ -199,8 +206,7 @@ import '../../react-bootstrap-table2-filter/style/react-bootstrap-table2-filter.
|
||||
// import bootstrap style by given version
|
||||
import bootstrapStyle, { BOOTSTRAP_VERSION } from './bootstrap-style';
|
||||
|
||||
storiesOf('Welcome', module)
|
||||
.add('react bootstrap table 2 ', () => <Welcome />);
|
||||
storiesOf('Welcome', module).add('react bootstrap table 2 ', () => <Welcome />);
|
||||
|
||||
storiesOf('Basic Table', module)
|
||||
.addDecorator(bootstrapStyle())
|
||||
@ -286,6 +292,14 @@ storiesOf('Work on Rows', module)
|
||||
.add('Hide Rows', () => <RowHiddenTable />)
|
||||
.add('Row Event', () => <RowEventTable />);
|
||||
|
||||
storiesOf('Footer', module)
|
||||
.addDecorator(bootstrapStyle())
|
||||
.add('Simple Footer', () => <SimpleFooter />)
|
||||
.add('Function Footer', () => <FunctionFooter />)
|
||||
.add('Footer Class', () => <FooterClassTable />)
|
||||
.add('Column Formatter', () => <FooterColumnFormatTable />)
|
||||
.add('Column Align', () => <FooterColumnAlignTable />);
|
||||
|
||||
storiesOf('Sort Table', module)
|
||||
.addDecorator(bootstrapStyle())
|
||||
.add('Enable Sort', () => <EnableSortTable />)
|
||||
|
||||
@ -11,6 +11,10 @@
|
||||
background-color: $green-lighten-4;
|
||||
}
|
||||
|
||||
.header-class {
|
||||
thead .header-class {
|
||||
background-color: $green-lighten-4;
|
||||
}
|
||||
|
||||
tfoot .footer-class {
|
||||
background-color: $green-lighten-4;
|
||||
}
|
||||
@ -7,8 +7,10 @@ import cs from 'classnames';
|
||||
import Header from './header';
|
||||
import Caption from './caption';
|
||||
import Body from './body';
|
||||
import Footer from './footer';
|
||||
import PropsBaseResolver from './props-resolver';
|
||||
import Const from './const';
|
||||
import _ from './utils';
|
||||
|
||||
class BootstrapTable extends PropsBaseResolver(Component) {
|
||||
constructor(props) {
|
||||
@ -25,11 +27,7 @@ class BootstrapTable extends PropsBaseResolver(Component) {
|
||||
const { loading, overlay } = this.props;
|
||||
if (overlay) {
|
||||
const LoadingOverlay = overlay(loading);
|
||||
return (
|
||||
<LoadingOverlay>
|
||||
{ this.renderTable() }
|
||||
</LoadingOverlay>
|
||||
);
|
||||
return <LoadingOverlay>{this.renderTable()}</LoadingOverlay>;
|
||||
}
|
||||
return this.renderTable();
|
||||
}
|
||||
@ -59,19 +57,25 @@ class BootstrapTable extends PropsBaseResolver(Component) {
|
||||
|
||||
const tableWrapperClass = cs('react-bootstrap-table', wrapperClasses);
|
||||
|
||||
const tableClass = cs('table', {
|
||||
'table-striped': striped,
|
||||
'table-hover': hover,
|
||||
'table-bordered': bordered,
|
||||
[(bootstrap4 ? 'table-sm' : 'table-condensed')]: condensed
|
||||
}, classes);
|
||||
const tableClass = cs(
|
||||
'table',
|
||||
{
|
||||
'table-striped': striped,
|
||||
'table-hover': hover,
|
||||
'table-bordered': bordered,
|
||||
[bootstrap4 ? 'table-sm' : 'table-condensed']: condensed
|
||||
},
|
||||
classes
|
||||
);
|
||||
|
||||
const tableCaption = (caption && <Caption>{ caption }</Caption>);
|
||||
const hasFooter = _.filter(columns, col => _.has(col, 'footer')).length > 0;
|
||||
|
||||
const tableCaption = caption && <Caption>{caption}</Caption>;
|
||||
|
||||
return (
|
||||
<div className={ tableWrapperClass }>
|
||||
<table id={ id } className={ tableClass }>
|
||||
{ tableCaption }
|
||||
{tableCaption}
|
||||
<Header
|
||||
columns={ columns }
|
||||
className={ this.props.headerClasses }
|
||||
@ -98,6 +102,9 @@ class BootstrapTable extends PropsBaseResolver(Component) {
|
||||
rowClasses={ rowClasses }
|
||||
rowEvents={ rowEvents }
|
||||
/>
|
||||
{hasFooter && (
|
||||
<Footer data={ data } columns={ columns } className={ this.props.footerClasses } />
|
||||
)}
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
@ -109,9 +116,12 @@ BootstrapTable.propTypes = {
|
||||
data: PropTypes.array.isRequired,
|
||||
columns: PropTypes.array.isRequired,
|
||||
bootstrap4: PropTypes.bool,
|
||||
remote: PropTypes.oneOfType([PropTypes.bool, PropTypes.shape({
|
||||
pagination: PropTypes.bool
|
||||
})]),
|
||||
remote: PropTypes.oneOfType([
|
||||
PropTypes.bool,
|
||||
PropTypes.shape({
|
||||
pagination: PropTypes.bool
|
||||
})
|
||||
]),
|
||||
noDataIndication: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
||||
striped: PropTypes.bool,
|
||||
bordered: PropTypes.bool,
|
||||
@ -121,10 +131,7 @@ BootstrapTable.propTypes = {
|
||||
classes: PropTypes.string,
|
||||
wrapperClasses: PropTypes.string,
|
||||
condensed: PropTypes.bool,
|
||||
caption: PropTypes.oneOfType([
|
||||
PropTypes.node,
|
||||
PropTypes.string
|
||||
]),
|
||||
caption: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
|
||||
pagination: PropTypes.object,
|
||||
filter: PropTypes.object,
|
||||
cellEdit: PropTypes.object,
|
||||
@ -168,10 +175,13 @@ BootstrapTable.propTypes = {
|
||||
rowEvents: PropTypes.object,
|
||||
rowClasses: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
|
||||
headerClasses: PropTypes.string,
|
||||
defaultSorted: PropTypes.arrayOf(PropTypes.shape({
|
||||
dataField: PropTypes.string.isRequired,
|
||||
order: PropTypes.oneOf([Const.SORT_DESC, Const.SORT_ASC]).isRequired
|
||||
})),
|
||||
footerClasses: PropTypes.string,
|
||||
defaultSorted: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
dataField: PropTypes.string.isRequired,
|
||||
order: PropTypes.oneOf([Const.SORT_DESC, Const.SORT_ASC]).isRequired
|
||||
})
|
||||
),
|
||||
defaultSortDirection: PropTypes.oneOf([Const.SORT_DESC, Const.SORT_ASC]),
|
||||
overlay: PropTypes.func,
|
||||
onTableChange: PropTypes.func,
|
||||
|
||||
64
packages/react-bootstrap-table2/src/footer-cell.js
vendored
Normal file
64
packages/react-bootstrap-table2/src/footer-cell.js
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
/* eslint react/require-default-props: 0 */
|
||||
import React from 'react';
|
||||
import cs from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import _ from './utils';
|
||||
|
||||
const FooterCell = (props) => {
|
||||
const { index, column, columnData } = props;
|
||||
|
||||
const {
|
||||
footer,
|
||||
footerTitle,
|
||||
footerAlign,
|
||||
footerFormatter,
|
||||
footerEvents,
|
||||
footerClasses,
|
||||
footerStyle,
|
||||
footerAttrs
|
||||
} = column;
|
||||
|
||||
const cellAttrs = {
|
||||
...(_.isFunction(footerAttrs) ? footerAttrs(column, index) : footerAttrs),
|
||||
...footerEvents
|
||||
};
|
||||
|
||||
let text = '';
|
||||
if (_.isString(footer)) {
|
||||
text = footer;
|
||||
} else if (_.isFunction(footer)) {
|
||||
text = footer(columnData, column, index);
|
||||
}
|
||||
|
||||
let cellStyle = {};
|
||||
const cellClasses = _.isFunction(footerClasses) ? footerClasses(column, index) : footerClasses;
|
||||
|
||||
if (footerStyle) {
|
||||
cellStyle = _.isFunction(footerStyle) ? footerStyle(column, index) : footerStyle;
|
||||
cellStyle = cellStyle ? { ...cellStyle } : cellStyle;
|
||||
}
|
||||
|
||||
if (footerTitle) {
|
||||
cellAttrs.title = _.isFunction(footerTitle) ? footerTitle(column, index) : text;
|
||||
}
|
||||
|
||||
if (footerAlign) {
|
||||
cellStyle.textAlign = _.isFunction(footerAlign) ? footerAlign(column, index) : footerAlign;
|
||||
}
|
||||
|
||||
if (cellClasses) cellAttrs.className = cs(cellAttrs.className, cellClasses);
|
||||
if (!_.isEmptyObject(cellStyle)) cellAttrs.style = cellStyle;
|
||||
|
||||
const children = footerFormatter ? footerFormatter(column, index) : text;
|
||||
|
||||
return React.createElement('th', cellAttrs, children);
|
||||
};
|
||||
|
||||
FooterCell.propTypes = {
|
||||
columnData: PropTypes.array,
|
||||
index: PropTypes.number,
|
||||
column: PropTypes.object
|
||||
};
|
||||
|
||||
export default FooterCell;
|
||||
41
packages/react-bootstrap-table2/src/footer.js
vendored
Normal file
41
packages/react-bootstrap-table2/src/footer.js
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
/* eslint react/require-default-props: 0 */
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import FooterCell from './footer-cell';
|
||||
import _ from './utils';
|
||||
|
||||
const Footer = (props) => {
|
||||
const { data, className, columns } = props;
|
||||
|
||||
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 }
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
</tfoot>
|
||||
);
|
||||
};
|
||||
|
||||
Footer.propTypes = {
|
||||
data: PropTypes.array,
|
||||
className: PropTypes.string,
|
||||
columns: PropTypes.array
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
61
packages/react-bootstrap-table2/test/footer.test.js
Normal file
61
packages/react-bootstrap-table2/test/footer.test.js
Normal file
@ -0,0 +1,61 @@
|
||||
/* eslint no-unused-vars: 0 */
|
||||
import 'jsdom-global/register';
|
||||
import React from 'react';
|
||||
import { shallow, mount } from 'enzyme';
|
||||
|
||||
import Footer from '../src/footer';
|
||||
import FooterCell from '../src/footer-cell';
|
||||
|
||||
describe('Footer', () => {
|
||||
let wrapper;
|
||||
const columns = [
|
||||
{
|
||||
dataField: 'id',
|
||||
text: 'ID',
|
||||
footer: 'Footer 1'
|
||||
},
|
||||
{
|
||||
dataField: 'name',
|
||||
text: 'Name',
|
||||
footer: (columnData, column) => 'Footer 2'
|
||||
}
|
||||
];
|
||||
|
||||
const data = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'A'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'B'
|
||||
}
|
||||
];
|
||||
|
||||
const keyField = 'id';
|
||||
|
||||
describe('simplest header', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<Footer data={ data } columns={ columns } />);
|
||||
});
|
||||
|
||||
it('should render successfully', () => {
|
||||
expect(wrapper.length).toBe(1);
|
||||
expect(wrapper.find('tr').length).toBe(1);
|
||||
expect(wrapper.find(FooterCell).length).toBe(columns.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe('className prop is exists', () => {
|
||||
const className = 'test-class';
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<Footer data={ data } columns={ columns } className={ className } />);
|
||||
});
|
||||
|
||||
it('should render successfully', () => {
|
||||
expect(wrapper.length).toBe(1);
|
||||
expect(wrapper.find(`.${className}`).length).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user