Add table footer

This commit is contained in:
Eduardo Reveles 2018-12-06 15:05:41 -06:00
parent 416fcf08d4
commit bd2ce5abf0
11 changed files with 505 additions and 27 deletions

View 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>
);

View 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>
);

View 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>
);

View 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>
);

View 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>
);

View File

@ -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 />)

View File

@ -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;
}

View File

@ -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,

View 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;

View 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;

View 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);
});
});
});