diff --git a/packages/react-bootstrap-table2-example/examples/footer/column-align-table.js b/packages/react-bootstrap-table2-example/examples/footer/column-align-table.js
new file mode 100644
index 0000000..bb57fc6
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/footer/column-align-table.js
@@ -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'
+}];
+
+
+`;
+
+export default () => (
+
+
+ { sourceCode }
+
+);
diff --git a/packages/react-bootstrap-table2-example/examples/footer/column-format-table.js b/packages/react-bootstrap-table2-example/examples/footer/column-format-table.js
new file mode 100644
index 0000000..26fbbc0
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/footer/column-format-table.js
@@ -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 (
+
+ $$ {column.text} $$
+
+ );
+}
+
+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 (
+ $$ { column.text } $$
+ );
+}
+
+const columns = [
+// omit...
+{
+ dataField: 'price',
+ text: 'Product Price',
+ footer: 'Footer 3',
+ footerFormatter: priceFormatter
+}];
+
+
+`;
+
+export default () => (
+
+
+ {sourceCode}
+
+);
diff --git a/packages/react-bootstrap-table2-example/examples/footer/footer-class-table.js b/packages/react-bootstrap-table2-example/examples/footer/footer-class-table.js
new file mode 100644
index 0000000..3f3cd62
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/footer/footer-class-table.js
@@ -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'
+ }
+ ];
+
+
+`;
+
+export default () => (
+
+
+ {sourceCode}
+
+);
diff --git a/packages/react-bootstrap-table2-example/examples/footer/function-footer.js b/packages/react-bootstrap-table2-example/examples/footer/function-footer.js
new file mode 100644
index 0000000..f30fe97
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/footer/function-footer.js
@@ -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)
+ }];
+
+
+`;
+
+export default () => (
+
+
+ {sourceCode}
+
+);
diff --git a/packages/react-bootstrap-table2-example/examples/footer/simple-footer.js b/packages/react-bootstrap-table2-example/examples/footer/simple-footer.js
new file mode 100644
index 0000000..b15e862
--- /dev/null
+++ b/packages/react-bootstrap-table2-example/examples/footer/simple-footer.js
@@ -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'
+ }];
+
+
+`;
+
+export default () => (
+
+
+ {sourceCode}
+
+);
diff --git a/packages/react-bootstrap-table2-example/stories/index.js b/packages/react-bootstrap-table2-example/stories/index.js
index 0461549..c2b6186 100644
--- a/packages/react-bootstrap-table2-example/stories/index.js
+++ b/packages/react-bootstrap-table2-example/stories/index.js
@@ -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 ', () => );
+storiesOf('Welcome', module).add('react bootstrap table 2 ', () => );
storiesOf('Basic Table', module)
.addDecorator(bootstrapStyle())
@@ -286,6 +292,14 @@ storiesOf('Work on Rows', module)
.add('Hide Rows', () => )
.add('Row Event', () => );
+storiesOf('Footer', module)
+ .addDecorator(bootstrapStyle())
+ .add('Simple Footer', () => )
+ .add('Function Footer', () => )
+ .add('Footer Class', () => )
+ .add('Column Formatter', () => )
+ .add('Column Align', () => );
+
storiesOf('Sort Table', module)
.addDecorator(bootstrapStyle())
.add('Enable Sort', () => )
diff --git a/packages/react-bootstrap-table2-example/stories/stylesheet/columns/_index.scss b/packages/react-bootstrap-table2-example/stories/stylesheet/columns/_index.scss
index a0ec3d1..e2700ac 100644
--- a/packages/react-bootstrap-table2-example/stories/stylesheet/columns/_index.scss
+++ b/packages/react-bootstrap-table2-example/stories/stylesheet/columns/_index.scss
@@ -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;
}
\ No newline at end of file
diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js
index 7c3a727..c79fdc1 100644
--- a/packages/react-bootstrap-table2/src/bootstrap-table.js
+++ b/packages/react-bootstrap-table2/src/bootstrap-table.js
@@ -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 (
-
- { this.renderTable() }
-
- );
+ return {this.renderTable()};
}
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 });
+ const hasFooter = _.filter(columns, col => _.has(col, 'footer')).length > 0;
+
+ const tableCaption = caption && {caption};
return (
- { tableCaption }
+ {tableCaption}
+ {hasFooter && (
+
+ )}
);
@@ -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,
diff --git a/packages/react-bootstrap-table2/src/footer-cell.js b/packages/react-bootstrap-table2/src/footer-cell.js
new file mode 100644
index 0000000..2995f4a
--- /dev/null
+++ b/packages/react-bootstrap-table2/src/footer-cell.js
@@ -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;
diff --git a/packages/react-bootstrap-table2/src/footer.js b/packages/react-bootstrap-table2/src/footer.js
new file mode 100644
index 0000000..38b2bde
--- /dev/null
+++ b/packages/react-bootstrap-table2/src/footer.js
@@ -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 (
+
+
+ {columns.map((column, i) => {
+ if (column.footer === undefined || column.footer === null || column.hidden) {
+ return false;
+ }
+
+ const columnData = _.pluck(data, column.dataField);
+
+ return (
+
+ );
+ })}
+
+
+ );
+};
+
+Footer.propTypes = {
+ data: PropTypes.array,
+ className: PropTypes.string,
+ columns: PropTypes.array
+};
+
+export default Footer;
diff --git a/packages/react-bootstrap-table2/test/footer.test.js b/packages/react-bootstrap-table2/test/footer.test.js
new file mode 100644
index 0000000..9f1024b
--- /dev/null
+++ b/packages/react-bootstrap-table2/test/footer.test.js
@@ -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();
+ });
+
+ 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();
+ });
+
+ it('should render successfully', () => {
+ expect(wrapper.length).toBe(1);
+ expect(wrapper.find(`.${className}`).length).toBe(1);
+ });
+ });
+});