mirror of
https://github.com/gosticks/react-table.git
synced 2026-03-28 00:24:25 +00:00
Use separate columns for Pivots and Expanders, more render options
This commit is contained in:
@@ -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,
|
||||
|
||||
163
src/index.js
163
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 (
|
||||
<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>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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> ➘ </span> : <span> ➙ </span>
|
||||
)}
|
||||
PivotValueComponent={ ({subRows, value}) => (
|
||||
<span><span style={{color: 'darkred'}}>{value}</span> {subRows && `(${subRows.length} Last Names)`}</span>
|
||||
)}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
|
||||
Reference in New Issue
Block a user