Use separate columns for Pivots and Expanders, more render options

This commit is contained in:
Tanner Linsley
2017-05-08 14:54:32 -06:00
parent ad7d31cd39
commit 3babdd1f66
6 changed files with 151 additions and 132 deletions

View File

@@ -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}) => (
<span>{value} {subRows && `(${subRows.length})`}</span>
),
PivotPreviewComponent: ({subRows, column}) => {
const previewValues = subRows.slice(0, 3).map((row, i) => (
<span key={i}>{row[column.id]}, </span>
))
return (
<span>{previewValues}...</span>
)
},
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,

View File

@@ -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 (
<ThComponent
@@ -480,73 +484,92 @@ export default class ReactTable extends Methods(Lifecycle(Component)) {
...columnProps.style
}
const extraProps = {}
if (column.expander) {
const onTdClick = (e) => {
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 (
<TdComponent
key={i2}
className={classnames(
'rt-pivot',
classes
)}
style={{
...styles,
paddingLeft: rowInfo.nestingPath.length === 1 ? undefined : `${30 * (cellInfo.nestingPath.length - 1)}px`,
flex: `${pivotFlex} 0 auto`,
width: `${pivotWidth}px`,
maxWidth: `${pivotMaxWidth}px`
}}
{...tdProps.rest}
onClick={onTdClick}
>
{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}
</TdComponent>
)
// 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 => (
<div>
<ResolvedExpanderComponent {...props} />
<ResolvedPivotValueComponent {...props} />
</div>
)
// 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}
</TdComponent>
)
})}

View File

@@ -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
user-select none

View File

@@ -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
}

View File

@@ -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) => (
<div>
<this.props.ExpanderComponent {...props} />
<this.props.PivotValueComponent {...props} />
</div>
),
...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,

View File

@@ -19,21 +19,24 @@ export default () => {
header: 'Name',
columns: [{
header: 'First Name',
accessor: 'firstName'
accessor: 'firstName',
pivotValueRender: ({value}) => <span style={{color: 'darkred'}}>{value}</span>
}, {
header: 'Last Name',
id: 'lastName',
accessor: d => d.lastName
accessor: d => d.lastName,
pivotValueRender: ({value}) => <span style={{color: 'darkblue'}}>{value}</span>
}]
}, {
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 <span>{row.aggregated ? `${row.value} (avg)` : row.value}</span>
}
}, {
@@ -43,11 +46,10 @@ export default () => {
hideFilter: true
}]
}, {
header: () => <strong>Overriden Pivot Column Header Group</strong>,
expander: true,
minWidth: 200,
pivotRender: ({value}) => <span style={{color: 'darkred'}}>{value}</span>,
footer: () => <div style={{textAlign: 'center'}}><strong>Overriden Pivot Column Footer</strong></div>
pivot: true,
header: () => <strong>Overriden Pivot Column Header Group</strong>
}, {
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 ? <span> &#10136; </span> : <span> &#10137; </span>
)}
PivotValueComponent={ ({subRows, value}) => (
<span><span style={{color: 'darkred'}}>{value}</span> {subRows && `(${subRows.length} Last Names)`}</span>
)}
SubComponent={(row) => {
return (
<div style={{padding: '20px'}}>