From 8de0fa420b5cdeadc5a969e8a8887ab06dcd633b Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Fri, 27 Jan 2017 14:10:47 -0700 Subject: [PATCH] Better default sorting, column.width support, expander column position --- .storybook/config.js | 8 +- README.md | 5 + react-table.css.zip | Bin 2040 -> 0 bytes react-table.js.zip | Bin 6828 -> 0 bytes src/index.js | 474 +++++++++++++--------- stories/CustomExpanderPosition.js | 108 +++++ stories/{MaxWidths.js => CustomWidths.js} | 8 +- stories/DefaultSorting.js | 87 ++++ 8 files changed, 482 insertions(+), 208 deletions(-) delete mode 100644 react-table.css.zip delete mode 100644 react-table.js.zip create mode 100644 stories/CustomExpanderPosition.js rename stories/{MaxWidths.js => CustomWidths.js} (95%) create mode 100644 stories/DefaultSorting.js diff --git a/.storybook/config.js b/.storybook/config.js index ccdf258..63be681 100644 --- a/.storybook/config.js +++ b/.storybook/config.js @@ -12,13 +12,15 @@ import Readme from '../README.md' // import Simple from '../stories/Simple.js' import CellRenderers from '../stories/CellRenderers.js' -import MaxWidths from '../stories/MaxWidths.js' +import DefaultSorting from '../stories/DefaultSorting.js' +import CustomWidths from '../stories/CustomWidths.js' import ServerSide from '../stories/ServerSide.js' import SubComponents from '../stories/SubComponents.js' import Pivoting from '../stories/Pivoting.js' import PivotingSubComponents from '../stories/PivotingSubComponents.js' import OneHundredKRows from '../stories/OneHundredKRows.js' import FunctionalRendering from '../stories/FunctionalRendering.js' +import CustomExpanderPosition from '../stories/CustomExpanderPosition.js' // configure(() => { storiesOf('1. Docs') @@ -36,11 +38,13 @@ configure(() => { storiesOf('2. Demos') .add('Simple Table', Simple) .add('Cell Renderers & Custom Components', CellRenderers) - .add('Max Widths', MaxWidths) + .add('Default Sorting', DefaultSorting) + .add('Custom Column Widths', CustomWidths) .add('Server-side Data', ServerSide) .add('Sub Components', SubComponents) .add('Pivoting & Aggregation', Pivoting) .add('Pivoting & Aggregation w/ Sub Components', PivotingSubComponents) .add('100k Rows w/ Pivoting & Sub Components', OneHundredKRows) .add('Functional Rendering', FunctionalRendering) + .add('Custom Expander Position', CustomExpanderPosition) }, module) diff --git a/README.md b/README.md index 62ab7f8..5f12d6e 100644 --- a/README.md +++ b/README.md @@ -206,9 +206,14 @@ Or just define them on the component per-instance sortable: true, sort: 'asc' or 'desc', // used to determine the column sorting on init show: true, // can be used to hide a column + width: undefined, // A hardcoded width for the column. This overrides both min and max width options minWidth: 100 // A minimum width for this column. If there is extra room, column will flex to fill available space (up to the max-width, if set) 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 + // Cell Options className: '', // Set the classname of the `td` element of the column style: {}, // Set the style of the `td` element of the column diff --git a/react-table.css.zip b/react-table.css.zip deleted file mode 100644 index 5fc419635b4c6834f14b2ef9dd2ad5863d24f171..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2040 zcmV*^#K$B?OIW9+cpsXD_V=9@n8wEQ#Xl(4Hz(B=zyWX)`tzl9%zZS zxX7YF(Qy)^|9wY_lt_t^b=$S^PO@$9vx&{R3X#67H|*yQf#Ao3PFke z9eo^NIjPp<0pmWGYNn(rbMz1yi^UC1EfIHxHaRw>DoHQ}&9)Ra_d*%L1&{79<0L_O zyOSP+y2*wXeo7clmIVx~6!VvO;c5TmKAohh$Ia zo9r2=;+K?&w|YWpTliRaBh?*UD9TaA{UjrKI=Y4-ceI{-*q?q)(}!>2#(gfz8~Y;0 zlt(+F)SgrL(4P04Fqz|q)Vu>%q@DRB5564N;cWYmt&-ePe$ra?Vg2td>T80fj%(^& z5Gis};Ql?i==PA_tr>K4m}4eG(&6~}J}$NVyciZO+Vj+XYW7Tqalz3-xcTOMRo7@9 zGB`pXVrzn=4@$Fv7UbNlf&rS>7Uqmom6ggfToBaIf4z-nu_y4Beinfb9<5N1 z<2>sQ&ob=r44V$mG3RiqC5Kir200*Sq{O;%0tb<%V}&7ma0Vux3p4NG^e7Y2dU&R8 z1;;ND_%9@G`6y6RDj~-gfWhFnZ=-+r6(^gA78}`A>XKAQ)26D4Ro~ZsSK=ZtUw*)V zW5&5(6B4$R5uvJzcKe)@GRLhpT{?AA&1^xy!>GoHTFsW!>FRiYS>C&fK%)5IEE#r6 zi-S^wW(l1&FBK zuom%e4je7;P9cI`iDuyWEk+{CVgNvQxQ>{xa06+ZImobGbxikMm`r_cCobi0I9I_+ z|2b-u7p$Iz?N$Kp+=vc1TGbS95ZLnx>}cHd@Nis{?dou<)KPXXnoT=XSEjH;Yu z@b?iOv7^>vvgi=QU2kFZ;3~EVvP<%d`}2NyQy0~VZkkG6l2&ZfR7Yr2EC;Cv0pRuk z8LdS^WTOC+K?mebEMPK5;Q*4}U;qu&%u#;;j_pISq3&c-<%gm^FEhk`edg5MzwURX zd+sSCJyMi=uJZaTp}|?6Lrn7r#JZ2AAXLZBb~;0IibR+^+TpgJ>B=-NjG2+=im0nR zL)SwuSF5@HJ(--1giv=Ksyou1Gr2>kW@zBNY>OSwt#AyLHDVQ~eS8O(S2@G9x(4q@ zFC}-!{_)$_(|6SdKC(&>p^1G&VbBeJM)J77ATe6L8P41qzB5;AM?14K%^n*$J*{8L zl=j_w`6=5s@22PU1EVJ;^g}C8%I4jJpE}TOSb+~Ta)%qxw=k_;TBOg>#7V&r4QLeqYOQkOHhW*@;N3uB;4& z6d7iOJ2Dz6bEvbQ0`>{O#zg-11=YKf;KA|zvI()tm_Q$qskBT9wf1ej89Y!o#26Dz zDXG>G=2}EO<;cd0OkrKI8Llfe0`5{$JKkMA()=-A`}Hf4irn1Xn6*n+9kb#OIj;Y$ zt4a0g=`&ph@cH>aP)i30w3jC}Oa}k}BPaj>P)h*<6$2Ci2mlBGU}!K(w3jC}Oa}k} zBPaj>4*(1R000000000$q=5hc0044jVPkYHbYWs_WiDfLb5&Rf00}33So0=*SWrs^ W1^@s60096207(D<0GS5>000037Qq7m diff --git a/react-table.js.zip b/react-table.js.zip deleted file mode 100644 index cda8814330b7eb9823a666e049f40e7f3ab6fbb4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6828 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 0 const canNext = page + 1 < pages - const rowWidth = (SubComponent ? expanderColumnWidth : 0) + _.sum(allVisibleColumns.map(d => d.minWidth)) + const rowMinWidth = _.sum(allVisibleColumns.map(d => _.getFirstDefined(d.width, d.minWidth))) let rowIndex = -1 - const makeHeaderGroup = () => ( + const makeHeaderGroups = () => ( - {pivotBy.length ? ( - - ) : SubComponent ? ( - - ) : null} - {headerGroups.map((column, i) => { - return ( - { - return d.maxWidth < d.minWidth ? d.maxWidth : d.minWidth - }))}px`, - maxWidth: `${_.sum(column.columns.map(d => d.maxWidth))}px` - }))} - > - {typeof column.header === 'function' ? ( - - ) : column.header} - - ) - })} + {headerGroups.map(makeHeaderGroup)} ) - const makeHeader = () => { - const pivotSort = pivotColumn && sorting.find(d => d.id === pivotColumn.id) + const makeHeaderGroup = (column, i) => { + const flex = _.sum(column.columns.map(d => d.width ? 0 : d.minWidth)) + const width = _.sum(column.columns.map(d => _.getFirstDefined(d.width, d.minWidth))) + const maxWidth = _.sum(column.columns.map(d => _.getFirstDefined(d.width, d.maxWidth))) + if (column.expander) { + if (column.pivotColumns) { + return ( + + ) + } + return ( + + ) + } + return ( + + {typeof column.header === 'function' ? ( + + ) : column.header} + + ) + } + + const makeHeaders = () => { return ( - {pivotBy.length ? ( - { - pivotColumn.sortable && this.sortColumn(pivotColumn.pivotColumns, e.shiftKey) - }} - > - {pivotColumn.pivotColumns.map((column, i) => { - return ( - - {typeof column.header === 'function' ? ( - - ) : column.header} - {i < pivotColumn.pivotColumns.length - 1 && ( - - )} - - ) - })} - - ) : SubComponent ? ( - - ) : null} - {standardColumns.map(makeHeaderGroupColumn)} + {allVisibleColumns.map(makeHeader)} ) } - const makeHeaderGroupColumn = (column, i) => { - const sort = sorting.find(d => d.id === column.id) + + const makeHeader = (column, i) => { + const sort = resolvedSorting.find(d => d.id === column.id) const show = typeof column.show === 'function' ? column.show() : column.show + const width = _.getFirstDefined(column.width, column.minWidth) + const maxWidth = _.getFirstDefined(column.width, column.maxWidth) + if (column.expander) { + if (column.pivotColumns) { + const pivotSort = resolvedSorting.find(d => d.id === column.id) + return ( + { + column.sortable && this.sortColumn(column.pivotColumns, e.shiftKey) + }} + > + {column.pivotColumns.map((pivotColumn, i) => { + return ( + + {typeof pivotColumn.header === 'function' ? ( + + ) : pivotColumn.header} + {i < column.pivotColumns.length - 1 && ( + + )} + + ) + })} + + ) + } + return ( + + ) + } + return ( { column.sortable && this.sortColumn(column, e.shiftKey) @@ -460,8 +474,6 @@ export default React.createClass({ subRows: row[subRowsKey] } const isExpanded = _.get(expandedRows, rowInfo.nestingPath) - const rowPivotColumn = allDecoratedColumns.find(d => d.id === row[pivotIDKey]) - const PivotCell = rowPivotColumn && rowPivotColumn.pivotRender return ( - {(pivotBy.length || SubComponent) && ( - { + {allVisibleColumns.map((column, i2) => { + const Cell = column.render + const show = typeof column.show === 'function' ? column.show() : column.show + const width = _.getFirstDefined(column.width, column.minWidth) + const maxWidth = _.getFirstDefined(column.width, column.maxWidth) + + if (column.expander) { + const onTdClick = (e) => { if (onExpandRow) { return onExpandRow(rowInfo.nestingPath, e) } @@ -491,40 +501,73 @@ export default React.createClass({ return this.setStateWithData({ expandedRows: _.set(newExpandedRows, rowInfo.nestingPath, {}) }) - }} - > - {rowInfo.subRows ? ( - - - {rowPivotColumn && rowPivotColumn.pivotRender ? ( - + {rowInfo.subRows ? ( + + + {column && column.pivotRender ? ( + + ) : {row[pivotValKey]} ({rowInfo.subRows.length})} + + ) : SubComponent ? ( + + + + ) : null} + + ) + } + + // Return the regular expander cell + return ( + + + - ) : {row[pivotValKey]} ({rowInfo.subRows.length})} - - ) : SubComponent ? ( - - - - ) : null} - - )} - {standardColumns.map((column, i2) => { - const Cell = column.render - const show = typeof column.show === 'function' ? column.show() : column.show + + + ) + } + + // Return regular cell return ( {typeof Cell === 'function' ? ( @@ -566,15 +609,18 @@ export default React.createClass({ })} /> )} - {standardColumns.map((column, i2) => { + {allVisibleColumns.map((column, i2) => { const show = typeof column.show === 'function' ? column.show() : column.show + const width = _.getFirstDefined(column.width, column.minWidth) + const maxWidth = _.getFirstDefined(column.width, column.maxWidth) return (   @@ -595,12 +641,12 @@ export default React.createClass({ className={classnames(tableClassName)} style={tableStyle} > - {hasHeaderGroups && makeHeaderGroup()} - {makeHeader()} + {hasHeaderGroups && makeHeaderGroups()} + {makeHeaders()} {pageRows.map((d, i) => makePageRow(d, i))} @@ -635,7 +681,7 @@ export default React.createClass({ padRows, canPrevious, canNext, - rowWidth + rowMinWidth } return children ? children(childState, makeTable, this) : makeTable() @@ -649,7 +695,9 @@ export default React.createClass({ data, pivotIDKey, pivotValKey, - subRowsKey + subRowsKey, + expanderColumnWidth, + SubComponent } = this.getResolvedState(nextProps, nextState) // Determine Header Groups @@ -665,21 +713,67 @@ export default React.createClass({ let currentSpan = [] // A convenience function to add a header and reset the currentSpan - const addHeader = (columns, column = {}) => { + const addHeader = (columns, column = columns[0]) => { headerGroups.push(Object.assign({}, column, { columns: columns })) currentSpan = [] } + const noSubExpanderColumns = columns.map(col => { + return { + ...col, + columns: col.columns ? col.columns.filter(d => !d.expander) : undefined + } + }) + + 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 + } + + const makeDecoratedColumn = (column) => { + const dcol = Object.assign({}, this.props.column, column) + + if (dcol.expander) { + dcol.width = expanderColumnWidth + return dcol + } + + if (typeof dcol.accessor === 'string') { + dcol.id = dcol.id || dcol.accessor + const accessorString = dcol.accessor + dcol.accessor = row => _.get(row, accessorString) + return dcol + } + + if (dcol.accessor && !dcol.id) { + console.warn(dcol) + throw new Error('A column id is required if using a non-string accessor for column above.') + } + + if (!dcol.accessor) { + dcol.accessor = d => undefined + } + + // Ensure minWidth is not greater than maxWidth if set + if (dcol.maxWidth < dcol.minWidth) { + dcol.minWidth = dcol.maxWidth + } + + return dcol + } + // Decorate the columns const decorateAndAddToAll = (col) => { - const decoratedColumn = this.makeDecoratedColumn(col) + const decoratedColumn = makeDecoratedColumn(col) allDecoratedColumns.push(decoratedColumn) return decoratedColumn } let allDecoratedColumns = [] - const decoratedColumns = columns.map((column, i) => { + const decoratedColumns = columnsWithExpander.map((column, i) => { if (column.columns) { return { ...column, @@ -690,7 +784,7 @@ export default React.createClass({ } }) - // Build the visible columns and headers and flat column list + // Build the visible columns, headers and flat column list let visibleColumns = decoratedColumns.slice() let allVisibleColumns = [] @@ -709,7 +803,23 @@ export default React.createClass({ return column.columns ? column.columns.length : pivotBy.indexOf(column.id) > -1 ? false : _.getFirstDefined(column.show, true) }) - // Build allVisible columns and HeaderGroups + // Move the pivot columns into a single column if needed + if (pivotBy.length) { + const pivotColumns = [] + for (var i = 0; i < allDecoratedColumns.length; i++) { + if (pivotBy.indexOf(allDecoratedColumns[i].id) > -1) { + pivotColumns.push(allDecoratedColumns[i]) + } + } + const pivotColumn = { + ...pivotColumns[0], + pivotColumns, + expander: true + } + visibleColumns[expanderColumnIndex] = pivotColumn + } + + // Build flast list of allVisibleColumns and HeaderGroups visibleColumns.forEach((column, i) => { if (column.columns) { allVisibleColumns = allVisibleColumns.concat(column.columns) @@ -726,23 +836,6 @@ export default React.createClass({ addHeader(currentSpan) } - // Move the pivot columns into a single column if needed - if (pivotBy.length) { - const pivotColumns = [] - for (var i = 0; i < allDecoratedColumns.length; i++) { - if (pivotBy.indexOf(allDecoratedColumns[i].id) > -1) { - pivotColumns.push(allDecoratedColumns[i]) - } - } - allVisibleColumns.unshift({ - ...pivotColumns[0], - pivotColumns - }) - } - - // Determine the flex percentage for each column - const columnPercentage = 100 / allVisibleColumns.length - // Access the data let resolvedData = data.map((d, i) => { const row = { @@ -750,6 +843,7 @@ export default React.createClass({ __index: i } allDecoratedColumns.forEach(column => { + if (column.expander) return row[column.id] = column.accessor(d) }) return row @@ -802,11 +896,9 @@ export default React.createClass({ return { resolvedData, - columnPercentage, pivotColumn, allVisibleColumns, headerGroups, - standardColumns, allDecoratedColumns, hasHeaderGroups } @@ -824,6 +916,7 @@ export default React.createClass({ // Resolve the data from either manual data or sorted data return { + resolvedSorting, sortedData: manual ? resolvedData : this.sortData(resolvedData, resolvedSorting) } }, @@ -850,10 +943,12 @@ export default React.createClass({ } }) - return initSorting.length ? initSorting : [{ - id: columns[0].id, - asc: true - }] + return initSorting + + // return initSorting.length ? initSorting : [{ + // id: columns.find(d => d.id).id, + // asc: true + // }] }, sortData (data, sorting) { const sorted = _.sortBy(data, sorting.map(sort => { @@ -875,32 +970,7 @@ export default React.createClass({ } }) }, - makeDecoratedColumn (column) { - const dcol = Object.assign({}, this.props.column, column) - if (typeof dcol.accessor === 'string') { - dcol.id = dcol.id || dcol.accessor - const accessorString = dcol.accessor - dcol.accessor = row => _.get(row, accessorString) - return dcol - } - - if (dcol.accessor && !dcol.id) { - console.warn(dcol) - throw new Error('A column id is required if using a non-string accessor for column above.') - } - - if (!dcol.accessor) { - dcol.accessor = d => undefined - } - - // Ensure minWidth is not greater than maxWidth if set - if (dcol.maxWidth < dcol.minWidth) { - dcol.minWidth = dcol.maxWidth - } - - return dcol - }, getMinRows () { return _.getFirstDefined(this.props.minRows, this.getStateOrProp('pageSize')) }, diff --git a/stories/CustomExpanderPosition.js b/stories/CustomExpanderPosition.js new file mode 100644 index 0000000..a8cf553 --- /dev/null +++ b/stories/CustomExpanderPosition.js @@ -0,0 +1,108 @@ +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(5553), d => { + return { + firstName: namor.generate({ words: 1, numLen: 0 }), + lastName: namor.generate({ words: 1, numLen: 0 }), + age: Math.floor(Math.random() * 30) + } + }) + + const columns = [{ + header: 'Name', + columns: [{ + header: 'First Name', + accessor: 'firstName', + render: row => { + return {row.aggregated ? '...' : row.value} + } + }, { + header: 'Last Name', + id: 'lastName', + accessor: d => d.lastName + }] + }, { + expander: true + }, { + header: 'Info', + columns: [{ + header: 'Age', + accessor: 'age', + aggregate: vals => _.round(_.mean(vals)), + render: row => { + return {row.aggregated ? `${row.value} (avg)` : row.value} + } + }] + }] + + return ( +
+
+ Hello} + pivotBy={['lastName']} + /> +
+
+
+ Tip: Hold shift when sorting to multi-sort! +
+ {() => getCode()} +
+ ) +} + +function getCode () { + return ` +import ReactTable from 'react-table' + +// Create some column definitions +const columns = [{ + header: 'Name', + columns: [{ + header: 'First Name', + accessor: 'firstName', + render: row => { + return {row.aggregated ? '...' : row.value} + } + }, { + header: 'Last Name', + id: 'lastName', + accessor: d => d.lastName + }] +}, { + expander: true +}, { + header: 'Info', + columns: [{ + header: 'Age', + accessor: 'age', + aggregate: vals => _.round(_.mean(vals)), + render: row => { + return {row.aggregated ? \`$\{row.value} (avg)\` : row.value} + } + }] +}] + +return ( + Hello} + pivotBy={['lastName']} + /> +) + ` +} diff --git a/stories/MaxWidths.js b/stories/CustomWidths.js similarity index 95% rename from stories/MaxWidths.js rename to stories/CustomWidths.js index 982ec90..cb56552 100644 --- a/stories/MaxWidths.js +++ b/stories/CustomWidths.js @@ -24,14 +24,14 @@ export default () => { header: 'Last Name', id: 'lastName', accessor: d => d.lastName, - maxWidth: 400 + width: 300 }] }, { header: 'Info', columns: [{ header: 'Age', accessor: 'age', - maxWidth: 60 + minWidth: 400 }] }] @@ -68,14 +68,14 @@ const columns = [{ header: 'Last Name', id: 'lastName', accessor: d => d.lastName, - maxWidth: 400 + width: 300 }] }, { header: 'Info', columns: [{ header: 'Age', accessor: 'age', - maxWidth: 60 + minWidth: 400 }] }] diff --git a/stories/DefaultSorting.js b/stories/DefaultSorting.js new file mode 100644 index 0000000..8a1705c --- /dev/null +++ b/stories/DefaultSorting.js @@ -0,0 +1,87 @@ +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(5553), d => { + return { + firstName: namor.generate({ words: 1, numLen: 0 }), + lastName: namor.generate({ words: 1, numLen: 0 }), + age: Math.floor(Math.random() * 30) + } + }) + + 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', + sort: 'desc' + }] + }] + + return ( +
+
+ +
+
+
+ Tip: Hold shift when sorting to multi-sort! +
+ {() => getCode()} +
+ ) +} + +function getCode () { + return ` +import ReactTable from 'react-table' + +// Create some column definitions +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' + }] +}] + +// Display your table! +return ( + +) + ` +}