diff --git a/docs/src/App.js b/docs/src/App.js index 70e68b1..87a409c 100644 --- a/docs/src/App.js +++ b/docs/src/App.js @@ -66,6 +66,7 @@ const stories = [ { name: 'Select Table (HOC)', component: CodeSandbox('7yq5ylw09j') }, { name: 'Select Tree Table (HOC)', component: CodeSandbox('2p7jp4klwp') }, { name: 'Foldable Table (HOC)', component: CodeSandbox('2p7jp4klwp') }, + { name: 'Advanced Expand Table (HOC)', component: CodeSandbox('y2m39jz8v1') }, ] export default class App extends React.Component { diff --git a/docs/src/examples/advancedexpandtable/index.js b/docs/src/examples/advancedexpandtable/index.js new file mode 100644 index 0000000..b3d60da --- /dev/null +++ b/docs/src/examples/advancedexpandtable/index.js @@ -0,0 +1,116 @@ +import React from 'react'; +import { render } from 'react-dom'; +import { makeData, Logo, Tips } from './Utils'; +import { advancedExpandTableHOC } from '../../../../lib/hoc/advancedExpandTable'; + +// Import React Table +import ReactTable from 'react-table'; +import 'react-table/react-table.css'; + +const columns = [ + { + Header: 'First Name', + accessor: 'firstName', + Cell: props => { + const { + // react table props + columnProps: { rest: { showRowSubComponent } }, + nestingPath + } = props; + return ( +
+ +
+ ); + } + }, + { + Header: 'Last Name', + id: 'lastName', + accessor: d => d.lastName + }, + { + Header: 'Info', + columns: [ + { + Header: 'Age', + accessor: 'age', + Cell: props => { + const { + // react table props + columnProps: { rest: { toggleRowSubComponent } }, + nestingPath + } = props; + return ( +
+ +
+ ); + } + }, + { + Header: 'Status', + accessor: 'status' + } + ] + }, + { + Header: 'Stats', + columns: [ + { + Header: 'Visits', + accessor: 'visits' + } + ] + } +]; + +const AdvancedExpandReactTable = advancedExpandTableHOC(ReactTable); + +class App extends React.Component { + constructor(props) { + super(props); + this.state = { + data: makeData() + }; + } + render() { + const { data } = this.state; + return ( +
+ { + return ( +
+ +
+ ); + }} + /> +
+ + +
+ ); + } +} + +render(, document.getElementById('root')); \ No newline at end of file diff --git a/src/hoc/README.md b/src/hoc/README.md index a68b6bc..0d01d39 100644 --- a/src/hoc/README.md +++ b/src/hoc/README.md @@ -205,6 +205,71 @@ const defaultFoldButtonComponent = ({ header, collapsed, icon, onClick }) => { } ``` +### AdvancedExpandTable + +HOC which allows any Cell in the row to toggle the row's +SubComponent (expand/collapse). Also allows the SubComponent to toggle itself. Technically supports toggling any row's SubComponent. + +Expand functions available to any SubComponent or Column Cell: + toggleRowSubComponent + showRowSubComponent + hideRowSubComponent + +They are available through the `props.columnProps.rest` object. + +On any change to the props, the table will reset the expanded state. + +Accepts a onExpandedChange callback to be called whenever the table expanded state changes + +Note: only supports 1 level of nesting. + +Example usage in a Column Cell Renderer: + +```javascript + Cell: props => { + const { + value + columnProps: { rest: { showRowSubComponent } }, + nestingPath + } = props; + return ( +
+ +
+ ); + } +``` + +Example usage in the ReactTable SubComponent (toggle itself): + +```javascript + const AdvancedExpandReactTable = advancedExpandTableHOC(ReactTable); + + + ... + SubComponent={({ row, nestingPath, toggleRowSubComponent }) => { + return ( +
+ +
+ ); + }} + /> +``` + +Each Column Renderer (E.g. Cell ) gets the expand functions in its props and each SubComponent gets the expand functions in its props + +Expand functions takes the `nestingPath` or `rowInfo` given to each +Column Renderer and SubComponent already by ReactTable. + ## HOC Guide for ReactTable There are a few rules required when writing a HOC for ReactTable (other than meeting the normal lint standards - which are diff --git a/src/hoc/advancedExpandTable/index.js b/src/hoc/advancedExpandTable/index.js new file mode 100644 index 0000000..4162bd4 --- /dev/null +++ b/src/hoc/advancedExpandTable/index.js @@ -0,0 +1,199 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import set from 'lodash.set' +import get from 'lodash.get' + +/* + AvancedExpandTableHOC for ReactTable + + HOC which allows any Cell in the row to toggle the row's + SubComponent. Also allows the SubComponent to toggle itself. + + Expand functions available to any SubComponent or Column Cell: + toggleRowSubComponent + showRowSubComponent + hideRowSubComponent + + Each Column Renderer (E.g. Cell ) gets the expand functions in its props + And Each SubComponent gets the expand functions in its props + + Expand functions takes the `rowInfo` given to each + Column Renderer and SubComponent already by ReactTable. +*/ + +export const subComponentWithToggle = (SubComponent, expandFuncs) => props => ( + +) + +// each cell in the column gets passed the function to toggle a sub component +export const columnsWithToggle = (columns, expandFuncs) => + columns.map(column => { + if (column.columns) { + return { + ...column, + columns: columnsWithToggle(column.columns, expandFuncs), + } + } + return { + ...column, + getProps () { + return { + ...expandFuncs, + } + }, + } + }) + +export const advancedExpandTableHOC = TableComponent => + class AdvancedExpandTable extends Component { + constructor (props) { + super(props) + this.state = { + expanded: {}, + } + this.toggleRowSubComponent = this.toggleRowSubComponent.bind(this) + this.showRowSubComponent = this.showRowSubComponent.bind(this) + this.hideRowSubComponent = this.hideRowSubComponent.bind(this) + this.getTdProps = this.getTdProps.bind(this) + this.fireOnExpandedChange = this.fireOnExpandedChange.bind(this) + this.expandFuncs = { + toggleRowSubComponent: this.toggleRowSubComponent, + showRowSubComponent: this.showRowSubComponent, + hideRowSubComponent: this.hideRowSubComponent, + } + } + + // after initial render if we get new + // data, columns, page changes, etc. + // we reset expanded state. + componentWillReceiveProps () { + this.setState({ + expanded: {}, + }) + } + + static propTypes = { + columns: PropTypes.array.isRequired, + SubComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]) + .isRequired, + onExpandedChange: PropTypes.func, + }; + + static defaultProps = { + onExpandedChange: null, + }; + + static DisplayName = 'AdvancedExpandTable'; + + // since we pass the expand functions to each Cell, + // we need to filter it out from being passed as an + // actual DOM attribute. See getProps in columnsWithToggle above. + static TdComponent ({ + toggleRowSubComponent, + showRowSubComponent, + hideRowSubComponent, + ...rest + }) { + return + } + + fireOnExpandedChange (rowInfo, e) { + // fire callback once state has changed. + if (this.props.onExpandedChange) { + this.props.onExpandedChange(rowInfo, e) + } + } + + resolveNewTableState (rowInfoOrNestingPath, e, expandType) { + // derive nestingPath if only rowInfo is passed + let nestingPath = rowInfoOrNestingPath + + if (rowInfoOrNestingPath.nestingPath) { + nestingPath = rowInfoOrNestingPath.nestingPath + } + + this.setState( + prevState => { + const isExpanded = get(prevState.expanded, nestingPath) + // since we do not support nested rows, a shallow clone is okay. + const newExpanded = { ...prevState.expanded } + + switch (expandType) { + case 'show': + set(newExpanded, nestingPath, {}) + break + case 'hide': + set(newExpanded, nestingPath, false) + break + default: + // toggle + set(newExpanded, nestingPath, isExpanded ? false : {}) + } + return { + ...prevState, + expanded: newExpanded, + } + }, + () => this.fireOnExpandedChange(rowInfoOrNestingPath, e) + ) + } + + toggleRowSubComponent (rowInfo, e) { + this.resolveNewTableState(rowInfo, e) + } + + showRowSubComponent (rowInfo, e) { + this.resolveNewTableState(rowInfo, e, 'show') + } + + hideRowSubComponent (rowInfo, e) { + this.resolveNewTableState(rowInfo, e, 'hide') + } + + getTdProps (tableState, rowInfo, column) { + const { expander } = column + + if (!expander) { + // no overrides + return {} + } + + return { + // only override onClick for column Td + onClick: e => { + this.toggleRowSubComponent(rowInfo, e) + }, + } + } + + getWrappedInstance () { + if (!this.wrappedInstance) { console.warn('AdvancedExpandTable - No wrapped instance') } + if (this.wrappedInstance.getWrappedInstance) { + return this.wrappedInstance.getWrappedInstance() + } + return this.wrappedInstance + } + + render () { + const { + columns, SubComponent, onExpandedChange, ...rest + } = this.props + + const wrappedColumns = columnsWithToggle(columns, this.expandFuncs) + const WrappedSubComponent = subComponentWithToggle( + SubComponent, + this.expandFuncs + ) + + return ( + + ) + } + }