-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Table multi column sort functionality (#966)
* Table passes mouse 'event' and Column 'defaultSortDirection' values to 'sort' prop handler * Added createMultiSort() helper to Table and export * Updated docs with example shown in PR * Added sort() callback as required param to createMultiSort * createMultiSort accounts for Mac 'meta' key too * Properly reset sort-by collection on regular click * Small docs tweak * Increase code coverage slightly
- Loading branch information
Showing
9 changed files
with
329 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
By default, `Table` assumes that its data will be sorted by single attribute, in either ascending or descending order. | ||
For advanced use cases, you may want to sort by multiple fields. | ||
This can be accomplished using the `createMultiSort` utility. | ||
|
||
```jsx | ||
import { | ||
createTableMultiSort, | ||
Column, | ||
Table, | ||
} from 'react-virtualized'; | ||
|
||
function sort({ | ||
sortBy, | ||
sortDirection, | ||
}) { | ||
// 'sortBy' is an ordered Array of fields. | ||
// 'sortDirection' is a map of field name to "ASC" or "DESC" directions. | ||
// Sort your collection however you'd like. | ||
// When you're done, setState() or update your Flux store, etc. | ||
} | ||
|
||
const sortState = createMultiSort(sort); | ||
|
||
// When rendering your header columns, | ||
// Use the sort state exposed by sortState: | ||
const headerRenderer = ({ dataKey, label }) => { | ||
const showSortIndicator = sortState.sortBy.includes(dataKey); | ||
return ( | ||
<> | ||
<span title={label}>{label}</span> | ||
{showSortIndicator && ( | ||
<SortIndicator sortDirection={sortState.sortDirection[dataKey]} /> | ||
)} | ||
</> | ||
); | ||
}; | ||
|
||
// Connect sortState to Table by way of the 'sort' prop: | ||
<Table | ||
{...tableProps} | ||
sort={sortState.sort} | ||
sortBy={undefined} | ||
sortDirection={undefined} | ||
> | ||
<Column | ||
{...columnProps} | ||
headerRenderer={headerRenderer} | ||
/> | ||
</Table> | ||
``` | ||
|
||
The `createMultiSort` utility also accepts default sort-by values: | ||
```js | ||
const sortState = createMultiSort(sort, { | ||
defaultSortBy: ['firstName', 'lastName'], | ||
defaultSortDirection: { | ||
firstName: 'ASC', | ||
lastName: 'ASC', | ||
}, | ||
}); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
import createMultiSort from './createMultiSort'; | ||
|
||
describe('createMultiSort', () => { | ||
function simulate( | ||
sort, | ||
dataKey, | ||
eventModifier = '', | ||
defaultSortDirection = 'ASC', | ||
) { | ||
sort({ | ||
defaultSortDirection, | ||
event: { | ||
ctrlKey: eventModifier === 'control', | ||
metaKey: eventModifier === 'meta', | ||
shiftKey: eventModifier === 'shift', | ||
}, | ||
sortBy: dataKey, | ||
}); | ||
} | ||
|
||
it('errors if the user did not specify a sort callback', () => { | ||
expect(createMultiSort).toThrow(); | ||
}); | ||
|
||
it('sets the correct default values', () => { | ||
const multiSort = createMultiSort(jest.fn(), { | ||
defaultSortBy: ['a', 'b'], | ||
defaultSortDirection: { | ||
a: 'ASC', | ||
b: 'DESC', | ||
}, | ||
}); | ||
expect(multiSort.sortBy).toEqual(['a', 'b']); | ||
expect(multiSort.sortDirection.a).toBe('ASC'); | ||
expect(multiSort.sortDirection.b).toBe('DESC'); | ||
}); | ||
|
||
it('sets the correct default sparse values', () => { | ||
const multiSort = createMultiSort(jest.fn(), { | ||
defaultSortBy: ['a', 'b'], | ||
}); | ||
expect(multiSort.sortBy).toEqual(['a', 'b']); | ||
expect(multiSort.sortDirection.a).toBe('ASC'); | ||
expect(multiSort.sortDirection.b).toBe('ASC'); | ||
}); | ||
|
||
describe('on click', () => { | ||
it('sets the correct default value for a field', () => { | ||
const multiSort = createMultiSort(jest.fn()); | ||
|
||
simulate(multiSort.sort, 'a'); | ||
expect(multiSort.sortBy).toEqual(['a']); | ||
expect(multiSort.sortDirection.a).toBe('ASC'); | ||
|
||
simulate(multiSort.sort, 'b', '', 'DESC'); | ||
expect(multiSort.sortBy).toEqual(['b']); | ||
expect(multiSort.sortDirection.b).toBe('DESC'); | ||
}); | ||
|
||
it('toggles a field value', () => { | ||
const multiSort = createMultiSort(jest.fn()); | ||
|
||
simulate(multiSort.sort, 'a'); | ||
expect(multiSort.sortBy).toEqual(['a']); | ||
expect(multiSort.sortDirection.a).toBe('ASC'); | ||
|
||
simulate(multiSort.sort, 'a'); | ||
expect(multiSort.sortBy).toEqual(['a']); | ||
expect(multiSort.sortDirection.a).toBe('DESC'); | ||
|
||
simulate(multiSort.sort, 'b', '', 'DESC'); | ||
expect(multiSort.sortBy).toEqual(['b']); | ||
expect(multiSort.sortDirection.b).toBe('DESC'); | ||
|
||
simulate(multiSort.sort, 'b', '', 'DESC'); | ||
expect(multiSort.sortBy).toEqual(['b']); | ||
expect(multiSort.sortDirection.b).toBe('ASC'); | ||
}); | ||
|
||
it('resets sort-by fields', () => { | ||
const multiSort = createMultiSort(jest.fn(), { | ||
defaultSortBy: ['a', 'b'], | ||
}); | ||
expect(multiSort.sortBy).toEqual(['a', 'b']); | ||
|
||
simulate(multiSort.sort, 'a'); | ||
expect(multiSort.sortBy).toEqual(['a']); | ||
}); | ||
}); | ||
|
||
describe('on shift click', () => { | ||
it('appends a field to the sort by list', () => { | ||
const multiSort = createMultiSort(jest.fn()); | ||
|
||
simulate(multiSort.sort, 'a'); | ||
expect(multiSort.sortBy).toEqual(['a']); | ||
expect(multiSort.sortDirection.a).toBe('ASC'); | ||
|
||
simulate(multiSort.sort, 'b', 'shift'); | ||
expect(multiSort.sortBy).toEqual(['a', 'b']); | ||
expect(multiSort.sortDirection.a).toBe('ASC'); | ||
expect(multiSort.sortDirection.b).toBe('ASC'); | ||
}); | ||
|
||
it('toggles an appended field value', () => { | ||
const multiSort = createMultiSort(jest.fn()); | ||
|
||
simulate(multiSort.sort, 'a'); | ||
expect(multiSort.sortBy).toEqual(['a']); | ||
expect(multiSort.sortDirection.a).toBe('ASC'); | ||
|
||
simulate(multiSort.sort, 'b', 'shift'); | ||
expect(multiSort.sortBy).toEqual(['a', 'b']); | ||
expect(multiSort.sortDirection.a).toBe('ASC'); | ||
expect(multiSort.sortDirection.b).toBe('ASC'); | ||
|
||
simulate(multiSort.sort, 'a', 'shift'); | ||
expect(multiSort.sortBy).toEqual(['a', 'b']); | ||
expect(multiSort.sortDirection.a).toBe('DESC'); | ||
expect(multiSort.sortDirection.b).toBe('ASC'); | ||
|
||
simulate(multiSort.sort, 'a', 'shift'); | ||
expect(multiSort.sortBy).toEqual(['a', 'b']); | ||
expect(multiSort.sortDirection.a).toBe('ASC'); | ||
expect(multiSort.sortDirection.b).toBe('ASC'); | ||
}); | ||
}); | ||
|
||
['control', 'meta'].forEach(modifier => { | ||
describe(`${modifier} click`, () => { | ||
it('removes a field from the sort by list', () => { | ||
const multiSort = createMultiSort(jest.fn(), { | ||
defaultSortBy: ['a', 'b'], | ||
}); | ||
expect(multiSort.sortBy).toEqual(['a', 'b']); | ||
|
||
simulate(multiSort.sort, 'a', modifier); | ||
expect(multiSort.sortBy).toEqual(['b']); | ||
|
||
simulate(multiSort.sort, 'b', modifier); | ||
expect(multiSort.sortBy).toEqual([]); | ||
}); | ||
|
||
it('ignores fields not in the list on control click', () => { | ||
const multiSort = createMultiSort(jest.fn(), { | ||
defaultSortBy: ['a', 'b'], | ||
}); | ||
expect(multiSort.sortBy).toEqual(['a', 'b']); | ||
|
||
simulate(multiSort.sort, 'c', modifier); | ||
expect(multiSort.sortBy).toEqual(['a', 'b']); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
/** @flow */ | ||
|
||
type SortDirection = 'ASC' | 'DESC'; | ||
|
||
type SortParams = { | ||
defaultSortDirection: SortDirection, | ||
event: MouseEvent, | ||
sortBy: string, | ||
}; | ||
|
||
type SortDirectionMap = {[string]: SortDirection}; | ||
|
||
type MultiSortOptions = { | ||
defaultSortBy: ?Array<string>, | ||
defaultSortDirection: ?SortDirectionMap, | ||
}; | ||
|
||
type MultiSortReturn = { | ||
/** | ||
* Sort property to be passed to the `Table` component. | ||
* This function updates `sortBy` and `sortDirection` values. | ||
*/ | ||
sort: (params: SortParams) => void, | ||
|
||
/** | ||
* Specifies the fields currently responsible for sorting data, | ||
* In order of importance. | ||
*/ | ||
sortBy: Array<string>, | ||
|
||
/** | ||
* Specifies the direction a specific field is being sorted in. | ||
*/ | ||
sortDirection: SortDirectionMap, | ||
}; | ||
|
||
export default function createMultiSort( | ||
sortCallback: Function, | ||
{defaultSortBy, defaultSortDirection = {}}: MultiSortOptions = {}, | ||
): MultiSortReturn { | ||
if (!sortCallback) { | ||
throw Error(`Required parameter "sortCallback" not specified`); | ||
} | ||
|
||
const sortBy = defaultSortBy || []; | ||
const sortDirection = {}; | ||
|
||
sortBy.forEach(dataKey => { | ||
sortDirection[dataKey] = defaultSortDirection.hasOwnProperty(dataKey) | ||
? defaultSortDirection[dataKey] | ||
: 'ASC'; | ||
}); | ||
|
||
function sort({ | ||
defaultSortDirection, | ||
event, | ||
sortBy: dataKey, | ||
}: SortParams): void { | ||
if (event.shiftKey) { | ||
// Shift + click appends a column to existing criteria | ||
if (sortDirection.hasOwnProperty(dataKey)) { | ||
sortDirection[dataKey] = | ||
sortDirection[dataKey] === 'ASC' ? 'DESC' : 'ASC'; | ||
} else { | ||
sortDirection[dataKey] = defaultSortDirection; | ||
sortBy.push(dataKey); | ||
} | ||
} else if (event.ctrlKey || event.metaKey) { | ||
// Control + click removes column from sort (if pressent) | ||
const index = sortBy.indexOf(dataKey); | ||
if (index >= 0) { | ||
sortBy.splice(index, 1); | ||
delete sortDirection[dataKey]; | ||
} | ||
} else { | ||
sortBy.length = 0; | ||
sortBy.push(dataKey); | ||
|
||
if (sortDirection.hasOwnProperty(dataKey)) { | ||
sortDirection[dataKey] = | ||
sortDirection[dataKey] === 'ASC' ? 'DESC' : 'ASC'; | ||
} else { | ||
sortDirection[dataKey] = defaultSortDirection; | ||
} | ||
} | ||
|
||
// Notify application code | ||
sortCallback({ | ||
sortBy, | ||
sortDirection, | ||
}); | ||
} | ||
|
||
return { | ||
sort, | ||
sortBy, | ||
sortDirection, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters