diff --git a/packages/react-bootstrap-table2/src/bootstrap-table.js b/packages/react-bootstrap-table2/src/bootstrap-table.js index 7f3a16c..e3dd58d 100644 --- a/packages/react-bootstrap-table2/src/bootstrap-table.js +++ b/packages/react-bootstrap-table2/src/bootstrap-table.js @@ -72,6 +72,7 @@ class BootstrapTable extends PropsBaseResolver(Component) { }); const tableCaption = (caption && { caption }); + const expandRow = this.resolveExpandRowProps(); return (
@@ -86,6 +87,7 @@ class BootstrapTable extends PropsBaseResolver(Component) { onFilter={ this.props.onFilter } onExternalFilter={ this.props.onExternalFilter } selectRow={ headerCellSelectionInfo } + expandRow={ expandRow } /> ({ expanded: currExpanded })); } + handleAllRowExpand = (e, expandAll) => { + const { + data, + keyField, + expandRow: { + onExpandAll, + nonExpandable + } + } = this.props; + const { expanded } = this.state; + + let currExpanded; + + if (expandAll) { + currExpanded = expanded.concat(dataOperator.expandableKeys(data, keyField, nonExpandable)); + } else { + currExpanded = expanded.filter(s => typeof data.find(d => d[keyField] === s) === 'undefined'); + } + + if (onExpandAll) { + onExpandAll(expandAll, dataOperator.getExpandedRows(data, keyField, currExpanded), e); + } + + this.setState(() => ({ expanded: currExpanded })); + } + render() { + const { data, keyField } = this.props; return ( { this.props.children } diff --git a/packages/react-bootstrap-table2/src/header.js b/packages/react-bootstrap-table2/src/header.js index 0b8ee80..67568ea 100644 --- a/packages/react-bootstrap-table2/src/header.js +++ b/packages/react-bootstrap-table2/src/header.js @@ -5,6 +5,7 @@ import Const from './const'; import HeaderCell from './header-cell'; import SelectionHeaderCell from './row-selection/selection-header-cell'; +import ExpandHeaderCell from './row-expand/expand-header-cell'; const Header = (props) => { const { ROW_SELECT_DISABLED } = Const; @@ -17,12 +18,21 @@ const Header = (props) => { sortField, sortOrder, selectRow, - onExternalFilter + onExternalFilter, + expandRow } = props; return ( + { + (expandRow && expandRow.showExpandColumn) + ? : null + } { (selectRow.mode !== ROW_SELECT_DISABLED && !selectRow.hideSelectColumn) ? : null @@ -62,7 +72,8 @@ Header.propTypes = { sortOrder: PropTypes.string, selectRow: PropTypes.object, onExternalFilter: PropTypes.func, - className: PropTypes.string + className: PropTypes.string, + expandRow: PropTypes.object }; export default Header; diff --git a/packages/react-bootstrap-table2/src/props-resolver/expand-row-resolver.js b/packages/react-bootstrap-table2/src/props-resolver/expand-row-resolver.js index c959776..015e321 100644 --- a/packages/react-bootstrap-table2/src/props-resolver/expand-row-resolver.js +++ b/packages/react-bootstrap-table2/src/props-resolver/expand-row-resolver.js @@ -1,12 +1,14 @@ export default ExtendBase => class ExpandRowResolver extends ExtendBase { resolveExpandRowProps() { - const { expandRow, expanded, onRowExpand } = this.props; + const { expandRow, expanded, onRowExpand, onAllRowExpand, isAnyExpands } = this.props; if (expandRow) { return { ...expandRow, expanded, onRowExpand, + onAllRowExpand, + isAnyExpands, nonExpandable: expandRow.nonExpandable || [] }; } diff --git a/packages/react-bootstrap-table2/src/row-expand/expand-cell.js b/packages/react-bootstrap-table2/src/row-expand/expand-cell.js new file mode 100644 index 0000000..3df5b1c --- /dev/null +++ b/packages/react-bootstrap-table2/src/row-expand/expand-cell.js @@ -0,0 +1,42 @@ +/* eslint + react/require-default-props: 0 + jsx-a11y/no-noninteractive-element-interactions: 0 +*/ +/* eslint no-nested-ternary: 0 */ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +export default class ExpandCell extends Component { + static propTypes = { + rowKey: PropTypes.any, + expanded: PropTypes.bool.isRequired, + onRowExpand: PropTypes.func.isRequired, + expandColumnRenderer: PropTypes.func, + rowIndex: PropTypes.number + } + + constructor() { + super(); + this.handleClick = this.handleClick.bind(this); + } + + handleClick(e) { + const { rowKey, expanded, onRowExpand, rowIndex } = this.props; + + onRowExpand(rowKey, expanded, rowIndex, e); + } + + render() { + const { expanded, expandColumnRenderer } = this.props; + + return ( + + { + expandColumnRenderer ? expandColumnRenderer({ + expanded + }) : (expanded ? '(-)' : '(+)') + } + + ); + } +} diff --git a/packages/react-bootstrap-table2/src/row-expand/expand-header-cell.js b/packages/react-bootstrap-table2/src/row-expand/expand-header-cell.js new file mode 100644 index 0000000..65c099b --- /dev/null +++ b/packages/react-bootstrap-table2/src/row-expand/expand-header-cell.js @@ -0,0 +1,40 @@ +/* eslint react/require-default-props: 0 */ +/* eslint no-nested-ternary: 0 */ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +export default class SelectionHeaderCell extends Component { + static propTypes = { + anyExpands: PropTypes.bool.isRequired, + onAllRowExpand: PropTypes.func.isRequired, + renderer: PropTypes.func + } + + constructor() { + super(); + this.handleCheckBoxClick = this.handleCheckBoxClick.bind(this); + } + + handleCheckBoxClick(e) { + const { anyExpands, onAllRowExpand } = this.props; + + onAllRowExpand(e, !anyExpands); + } + + render() { + const { anyExpands, renderer } = this.props; + const attrs = { + onClick: this.handleCheckBoxClick + }; + + return ( + + { + renderer ? + renderer({ isAnyExpands: anyExpands }) : + (anyExpands ? '(-)' : '(+)') + } + + ); + } +} diff --git a/packages/react-bootstrap-table2/src/row.js b/packages/react-bootstrap-table2/src/row.js index b6222bb..1196716 100644 --- a/packages/react-bootstrap-table2/src/row.js +++ b/packages/react-bootstrap-table2/src/row.js @@ -6,6 +6,7 @@ import PropTypes from 'prop-types'; import _ from './utils'; import Cell from './cell'; import SelectionCell from './row-selection/selection-cell'; +import ExpandCell from './row-expand/expand-cell'; import eventDelegater from './row-event-delegater'; import Const from './const'; @@ -22,6 +23,8 @@ class Row extends eventDelegater(Component) { cellEdit, selected, selectRow, + expanded, + expandRow, selectable, editable: editableRow } = this.props; @@ -39,10 +42,21 @@ class Row extends eventDelegater(Component) { const key = _.get(row, keyField); const { hideSelectColumn } = selectRow; + const { showExpandColumn } = expandRow || {}; const trAttrs = this.delegate(attrs); return ( + { + showExpandColumn ? ( + + ) : null + } { (selectRow.mode !== Const.ROW_SELECT_DISABLED && !hideSelectColumn) ? ( diff --git a/packages/react-bootstrap-table2/src/store/expand.js b/packages/react-bootstrap-table2/src/store/expand.js new file mode 100644 index 0000000..31feb0a --- /dev/null +++ b/packages/react-bootstrap-table2/src/store/expand.js @@ -0,0 +1,28 @@ +import _ from '../utils'; +import { getRowByRowId } from './rows'; + +export const isAnyExpands = ( + data, + keyField, + expanded = [] +) => { + for (let i = 0; i < data.length; i += 1) { + const rowKey = _.get(data[i], keyField); + if (typeof expanded.find(x => x === rowKey) !== 'undefined') { + return true; + } + } + return false; +}; + +export const expandableKeys = (data, keyField, skips = []) => { + if (skips.length === 0) { + return data.map(row => _.get(row, keyField)); + } + return data + .filter(row => !skips.includes(_.get(row, keyField))) + .map(row => _.get(row, keyField)); +}; + +export const getExpandedRows = (data, keyField, expanded) => + expanded.map(k => getRowByRowId(data, keyField, k)); diff --git a/packages/react-bootstrap-table2/src/store/operators.js b/packages/react-bootstrap-table2/src/store/operators.js index c01ddad..de055ee 100644 --- a/packages/react-bootstrap-table2/src/store/operators.js +++ b/packages/react-bootstrap-table2/src/store/operators.js @@ -1,11 +1,13 @@ import * as rows from './rows'; import * as selection from './selection'; +import * as expand from './expand'; import * as mutate from './mutate'; import * as sort from './sort'; export default { ...rows, ...selection, + ...expand, ...mutate, ...sort };