diff --git a/src/defaultProps.js b/src/defaultProps.js index b9a8f6a..2f3583d 100644 --- a/src/defaultProps.js +++ b/src/defaultProps.js @@ -95,6 +95,10 @@ export default { className: '', style: {}, getProps: emptyObj, + // Pivot only + aggregate: undefined, + pivotRender: undefined, // this is a dynamic default, set at run-time in methods.js to display PivotComponent + pivotPreviewRender: undefined, // this is a dynamic default, set at run-time in methods.js to display PivotComponent // Headers only header: undefined, headerClassName: '', @@ -123,13 +127,7 @@ export default { sortable: false, width: 35, hideFilter: true - // render: will be overriden in methods.js to display ExpanderComponent - }, - - // Global Pivot Column Defaults - pivotDefaults: { - filterRender: undefined - // render: will be overriden in methods.js to display ExpanderComponent and PivotValueComponent + // render: undefined // this is a dynamic default, set at run-time in methods.js to display ExpanderComponent }, // Text @@ -170,6 +168,16 @@ export default { PivotValueComponent: ({subRows, value}) => ( {value} {subRows && `(${subRows.length})`} ), + PivotPreviewComponent: ({subRows, column}) => { + const previewValues = subRows.slice(0, 3).map((row, i) => ( + {row[column.id]}, + )) + return ( + {previewValues}... + ) + }, + PivotComponent: undefined, // this is a computed default generated using + // the ExpanderComponent and PivotValueComponent at run-time in methods.js PaginationComponent: Pagination, PreviousComponent: undefined, NextComponent: undefined, diff --git a/src/index.js b/src/index.js index 77a272e..4db0069 100644 --- a/src/index.js +++ b/src/index.js @@ -87,6 +87,7 @@ export default class ReactTable extends Methods(Lifecycle(Component)) { pages, // Pivoting State pivotValKey, + pivotIDKey, pivotBy, subRowsKey, expandedRows, @@ -105,6 +106,9 @@ export default class ReactTable extends Methods(Lifecycle(Component)) { SubComponent, NoDataComponent, ResizerComponent, + ExpanderComponent, + PivotValueComponent, + PivotPreviewComponent, // Data model resolvedData, allVisibleColumns, @@ -314,9 +318,9 @@ export default class ReactTable extends Methods(Lifecycle(Component)) { /> ) : null - if (column.pivotColumns) { - return column.pivotColumns.map(makeHeader) - } + // if (column.pivotColumns) { + // return column.pivotColumns.map(makeHeader) + // } return ( { - if (onExpandRow) { - return onExpandRow(cellInfo.nestingPath, e) - } - let newExpandedRows = _.clone(expandedRows) - if (isExpanded) { - return this.setStateWithData({ - expandedRows: _.set(newExpandedRows, cellInfo.nestingPath, false) - }) - } + const onExpanderClick = (e) => { + if (onExpandRow) { + return onExpandRow(cellInfo.nestingPath, e) + } + let newExpandedRows = _.clone(expandedRows) + if (isExpanded) { return this.setStateWithData({ - expandedRows: _.set(newExpandedRows, cellInfo.nestingPath, {}) + expandedRows: _.set(newExpandedRows, cellInfo.nestingPath, false) }) } + return this.setStateWithData({ + expandedRows: _.set(newExpandedRows, cellInfo.nestingPath, {}) + }) + } - extraProps['onClick'] = onTdClick + // Default to a standard cell + let resolvedCell = _.normalizeComponent(column.render, { + ...cellInfo, + value: cellInfo.rowValues[column.id], + isExpanded + }, cellInfo.rowValues[column.id]) - if (column.pivotColumns) { - const pivotFlex = _.sum(column.pivotColumns.map(d => { - const resized = resizing.find(x => x.id === d.id) || {} - return d.width || resized.value ? 0 : d.minWidth - })) - const pivotWidth = _.sum(column.pivotColumns.map(d => { - const resized = resizing.find(x => x.id === d.id) || {} - return _.getFirstDefined(resized.value, d.width, d.minWidth) - })) - const pivotMaxWidth = _.sum(column.pivotColumns.map(d => { - const resized = resizing.find(x => x.id === d.id) || {} - return _.getFirstDefined(resized.value, d.width, d.maxWidth) - })) + let interactionProps + let isBranch + let isPreview + let expandable - // Return the pivot expander cell - return ( - - {cellInfo.subRows ? ( - _.normalizeComponent(column.render, { - ...cellInfo, - value: row[pivotValKey], - isExpanded - }, cellInfo.rowValues[column.id]) - ) : SubComponent ? ( - _.normalizeComponent(column.render, { - ...cellInfo, - value: cellInfo.rowValues[column.id], - isExpanded - }, cellInfo.rowValues[column.id]) - ) : null} - - ) + // Is this column pivoted? + if (pivotBy && column.pivot) { + // Is this column a branch? + isBranch = rowInfo.rowValues[pivotIDKey] === column.id && + cellInfo.subRows + // Should this column be blank? + isPreview = pivotBy.indexOf(column.id) >= pivotBy.indexOf(rowInfo.rowValues[pivotIDKey]) && + cellInfo.subRows + + // Resolve renderers + const ResolvedExpanderComponent = column.expanderRender || ExpanderComponent + const ResolvedPivotValueComponent = column.pivotValueRender || PivotValueComponent + const ResolvedPivotPreviewComponent = column.pivotPreviewRender || PivotPreviewComponent + // Build the default PivotComponent + const DefaultResolvedPivotComponent = props => ( +
+ + +
+ ) + // Allow a completely custom pivotRender + const resolvedPivot = column.pivotRender || DefaultResolvedPivotComponent + // Pivot Cell Render Override + if (isBranch) { + // isPivot + resolvedCell = _.normalizeComponent(resolvedPivot, { + ...cellInfo, + value: row[pivotValKey], + isExpanded + }, cellInfo.rowValues[column.id]) + interactionProps = { + onClick: onExpanderClick + } + expandable = true + } else if (isPreview) { + // Show the pivot preview + resolvedCell = _.normalizeComponent(ResolvedPivotPreviewComponent, { + ...cellInfo, + value: cellInfo.rowValues[column.id], + isExpanded + }, cellInfo.rowValues[column.id]) + interactionProps = { + onClick: onExpanderClick + } + expandable = true + } else { + resolvedCell = null + } + } + + // Expander onClick event + if (column.expander) { + if (cellInfo.subRows) { + resolvedCell = null + } else { + expandable = true + interactionProps = { + onClick: onExpanderClick + } } } @@ -556,7 +579,9 @@ export default class ReactTable extends Methods(Lifecycle(Component)) { key={i2 + '-' + column.id} className={classnames( classes, - !show && 'hidden' + !show && 'hidden', + expandable && 'rt-expandable', + (isBranch || isPreview) && 'rt-pivot' )} style={{ ...styles, @@ -565,13 +590,9 @@ export default class ReactTable extends Methods(Lifecycle(Component)) { maxWidth: `${maxWidth}px` }} {...tdProps.rest} - {...extraProps} + {...interactionProps} > - {_.normalizeComponent(column.render, { - ...cellInfo, - value: cellInfo.rowValues[column.id], - isExpanded - }, cellInfo.rowValues[column.id])} + {resolvedCell} ) })} diff --git a/src/index.styl b/src/index.styl index 7bbb745..ea82796 100644 --- a/src/index.styl +++ b/src/index.styl @@ -102,7 +102,7 @@ $expandSize = 7px border-right:1px solid alpha(black, .02) &:last-child border-right:0 - .rt-pivot + .rt-expandable cursor: pointer .rt-tr-group display: flex @@ -318,4 +318,4 @@ $expandSize = 7px .rt-td transition: none!important cursor: col-resize - user-select none \ No newline at end of file + user-select none diff --git a/src/lifecycle.js b/src/lifecycle.js index 79337a3..caf6f80 100644 --- a/src/lifecycle.js +++ b/src/lifecycle.js @@ -12,7 +12,7 @@ export default Base => class extends Base { const oldState = this.getResolvedState() const newState = this.getResolvedState(nextProps, nextState) - if (oldState.defaultSorting !== newState.defaultSorting) { + if (JSON.stringify(oldState.defaultSorting) !== JSON.stringify(newState.defaultSorting)) { newState.sorting = newState.defaultSorting } diff --git a/src/methods.js b/src/methods.js index e60c7ea..5845e25 100644 --- a/src/methods.js +++ b/src/methods.js @@ -1,4 +1,3 @@ -import React from 'react' import _ from './utils' export default Base => class extends Base { @@ -53,27 +52,15 @@ export default Base => class extends Base { expanderColumn = expanderColumn.columns.find(col => col.expander) } - // If it has subrows or pivot columns we need to make sure we have an expander column - if ((SubComponent || pivotBy.length) && !expanderColumn) { + // If we have SubComponent's we need to make sure we have an expander column + if (SubComponent && !expanderColumn) { expanderColumn = {expander: true} columnsWithExpander = [expanderColumn, ...columnsWithExpander] } const makeDecoratedColumn = (column) => { let dcol - if (pivotBy.length && column.expander) { - dcol = { - ...this.props.column, - render: (props) => ( -
- - -
- ), - ...this.props.pivotDefaults, - ...column - } - } else if (column.expander) { + if (column.expander) { dcol = { ...this.props.column, render: this.props.ExpanderComponent, @@ -148,31 +135,36 @@ export default Base => class extends Base { return column.columns ? column.columns.length : pivotBy.indexOf(column.id) > -1 ? false : _.getFirstDefined(column.show, true) }) - // Move the pivot columns into a single column if needed + // Find any custom pivot location + const pivotIndex = visibleColumns.findIndex(col => col.pivot) + + // Handle Pivot Columns if (pivotBy.length) { + // Retrieve the pivot columns in the correct pivot order const pivotColumns = [] - for (var i = 0; i < allDecoratedColumns.length; i++) { - if (pivotBy.indexOf(allDecoratedColumns[i].id) > -1) { - pivotColumns.push(allDecoratedColumns[i]) + pivotBy.forEach(pivotID => { + const found = allDecoratedColumns.find(d => d.id === pivotID) + if (found) { + pivotColumns.push(found) } + }) + + let pivotColumnGroup = { + columns: pivotColumns.map(col => ({ + ...col, + pivot: true + })) } - const pivotExpanderColumn = visibleColumns.findIndex(col => col.expander || (col.columns && col.columns.some(col2 => col2.expander))) - if (pivotExpanderColumn >= 0) { - const pivotColumn = { - ...visibleColumns[pivotExpanderColumn], - pivotColumns + // Place the pivotColumns back into the visibleColumns + if (pivotIndex >= 0) { + pivotColumnGroup = { + ...visibleColumns[pivotIndex], + ...pivotColumnGroup } - visibleColumns[pivotExpanderColumn] = pivotColumn + visibleColumns.splice(pivotIndex, 1, pivotColumnGroup) } else { - // If the expander column wasn't on the top level column, find it in the `columns` option. - const pivotExpanderSubColumn = visibleColumns[pivotExpanderColumn].columns.findIndex(col => col.expander) - const pivotColumn = { - ...visibleColumns[pivotExpanderColumn].columns[pivotExpanderSubColumn], - pivotColumns - } - // Add the pivot columns to the expander column - visibleColumns[pivotExpanderColumn].columns[pivotExpanderSubColumn] = pivotColumn + visibleColumns.unshift(pivotColumnGroup) } } @@ -215,10 +207,10 @@ export default Base => class extends Base { }) return aggregationValues } + + // TODO: Make it possible to fabricate nested rows without pivoting const aggregatingColumns = allVisibleColumns.filter(d => !d.expander && d.aggregate) - let pivotColumn if (pivotBy.length) { - pivotColumn = allVisibleColumns[0] const groupRecursively = (rows, keys, i = 0) => { // This is the last level, just return the rows if (i === keys.length) { @@ -252,7 +244,6 @@ export default Base => class extends Base { return { ...newState, resolvedData, - pivotColumn, allVisibleColumns, headerGroups, allDecoratedColumns, diff --git a/stories/PivotingOptions.js b/stories/PivotingOptions.js index 83cae96..b3f42ec 100644 --- a/stories/PivotingOptions.js +++ b/stories/PivotingOptions.js @@ -19,21 +19,24 @@ export default () => { header: 'Name', columns: [{ header: 'First Name', - accessor: 'firstName' + accessor: 'firstName', + pivotValueRender: ({value}) => {value} }, { header: 'Last Name', id: 'lastName', - accessor: d => d.lastName + accessor: d => d.lastName, + pivotValueRender: ({value}) => {value} }] }, { header: 'Info', columns: [{ header: 'Age', accessor: 'age', - aggregate: vals => { - return _.round(_.mean(vals)) - }, + // aggregate: vals => { + // return _.round(_.mean(vals)) + // }, render: row => { + console.log(row.value) return {row.aggregated ? `${row.value} (avg)` : row.value} } }, { @@ -43,11 +46,10 @@ export default () => { hideFilter: true }] }, { - header: () => Overriden Pivot Column Header Group, - expander: true, - minWidth: 200, - pivotRender: ({value}) => {value}, - footer: () =>
Overriden Pivot Column Footer
+ pivot: true, + header: () => Overriden Pivot Column Header Group + }, { + expander: true }] return ( @@ -61,13 +63,10 @@ export default () => { pivotBy={['firstName', 'lastName']} defaultSorting={[{id: 'firstName', desc: false}, {id: 'lastName', desc: true}]} collapseOnSortingChange={false} - showFilters={true} + showFilters ExpanderComponent={({isExpanded, ...rest}) => ( isExpanded ? : )} - PivotValueComponent={ ({subRows, value}) => ( - {value} {subRows && `(${subRows.length} Last Names)`} - )} SubComponent={(row) => { return (