diff --git a/.storybook/config.js b/.storybook/config.js
index 585424b..b064361 100644
--- a/.storybook/config.js
+++ b/.storybook/config.js
@@ -26,6 +26,7 @@ import NoDataText from '../stories/NoDataText.js'
import Footers from '../stories/Footers.js'
import Filtering from '../stories/Filtering.js'
import ControlledTable from '../stories/ControlledTable.js'
+import PivotingOptions from '../stories/PivotingOptions.js'
//
configure(() => {
storiesOf('1. Docs')
@@ -51,6 +52,7 @@ configure(() => {
.add('Pivoting & Aggregation', Pivoting)
.add('Pivoting & Aggregation w/ Sub Components', PivotingSubComponents)
.add('100k Rows w/ Pivoting & Sub Components', OneHundredKRows)
+ .add('Pivoting Options', PivotingOptions)
.add('Functional Rendering', FunctionalRendering)
.add('Custom Expander Position', CustomExpanderPosition)
.add('Custom "No Data" Text', NoDataText)
diff --git a/README.md b/README.md
index 93c03de..d78a86a 100644
--- a/README.md
+++ b/README.md
@@ -153,7 +153,6 @@ These are all of the available props (and their default values) for the main `
)
},
+
+ // Global Expander Column Defaults
+ expanderDefaults: {
+ sortable: false,
+ width: 35,
+ hideFilter: true
+ // render: will be overriden in methods.js to display ExpanderComponent
+ },
+
+ // Global Pivot Column Defaults
+ pivotDefaults: {
+ // render: will be overriden in methods.js to display ExpanderComponent
+ },
// Text
previousText: 'Previous',
@@ -307,8 +319,13 @@ Or just define them as props
maxWidth: undefined, // A maximum width for this column.
// Special
- expander: false, // This option will override all data-related options and designates the column to be used
- // for pivoting and sub-component expansion
+ // Turns this column into a special column for specifying expander and pivot column options.
+ // If this option is true and there is NOT a pivot column, the `expanderDefaults` options will be applied on top of the column options.
+ // If this option is true and there IS a pivot column, the `pivotDefaults` options will be applied on top of the column options.
+ // Adding a column with the `expander` option set will allow you to rearrange expander and pivot column orderings in the table.
+ // It will also let you specify rendering of the header (and header group if this special column is placed in the `columns` option of another column) and
+ // the rendering of the expander itself.
+ expander: false,
// Cell Options
className: '', // Set the classname of the `td` element of the column
diff --git a/src/defaultProps.js b/src/defaultProps.js
index be40182..7cf413c 100644
--- a/src/defaultProps.js
+++ b/src/defaultProps.js
@@ -15,7 +15,6 @@ export default {
pageSizeOptions: [5, 10, 20, 25, 50, 100],
defaultPageSize: 20,
showPageJump: true,
- expanderColumnWidth: 35,
collapseOnSortingChange: true,
collapseOnPageChange: true,
collapseOnDataChange: true,
@@ -120,6 +119,19 @@ export default {
)
},
+ // Global Expander Column Defaults
+ expanderDefaults: {
+ sortable: false,
+ width: 35,
+ hideFilter: true
+ // render: will be overriden in methods.js to display ExpanderComponent
+ },
+
+ // Global Pivot Column Defaults
+ pivotDefaults: {
+ // render: will be overriden in methods.js to display ExpanderComponent
+ },
+
// Text
previousText: 'Previous',
nextText: 'Next',
@@ -150,12 +162,11 @@ export default {
},
TdComponent: _.makeTemplateComponent('rt-td'),
TfootComponent: _.makeTemplateComponent('rt-tfoot'),
- ExpanderComponent: ({isExpanded, ...rest}) => {
+ ExpanderComponent: ({isExpanded}) => {
return (
-
•
+
+ •
+
)
},
PaginationComponent: Pagination,
diff --git a/src/index.js b/src/index.js
index 5a6a3a6..6d19f76 100644
--- a/src/index.js
+++ b/src/index.js
@@ -10,6 +10,7 @@ export const ReactTableDefaults = defaultProps
export default class ReactTable extends Methods(Lifecycle(Component)) {
static defaultProps = defaultProps
+
constructor (props) {
super()
@@ -41,6 +42,7 @@ export default class ReactTable extends Methods(Lifecycle(Component)) {
skipNextSort: false
}
}
+
render () {
const resolvedState = this.getResolvedState()
const {
@@ -70,7 +72,6 @@ export default class ReactTable extends Methods(Lifecycle(Component)) {
getNoDataProps,
getResizerProps,
showPagination,
- expanderColumnWidth,
manual,
loadingText,
noDataText,
@@ -98,7 +99,6 @@ export default class ReactTable extends Methods(Lifecycle(Component)) {
ThComponent,
TdComponent,
TfootComponent,
- ExpanderComponent,
PaginationComponent,
LoadingComponent,
SubComponent,
@@ -229,39 +229,6 @@ export default class ReactTable extends Methods(Lifecycle(Component)) {
maxWidth: `${maxWidth}px`
}
- if (column.expander) {
- if (column.pivotColumns) {
- return (
-
- )
- }
- return (
-
- )
- }
return (
+ _.normalizeComponent(column.render, {
+ data: sortedData,
+ column: column
+ })
)}
)
@@ -382,21 +352,6 @@ export default class ReactTable extends Methods(Lifecycle(Component)) {
)
}
- return (
-
- )
}
return (
@@ -500,7 +455,15 @@ export default class ReactTable extends Methods(Lifecycle(Component)) {
)
if (i < column.pivotColumns.length - 1) {
- pivotCols.push()
+ pivotCols.push(
+ _.normalizeComponent(column.filterRender,
+ {
+ column,
+ filter,
+ key: col.id + '-' + i
+ },
+ defaultProps.column.filterRender
+ ))
}
}
return (
@@ -524,21 +487,6 @@ export default class ReactTable extends Methods(Lifecycle(Component)) {
)
}
- return (
-
- )
}
const filter = filtering.find(filter => filter.id === column.id)
@@ -618,6 +566,8 @@ export default class ReactTable extends Methods(Lifecycle(Component)) {
...columnProps.style
}
+ const extraProps = {}
+
if (column.expander) {
const onTdClick = (e) => {
if (onExpandRow) {
@@ -634,6 +584,8 @@ export default class ReactTable extends Methods(Lifecycle(Component)) {
})
}
+ extraProps['onClick'] = onTdClick
+
if (column.pivotColumns) {
// Return the pivot expander cell
const PivotCell = column.pivotRender
@@ -656,9 +608,11 @@ export default class ReactTable extends Methods(Lifecycle(Component)) {
>
{rowInfo.subRows ? (
-
+ {_.normalizeComponent(column.render, {
+ ...rowInfo,
+ value: rowInfo.rowValues[column.id],
+ isExpanded
+ }, rowInfo.rowValues[column.id])}
{column && column.pivotRender ? (
) : SubComponent ? (
-
+ {_.normalizeComponent(column.render, {
+ ...rowInfo,
+ value: rowInfo.rowValues[column.id],
+ isExpanded
+ }, rowInfo.rowValues[column.id])}
) : null}
)
}
-
- // Return the regular expander cell
- return (
-
-
-
-
-
- )
}
// Return regular cell
@@ -716,10 +649,12 @@ export default class ReactTable extends Methods(Lifecycle(Component)) {
maxWidth: `${maxWidth}px`
}}
{...tdProps.rest}
+ {...extraProps}
>
{_.normalizeComponent(column.render, {
...rowInfo,
- value: rowInfo.rowValues[column.id]
+ value: rowInfo.rowValues[column.id],
+ isExpanded
}, rowInfo.rowValues[column.id])}
)
@@ -859,22 +794,6 @@ export default class ReactTable extends Methods(Lifecycle(Component)) {
)
}
-
- // Return the regular expander cell
- return (
-
- )
}
// Return regular cell
diff --git a/src/methods.js b/src/methods.js
index 7ed5831..96f7bb8 100644
--- a/src/methods.js
+++ b/src/methods.js
@@ -10,6 +10,7 @@ export default Base => class extends Base {
}
return resolvedState
}
+
getDataModel (newState) {
const {
columns,
@@ -18,7 +19,6 @@ export default Base => class extends Base {
pivotIDKey,
pivotValKey,
subRowsKey,
- expanderColumnWidth,
SubComponent
} = newState
@@ -44,29 +44,42 @@ export default Base => class extends Base {
currentSpan = []
}
- const noSubExpanderColumns = columns.map(col => {
- return {
- ...col,
- columns: col.columns ? col.columns.filter(d => !d.expander) : undefined
- }
- })
+ let columnsWithExpander = [...columns]
- let expanderColumnIndex = columns.findIndex(col => col.expander)
- const needsExpander = (SubComponent || pivotBy.length) && expanderColumnIndex === -1
- const columnsWithExpander = needsExpander ? [{expander: true}, ...noSubExpanderColumns] : noSubExpanderColumns
- if (needsExpander) {
- expanderColumnIndex = 0
+ let expanderColumn = columns.find(col => col.expander || (col.columns && col.columns.some(col2 => col2.expander)))
+ // The actual expander might be in the columns field of a group column
+ if (expanderColumn && !expanderColumn.expander) {
+ 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) {
+ expanderColumn = {expander: true}
+ columnsWithExpander = [expanderColumn, ...columnsWithExpander]
}
const makeDecoratedColumn = (column) => {
- const dcol = {
- ...this.props.column,
- ...column
- }
-
- if (dcol.expander) {
- dcol.width = expanderColumnWidth
- return dcol
+ let dcol
+ if (pivotBy.length && column.expander) {
+ dcol = {
+ ...this.props.column,
+ render: this.props.ExpanderComponent,
+ filterRender: this.props.ExpanderComponent,
+ ...this.props.pivotDefaults,
+ ...column
+ }
+ } else if (column.expander) {
+ dcol = {
+ ...this.props.column,
+ render: this.props.ExpanderComponent,
+ ...this.props.expanderDefaults,
+ ...column
+ }
+ } else {
+ dcol = {
+ ...this.props.column,
+ ...column
+ }
}
if (typeof dcol.accessor === 'string') {
@@ -138,12 +151,24 @@ export default Base => class extends Base {
pivotColumns.push(allDecoratedColumns[i])
}
}
- const pivotColumn = {
- ...pivotColumns[0],
- pivotColumns,
- expander: true
+
+ const pivotExpanderColumn = visibleColumns.findIndex(col => col.expander || (col.columns && col.columns.some(col2 => col2.expander)))
+ if (pivotExpanderColumn >= 0) {
+ const pivotColumn = {
+ ...visibleColumns[pivotExpanderColumn],
+ pivotColumns
+ }
+ visibleColumns[pivotExpanderColumn] = pivotColumn
+ } 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[expanderColumnIndex] = pivotColumn
}
// Build flast list of allVisibleColumns and HeaderGroups
@@ -185,8 +210,7 @@ export default Base => class extends Base {
})
return aggregationValues
}
- let standardColumns = pivotBy.length ? allVisibleColumns.slice(1) : allVisibleColumns
- const aggregatingColumns = standardColumns.filter(d => d.aggregate)
+ const aggregatingColumns = allVisibleColumns.filter(d => !d.expander && d.aggregate)
let pivotColumn
if (pivotBy.length) {
pivotColumn = allVisibleColumns[0]
diff --git a/stories/CustomExpanderPosition.js b/stories/CustomExpanderPosition.js
index 9648c72..3277b09 100644
--- a/stories/CustomExpanderPosition.js
+++ b/stories/CustomExpanderPosition.js
@@ -8,8 +8,8 @@ import ReactTable from '../src/index'
export default () => {
const data = _.map(_.range(5553), d => {
return {
- firstName: namor.generate({ words: 1, numLen: 0 }),
- lastName: namor.generate({ words: 1, numLen: 0 }),
+ firstName: namor.generate({words: 1, numLen: 0}),
+ lastName: namor.generate({words: 1, numLen: 0}),
age: Math.floor(Math.random() * 30)
}
})
@@ -19,26 +19,33 @@ export default () => {
columns: [{
header: 'First Name',
accessor: 'firstName',
- render: row => {
- return {row.aggregated ? '...' : row.value}
- }
+ footer: () => First Name
}, {
header: 'Last Name',
- id: 'lastName',
- accessor: d => d.lastName
+ accessor: 'lastName',
+ footer: () => Last Name
}]
}, {
header: 'Info',
columns: [{
header: 'Age',
accessor: 'age',
- aggregate: vals => _.round(_.mean(vals)),
- render: row => {
- return {row.aggregated ? `${row.value} (avg)` : row.value}
- }
+ footer: () => Age
}]
}, {
- expander: true
+ header: 'Expand',
+ columns: [{
+ expander: true,
+ header: () => (More),
+ width: 65,
+ render: ({isExpanded, ...rest}) => (
+
+ {isExpanded ? ⊙ : ⊕}
+
+ ),
+ style: {cursor: 'pointer', fontSize: 25, padding: '0', textAlign: 'center', userSelect: 'none'},
+ footer: () => ♥
+ }]
}]
return (
@@ -71,26 +78,33 @@ const columns = [{
columns: [{
header: 'First Name',
accessor: 'firstName',
- render: row => {
- return {row.aggregated ? '...' : row.value}
- }
+ footer: () => First Name
}, {
header: 'Last Name',
- id: 'lastName',
- accessor: d => d.lastName
+ accessor: 'lastName',
+ footer: () => Last Name
}]
}, {
header: 'Info',
columns: [{
header: 'Age',
accessor: 'age',
- aggregate: vals => _.round(_.mean(vals)),
- render: row => {
- return {row.aggregated ? \`$\{row.value} (avg)\` : row.value}
- }
+ footer: () => Age
}]
}, {
- expander: true
+ header: 'Expand',
+ columns: [{
+ expander: true,
+ header: () => (More),
+ width: 65,
+ render: ({isExpanded, ...rest}) => (
+
+ {isExpanded ? ⊙ : ⊕}
+
+ ),
+ style: {cursor: 'pointer', fontSize: 25, padding: '0', textAlign: 'center', userSelect: 'none'},
+ footer: () => ♥
+ }]
}]
return (
diff --git a/stories/PivotingOptions.js b/stories/PivotingOptions.js
new file mode 100644
index 0000000..97ebb36
--- /dev/null
+++ b/stories/PivotingOptions.js
@@ -0,0 +1,183 @@
+import React from 'react'
+import _ from 'lodash'
+import namor from 'namor'
+
+import CodeHighlight from './components/codeHighlight'
+import ReactTable from '../src/index'
+
+export default () => {
+ const data = _.map(_.range(1000), d => {
+ return {
+ firstName: namor.generate({words: 1, numLen: 0}),
+ lastName: namor.generate({words: 1, numLen: 0}),
+ age: Math.floor(Math.random() * 30),
+ visits: Math.floor(Math.random() * 100)
+ }
+ })
+
+ const columns = [{
+ header: 'Name',
+ columns: [{
+ header: 'First Name',
+ accessor: 'firstName'
+ }, {
+ header: 'Last Name',
+ id: 'lastName',
+ accessor: d => d.lastName
+ }]
+ }, {
+ header: 'Info',
+ columns: [{
+ header: 'Age',
+ accessor: 'age',
+ aggregate: vals => {
+ return _.round(_.mean(vals))
+ },
+ render: row => {
+ return {row.aggregated ? `${row.value} (avg)` : row.value}
+ }
+ }, {
+ header: 'Visits',
+ accessor: 'visits',
+ aggregate: vals => _.sum(vals),
+ hideFilter: true
+ }]
+ }, {
+ header: () => First ⇢ Last Name Pivot,
+ expander: true,
+ minWidth: 200,
+ render: ({isExpanded, ...rest}) => (
+ isExpanded ? ➘ : ➙
+ ),
+ filterRender: ({key}) => ⥱ ,
+ footer: () => Pivot Footer
+ }]
+
+ return (
+
+
+
{
+ return (
+
+
You can put any component you want here, even another React Table!
+
+
+
!x.expander)}
+ defaultPageSize={3}
+ showPagination={false}
+ SubComponent={(row) => {
+ return (
+
+ It even has access to the row data:
+ {() => JSON.stringify(row, null, 2)}
+
+ )
+ }}
+ />
+
+ )
+ }}
+ />
+
+
+
+ Tip: Hold shift when sorting to multi-sort!
+
+
{() => getCode()}
+
+ )
+}
+
+function getCode () {
+ return `
+const columns = [{
+ header: 'Name',
+ columns: [{
+ header: 'First Name',
+ accessor: 'firstName'
+ }, {
+ header: 'Last Name',
+ id: 'lastName',
+ accessor: d => d.lastName
+ }]
+}, {
+ header: 'Info',
+ columns: [{
+ header: 'Age',
+ accessor: 'age',
+ aggregate: vals => {
+ return _.round(_.mean(vals))
+ },
+ render: row => {
+ return {row.aggregated ? \`\${row.value} (avg)\` : row.value}
+ }
+ }, {
+ header: 'Visits',
+ accessor: 'visits',
+ aggregate: vals => _.sum(vals),
+ hideFilter: true
+ }]
+}, {
+ header: () => First ⇢ Last Name Pivot,
+ expander: true,
+ minWidth: 200,
+ render: ({isExpanded, ...rest}) => (
+ isExpanded ? ➘ : ➙
+ ),
+ filterRender: ({key}) => ⥱ ,
+ footer: () => Pivot Footer
+}]
+
+return (
+
+
+
{
+ return (
+
+
You can put any component you want here, even another React Table!
+
+
+
!x.expander)}
+ defaultPageSize={3}
+ showPagination={false}
+ SubComponent={(row) => {
+ return (
+
+ It even has access to the row data:
+ {() => JSON.stringify(row, null, 2)}
+
+ )
+ }}
+ />
+
+ )
+ }}
+ />
+
+
+
+ Tip: Hold shift when sorting to multi-sort!
+
+
{() => getCode()}
+
+)
+ `
+}