Better custom sorting

This commit is contained in:
Tanner Linsley
2017-05-10 13:57:23 -06:00
parent 53c6d17b40
commit bdf759c8f8
7 changed files with 112 additions and 67 deletions

View File

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

View File

@@ -166,6 +166,7 @@ These are all of the available props (and their default values) for the main `<R
},
resizable: true,
defaultResizing: [],
defaultSortMethod: undefined,
// Controlled State Overrides (see Fully Controlled Component section)
page: undefined,
@@ -770,6 +771,34 @@ Sorting comes built in with React-Table. Click column header to sort by its colu
## Multi-Sort
When clicking on a column header, hold shift to multi-sort! You can toggle `ascending` `descending` and `none` for multi-sort columns. Clicking on a header without holding shift will clear the multi-sort and replace it with the single sort of that column. It's quite handy!
## Custom Sorting Algorithm
To override the default sorting algorithm for the whole table use the `defaultSortMethod` prop.
To override the sorting algorithm for a single column, use the `sortMethod` column property.
Supply a function that implements the native javascript [`Array.sort`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) interface. This is React Table's default sorting algorithm:
- `a` the first value to compare
- `b` the second value to compare
- `dir` the
```javascript
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 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.

View File

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

View File

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

View File

@@ -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 () {

View File

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

View File

@@ -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
}
}]
}, {