diff --git a/.storybook/config.js b/.storybook/config.js index 92aeac8..73fecf4 100644 --- a/.storybook/config.js +++ b/.storybook/config.js @@ -64,5 +64,5 @@ configure(() => { .add('Custom Filtering', Filtering) .add('Controlled Component', ControlledTable) .add('Editable table', EditableTable) - .add('Sub Rows', SubRows) + // .add('Sub Rows', SubRows) }, module) diff --git a/README.md b/README.md index 741dafe..64ab9bc 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,7 @@ These are all of the available props (and their default values) for the main ` { + // force null and undefined to the bottom + a = (a === null || a === undefined) ? -Infinity : a + b = (b === null || b === undefined) ? -Infinity : b + // force any string values to lowercase + a = a === 'string' ? a.toLowerCase() : a + b = b === 'string' ? b.toLowerCase() : b + // Return either 1 or -1 to indicate a sort priority + if (a > b) { + return 1 + } + if (a < b) { + return -1 + } + // returning 0 or undefined will use any subsequent column sorting methods or the row index as a tiebreaker + return 0 +} +``` + ## Filtering Filtering can be enabled by setting the `showFilters` option on the table. diff --git a/src/defaultProps.js b/src/defaultProps.js index ef3202a..1f74145 100644 --- a/src/defaultProps.js +++ b/src/defaultProps.js @@ -26,6 +26,23 @@ export default { const id = filter.pivotId || filter.id return row[id] !== undefined ? String(row[id]).startsWith(filter.value) : true }, + defaultSortMethod: (a, b) => { + // force null and undefined to the bottom + a = (a === null || a === undefined) ? -Infinity : a + b = (b === null || b === undefined) ? -Infinity : b + // force any string values to lowercase + a = a === 'string' ? a.toLowerCase() : a + b = b === 'string' ? b.toLowerCase() : b + // Return either 1 or -1 to indicate a sort priority + if (a > b) { + return 1 + } + if (a < b) { + return -1 + } + // returning 0, undefined or any falsey value will use subsequent sorts or the index as a tiebreaker + return 0 + }, resizable: true, defaultResizing: [], @@ -119,12 +136,6 @@ export default { footerStyle: {}, getFooterProps: emptyObj, filterMethod: undefined, - sortMethod: value => { - if (value === null || value === undefined) { - return -Infinity - } - return typeof value === 'string' ? value.toLowerCase() : value - }, hideFilter: false }, diff --git a/src/index.js b/src/index.js index dce8f64..9d6be77 100644 --- a/src/index.js +++ b/src/index.js @@ -561,22 +561,18 @@ export default class ReactTable extends Methods(Lifecycle(Component)) { } } else if (cellInfo.aggregated) { resolvedCell = _.normalizeComponent(ResolvedAggregatedComponent, cellInfo, value) - } else if (column.expander) { - resolvedCell = _.normalizeComponent(ResolvedPivotComponent, cellInfo, value) - if (cellInfo.subRows) { - resolvedCell = null + } + + if (cellInfo.expander) { + resolvedCell = _.normalizeComponent(ResolvedExpanderComponent, cellInfo, row[pivotValKey]) + if (pivotBy) { + if (cellInfo.groupedByPivot) { + resolvedCell = null + } + if (!cellInfo.subRows && !SubComponent) { + resolvedCell = null + } } - if (!cellInfo.subRows && !SubComponent) { - resolvedCell = null - } - // if (pivotBy) { - // if (cellInfo.groupedByPivot) { - // resolvedCell = null - // } - // if (!cellInfo.subRows && !SubComponent) { - // resolvedCell = null - // } - // } } // Return the cell diff --git a/src/methods.js b/src/methods.js index 32b4a90..484dc72 100644 --- a/src/methods.js +++ b/src/methods.js @@ -52,18 +52,18 @@ export default Base => class extends Base { const makeDecoratedColumn = (column) => { let dcol - // if (column.expander) { - // dcol = { - // ...this.props.column, - // ...this.props.expanderDefaults, - // ...column - // } - // } else { + if (column.expander) { + dcol = { + ...this.props.column, + ...this.props.expanderDefaults, + ...column + } + } else { dcol = { ...this.props.column, ...column } - // } + } if (typeof dcol.accessor === 'string') { dcol.id = dcol.id || dcol.accessor @@ -279,12 +279,12 @@ export default Base => class extends Base { allDecoratedColumns } = resolvedState - const sortersByID = {} + const sortMethodsByColumnID = {} allDecoratedColumns .filter(col => col.sortMethod) .forEach(col => { - sortersByID[col.id] = col.sortMethod + sortMethodsByColumnID[col.id] = col.sortMethod }) // Resolve the data from either manual data or sorted data @@ -298,7 +298,7 @@ export default Base => class extends Base { allVisibleColumns ), sorting, - sortersByID + sortMethodsByColumnID ) } } @@ -366,32 +366,36 @@ export default Base => class extends Base { return filteredData } - sortData (data, sorting, sortersByID = {}) { + sortData (data, sorting, sortMethodsByColumnID = {}) { if (!sorting.length) { return data } - const sorted = _.orderBy(data, sorting.map(sort => { - // Support custom sorting methods for each column - if (sortersByID[sort.id]) { - return row => { - return sortersByID[sort.id](row[sort.id]) + const sorted = (this.props.orderByMethod || _.orderBy)( + data, + sorting.map(sort => { + // Support custom sorting methods for each column + if (sortMethodsByColumnID[sort.id]) { + return (a, b) => { + return sortMethodsByColumnID[sort.id](a[sort.id], b[sort.id]) + } } - } - return row => { - return this.props.sortMethod[sort.id](row[sort.id]) - } - }), sorting.map(d => !d.desc)) + return (a, b) => { + return this.props.defaultSortMethod(a[sort.id], b[sort.id]) + } + }), + sorting.map(d => !d.desc), + this.props.indexKey + ) - return sorted.map(row => { + sorted.forEach(row => { if (!row[this.props.subRowsKey]) { - return row - } - return { - ...row, - [this.props.subRowsKey]: this.sortData(row[this.props.subRowsKey], sorting, sortersByID) + return } + row[this.props.subRowsKey] = this.sortData(row[this.props.subRowsKey], sorting, sortMethodsByColumnID) }) + + return sorted } getMinRows () { diff --git a/src/utils.js b/src/utils.js index 35733a9..460eb02 100644 --- a/src/utils.js +++ b/src/utils.js @@ -64,23 +64,20 @@ function range (n) { return arr } -function orderBy (arr, funcs, dirs) { - return arr.sort((a, b) => { +function orderBy (arr, funcs, dirs, indexKey) { + return arr.sort((rowA, rowB) => { for (let i = 0; i < funcs.length; i++) { const comp = funcs[i] - const ca = comp(a) - const cb = comp(b) const desc = dirs[i] === false || dirs[i] === 'desc' - if (ca > cb) { - return desc ? -1 : 1 - } - if (ca < cb) { - return desc ? 1 : -1 + const sortInt = comp(rowA, rowB) + if (sortInt) { + return desc ? -sortInt : sortInt } } + // Use the row index for tie breakers return dirs[0] - ? a.__index - b.__index - : b.__index - a.__index + ? rowA[indexKey] - rowB[indexKey] + : rowB[indexKey] - rowA[indexKey] }) } diff --git a/stories/CustomSorting.js b/stories/CustomSorting.js index 0156188..3333fc6 100644 --- a/stories/CustomSorting.js +++ b/stories/CustomSorting.js @@ -17,17 +17,25 @@ export default () => { const columns = [{ Header: 'Name', columns: [{ - Header: 'First Name (Sorted by Length)', + Header: 'First Name (Sorted by Length, A-Z)', accessor: 'firstName', - sortMethod: value => { - return value.length + sortMethod: (a, b) => { + if (a.length === b.length) { + return a > b ? 1 : -1 + } + return a.length > b.length ? 1 : -1 } }, { - Header: 'Last Name (Sorted by last letter)', + Header: 'Last Name (Sorted in reverse, A-Z)', id: 'lastName', accessor: d => d.lastName, - sortMethod: value => { - return value.substring([value.length - 1]) + sortMethod: (a, b) => { + if (a === b) { + return 0 + } + const aReverse = a.split('').reverse().join('') + const bReverse = b.split('').reverse().join('') + return aReverse > bReverse ? 1 : -1 } }] }, {