From bd2ce5abf09180647ac3309e65e8225e3e233b1d Mon Sep 17 00:00:00 2001 From: Eduardo Reveles Date: Thu, 6 Dec 2018 15:05:41 -0600 Subject: [PATCH 1/3] Add table footer --- .../examples/footer/column-align-table.js | 52 ++++++++++++++ .../examples/footer/column-format-table.js | 67 +++++++++++++++++++ .../examples/footer/footer-class-table.js | 61 +++++++++++++++++ .../examples/footer/function-footer.js | 52 ++++++++++++++ .../examples/footer/simple-footer.js | 52 ++++++++++++++ .../stories/index.js | 18 ++++- .../stories/stylesheet/columns/_index.scss | 6 +- .../src/bootstrap-table.js | 58 +++++++++------- .../react-bootstrap-table2/src/footer-cell.js | 64 ++++++++++++++++++ packages/react-bootstrap-table2/src/footer.js | 41 ++++++++++++ .../test/footer.test.js | 61 +++++++++++++++++ 11 files changed, 505 insertions(+), 27 deletions(-) create mode 100644 packages/react-bootstrap-table2-example/examples/footer/column-align-table.js create mode 100644 packages/react-bootstrap-table2-example/examples/footer/column-format-table.js create mode 100644 packages/react-bootstrap-table2-example/examples/footer/footer-class-table.js create mode 100644 packages/react-bootstrap-table2-example/examples/footer/function-footer.js create mode 100644 packages/react-bootstrap-table2-example/examples/footer/simple-footer.js create mode 100644 packages/react-bootstrap-table2/src/footer-cell.js create mode 100644 packages/react-bootstrap-table2/src/footer.js create mode 100644 packages/react-bootstrap-table2/test/footer.test.js 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 000000000..bb57fc6ae --- /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 000000000..26fbbc08b --- /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 000000000..3f3cd62f7 --- /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 000000000..f30fe974b --- /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 000000000..b15e862d5 --- /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 046154938..c2b6186b1 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 a0ec3d187..e2700ac33 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 7c3a72704..c79fdc1cd 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 hasFooter = _.filter(columns, col => _.has(col, 'footer')).length > 0; - const tableCaption = (caption && { caption }); + 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 000000000..2995f4aa7 --- /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 000000000..38b2bded6 --- /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 000000000..9f1024b01 --- /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(