diff --git a/assets/index.less b/assets/index.less
index 173d5f1eb..c8d39e65b 100644
--- a/assets/index.less
+++ b/assets/index.less
@@ -38,7 +38,8 @@
overflow: scroll;
}
- &-fixed-header &-scroll &-header {
+ &-fixed-header &-scroll &-header,
+ &-fixed-header &-scroll &-in-table-footer {
overflow-x: scroll;
padding-bottom: 20px;
margin-bottom: -20px;
diff --git a/examples/columnFooter.html b/examples/columnFooter.html
new file mode 100644
index 000000000..e69de29bb
diff --git a/examples/columnFooter.js b/examples/columnFooter.js
new file mode 100644
index 000000000..c23d5701b
--- /dev/null
+++ b/examples/columnFooter.js
@@ -0,0 +1,64 @@
+/* eslint-disable no-console,func-names,react/no-multi-comp */
+import React from 'react';
+import ReactDOM from 'react-dom';
+import Table from 'rc-table';
+import 'rc-table/assets/index.less';
+
+const columns = [
+ {
+ title: 'Name',
+ dataIndex: 'name',
+ width: 100,
+ fixed: 'left',
+ footer: 'Summary',
+ },
+ {
+ title: 'Money',
+ dataIndex: 'money',
+ width: 100,
+ render: text => `$${text.toFixed(2)}`,
+ footer: data => `Total: $${data.reduce((acc, row) => acc + row.money, 0).toFixed(2)}`,
+ },
+ { title: 'Address', dataIndex: 'address', width: 300 },
+];
+
+const data = [
+ {
+ name: 'John Brown',
+ money: 300,
+ address: 'New York No. 1 Lake Park',
+ },
+ {
+ name: 'Jim Green',
+ money: 128,
+ address: 'London No. 1 Lake Park',
+ },
+ {
+ name: 'Joe Black',
+ money: 240,
+ address: 'Sidney No. 1 Lake Park',
+ },
+ {
+ name: 'Mick Sydney',
+ money: 300,
+ address: 'Sidney No. 1 Lake Park',
+ },
+ {
+ name: 'Miguel Manning',
+ money: 120,
+ address: 'Sidney No. 1 Lake Park',
+ },
+ {
+ name: 'John Appleseed',
+ money: 256,
+ address: '1 Infinite Loop; Cupertino, CA 95014',
+ },
+];
+
+ReactDOM.render(
+
+
Demonstrate column footer
+
+
,
+ document.getElementById('__react-content')
+);
diff --git a/src/BaseTable.js b/src/BaseTable.js
index 18d91610f..0d1be2418 100644
--- a/src/BaseTable.js
+++ b/src/BaseTable.js
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { connect } from 'mini-store';
import ColGroup from './ColGroup';
import TableHeader from './TableHeader';
+import TableFooter from './TableFooter';
import TableRow from './TableRow';
import ExpandableRow from './ExpandableRow';
@@ -16,6 +17,7 @@ class BaseTable extends React.Component {
tableClassName: PropTypes.string.isRequired,
hasHead: PropTypes.bool.isRequired,
hasBody: PropTypes.bool.isRequired,
+ hasFoot: PropTypes.bool.isRequired,
store: PropTypes.object.isRequired,
expander: PropTypes.object.isRequired,
getRowKey: PropTypes.func,
@@ -134,7 +136,7 @@ class BaseTable extends React.Component {
const { table } = this.context;
const { components } = table;
const { prefixCls, scroll, data, getBodyWrapper } = table.props;
- const { expander, tableClassName, hasHead, hasBody, fixed, columns } = this.props;
+ const { expander, tableClassName, hasHead, hasBody, hasFoot, fixed, columns } = this.props;
const tableStyle = {};
if (!fixed && scroll.x) {
@@ -165,6 +167,7 @@ class BaseTable extends React.Component {
{hasHead && }
+ {hasFoot && }
{body}
);
diff --git a/src/BodyTable.js b/src/BodyTable.js
index 3648dafb5..bc3f806cc 100644
--- a/src/BodyTable.js
+++ b/src/BodyTable.js
@@ -51,6 +51,7 @@ export default function BodyTable(props, { table }) {
tableClassName={tableClassName}
hasHead={!useFixedHeader}
hasBody
+ hasFoot={!useFixedHeader}
fixed={fixed}
columns={columns}
expander={expander}
diff --git a/src/Column.js b/src/Column.js
index f891975b1..8b4d898ae 100644
--- a/src/Column.js
+++ b/src/Column.js
@@ -6,6 +6,7 @@ export default class Column extends Component {
className: PropTypes.string,
colSpan: PropTypes.number,
title: PropTypes.node,
+ footer: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
dataIndex: PropTypes.string,
width: PropTypes.oneOfType([
PropTypes.number,
diff --git a/src/FootTable.js b/src/FootTable.js
new file mode 100644
index 000000000..c20549a1d
--- /dev/null
+++ b/src/FootTable.js
@@ -0,0 +1,61 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { measureScrollbar } from './utils';
+import BaseTable from './BaseTable';
+
+export default function FootTable(props, { table }) {
+ const { prefixCls, scroll } = table.props;
+ const { columns, fixed, tableClassName, handleBodyScrollLeft, expander } = props;
+ const { saveRef } = table;
+ let { useFixedHeader } = table.props;
+ const footStyle = {};
+
+ if (scroll.y) {
+ useFixedHeader = true;
+ // Add negative margin bottom for scroll bar overflow bug
+ const scrollbarWidth = measureScrollbar();
+ if (scrollbarWidth > 0 && !fixed) {
+ footStyle.marginBottom = `-${scrollbarWidth}px`;
+ footStyle.paddingBottom = '0px';
+ }
+ }
+
+ if (!useFixedHeader) {
+ return null;
+ }
+
+ return (
+
+
+
+ );
+}
+
+FootTable.propTypes = {
+ fixed: PropTypes.oneOfType([
+ PropTypes.string,
+ PropTypes.bool,
+ ]),
+ columns: PropTypes.array.isRequired,
+ tableClassName: PropTypes.string.isRequired,
+ handleBodyScrollLeft: PropTypes.func.isRequired,
+ expander: PropTypes.object.isRequired,
+};
+
+FootTable.contextTypes = {
+ table: PropTypes.any,
+};
diff --git a/src/HeadTable.js b/src/HeadTable.js
index 5b427f9e6..6c206aff5 100644
--- a/src/HeadTable.js
+++ b/src/HeadTable.js
@@ -36,6 +36,7 @@ export default function HeadTable(props, { table }) {
tableClassName={tableClassName}
hasHead
hasBody={false}
+ hasFoot={false}
fixed={fixed}
columns={columns}
expander={expander}
diff --git a/src/Table.js b/src/Table.js
index 8d9ecea1b..3c5350e95 100644
--- a/src/Table.js
+++ b/src/Table.js
@@ -9,6 +9,7 @@ import ColumnManager from './ColumnManager';
import classes from 'component-classes';
import HeadTable from './HeadTable';
import BodyTable from './BodyTable';
+import FootTable from './FootTable';
import ExpandableTable from './ExpandableTable';
export default class Table extends React.Component {
@@ -49,6 +50,11 @@ export default class Table extends React.Component {
row: PropTypes.any,
cell: PropTypes.any,
}),
+ footer: PropTypes.shape({
+ wrapper: PropTypes.any,
+ row: PropTypes.any,
+ cell: PropTypes.any,
+ }),
}),
...ExpandableTable.PropTypes,
}
@@ -126,6 +132,11 @@ export default class Table extends React.Component {
row: 'tr',
cell: 'td',
},
+ footer: {
+ wrapper: 'tfoot',
+ row: 'tr',
+ cell: 'td',
+ },
}, this.props.components),
},
};
@@ -260,6 +271,9 @@ export default class Table extends React.Component {
if (this.bodyTable) {
this.bodyTable.scrollLeft = 0;
}
+ if (this.footTable) {
+ this.footTable.scrollLeft = 0;
+ }
}
hasScrollX() {
@@ -274,12 +288,17 @@ export default class Table extends React.Component {
}
const target = e.target;
const { scroll = {} } = this.props;
- const { headTable, bodyTable } = this;
+ const { headTable, bodyTable, footTable } = this;
if (target.scrollLeft !== this.lastScrollLeft && scroll.x) {
- if (target === bodyTable && headTable) {
- headTable.scrollLeft = target.scrollLeft;
- } else if (target === headTable && bodyTable) {
- bodyTable.scrollLeft = target.scrollLeft;
+ if (target === bodyTable) {
+ if (headTable) headTable.scrollLeft = target.scrollLeft;
+ if (footTable) footTable.scrollLeft = target.scrollLeft;
+ } else if (target === headTable) {
+ if (bodyTable) bodyTable.scrollLeft = target.scrollLeft;
+ if (footTable) footTable.scrollLeft = target.scrollLeft;
+ } else if (target === footTable) {
+ if (bodyTable) bodyTable.scrollLeft = target.scrollLeft;
+ if (headTable) headTable.scrollLeft = target.scrollLeft;
}
this.setScrollPositionClassName();
}
@@ -390,7 +409,18 @@ export default class Table extends React.Component {
/>
);
- return [headTable, bodyTable];
+ const footTable = (
+
+ );
+
+ return [headTable, bodyTable, footTable];
}
renderTitle() {
diff --git a/src/TableFooter.js b/src/TableFooter.js
new file mode 100644
index 000000000..65fb8c347
--- /dev/null
+++ b/src/TableFooter.js
@@ -0,0 +1,64 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+function getColumnFooter(col) {
+ if (typeof col.footer === 'function') return col.footer;
+ return () => col.footer;
+}
+
+export default function TableFooter(props, { table }) {
+ const { columnManager, components } = table;
+ const { prefixCls, data, expandIconAsCell } = table.props;
+ const { fixed } = props;
+
+ if (!columnManager.leafColumns().some(col => col.footer)) {
+ return null;
+ }
+
+ const expandIconCol = {
+ key: 'expand-icon-placeholder',
+ render: () => null,
+ };
+
+ let leafColumns;
+ if (fixed === 'left') {
+ leafColumns = columnManager.leftLeafColumns();
+ if (expandIconAsCell) {
+ leafColumns = [expandIconCol, ...leafColumns];
+ }
+ } else if (fixed === 'right') {
+ leafColumns = columnManager.rightLeafColumns();
+ } else {
+ leafColumns = columnManager.leafColumns();
+ if (expandIconAsCell) {
+ leafColumns = [expandIconCol, ...leafColumns];
+ }
+ }
+
+ const FooterWrapper = components.footer.wrapper;
+ const FooterRow = components.footer.row;
+ const FooterCell = components.footer.cell;
+
+ return (
+
+
+ {leafColumns.map(col =>
+
+ {col.footer === undefined ? null : getColumnFooter(col)(data)}
+
+ )}
+
+
+ );
+}
+
+TableFooter.propTypes = {
+ fixed: PropTypes.string,
+ columns: PropTypes.array.isRequired,
+};
+
+TableFooter.contextTypes = {
+ table: PropTypes.any,
+};