From fa662bc33d4356b79390dd24140b5cc56b75e1d6 Mon Sep 17 00:00:00 2001 From: Zeyu Yang <45103519+goldflag@users.noreply.github.com> Date: Sun, 25 Apr 2021 18:54:12 -0400 Subject: [PATCH] feat: Add two new built in sort types (#3235) * feat(sortTypes): Add string and number sort types New sort types better serve certain edge cases for columns with string or number values. Number strips out non decimal or numerical value from strings and only sorts what is left. String sorts strings case-insensitively until it reaches a title, and then it will do case-sensitive sort (https://github.com/tannerlinsley/react-table/issues/3137). * docs(useSortBy): Add two new sort types Add new built-in sort functions 'number' and 'string' * test(useSortBy): Increase coverage for useSortBy Add new sort types (number, string) to certain columns to achieve sufficient coverage. --- docs/src/pages/docs/api/useSortBy.md | 2 +- src/plugin-hooks/tests/useSortBy.test.js | 2 + src/sortTypes.js | 59 ++++++++++++++++++++---- 3 files changed, 53 insertions(+), 10 deletions(-) diff --git a/docs/src/pages/docs/api/useSortBy.md b/docs/src/pages/docs/api/useSortBy.md index f2ba1acbdb..32a7170e1f 100644 --- a/docs/src/pages/docs/api/useSortBy.md +++ b/docs/src/pages/docs/api/useSortBy.md @@ -75,7 +75,7 @@ The following options are supported on any `Column` object passed to the `column - `sortType: String | Function(rowA: , rowB: , columnId: String, desc: Bool)` - Used to compare 2 rows of data and order them correctly. - If a **function** is passed, it must be **memoized**. The sortType function should return 1 if rowA is larger, and -1 if rowB is larger. `react-table` will take care of the rest. - - String options: `basic`, `datetime`, `alphanumeric`. Defaults to `alphanumeric`. + - String options: `string`, `number`, `basic`, `datetime`, `alphanumeric`. Defaults to `alphanumeric`. - The resolved function from the this string/function will be used to sort the this column's data. - If a `string` is passed, the function with that name located on either the custom `sortTypes` option or the built-in sorting types object will be used. - If a `function` is passed, it will be used. diff --git a/src/plugin-hooks/tests/useSortBy.test.js b/src/plugin-hooks/tests/useSortBy.test.js index 86dd6d9939..39f9ca9af6 100644 --- a/src/plugin-hooks/tests/useSortBy.test.js +++ b/src/plugin-hooks/tests/useSortBy.test.js @@ -155,6 +155,7 @@ function App({ useTableRef, initialState }) { { Header: 'First Name', accessor: 'firstName', + sortType: 'string' }, { Header: 'Last Name', @@ -168,6 +169,7 @@ function App({ useTableRef, initialState }) { { Header: 'Age', accessor: 'age', + sortType: 'number' }, { Header: 'Visits', diff --git a/src/sortTypes.js b/src/sortTypes.js index d7cfe3e53d..7efcd6c71e 100644 --- a/src/sortTypes.js +++ b/src/sortTypes.js @@ -4,8 +4,8 @@ const reSplitAlphaNumeric = /([0-9]+)/gm // It handles numbers, mixed alphanumeric combinations, and even // null, undefined, and Infinity export const alphanumeric = (rowA, rowB, columnId) => { - let a = getRowValueByColumnID(rowA, columnId) - let b = getRowValueByColumnID(rowB, columnId) + let [a, b] = getRowValuesByColumnID(rowA, rowB, columnId) + // Force to strings (or "" for unsupported types) a = toString(a) b = toString(b) @@ -52,10 +52,8 @@ export const alphanumeric = (rowA, rowB, columnId) => { return a.length - b.length } - export function datetime(rowA, rowB, columnId) { - let a = getRowValueByColumnID(rowA, columnId) - let b = getRowValueByColumnID(rowB, columnId) + let [a, b] = getRowValuesByColumnID(rowA, rowB, columnId) a = a.getTime() b = b.getTime() @@ -64,8 +62,51 @@ export function datetime(rowA, rowB, columnId) { } export function basic(rowA, rowB, columnId) { - let a = getRowValueByColumnID(rowA, columnId) - let b = getRowValueByColumnID(rowB, columnId) + let [a, b] = getRowValuesByColumnID(rowA, rowB, columnId) + + return compareBasic(a, b) +} + +export function string(rowA, rowB, columnId) { + let [a, b] = getRowValuesByColumnID(rowA, rowB, columnId) + + a = a.split('').filter(Boolean) + b = b.split('').filter(Boolean) + + while (a.length && b.length) { + let aa = a.shift() + let bb = b.shift() + + let alower = aa.toLowerCase() + let blower = bb.toLowerCase() + + // Case insensitive comparison until characters match + if (alower > blower) { + return 1 + } + if (blower > alower) { + return -1 + } + // If lowercase characters are identical + if (aa > bb) { + return 1 + } + if (bb > aa) { + return -1 + } + continue + } + + return a.length - b.length +} + +export function number(rowA, rowB, columnId) { + let [a, b] = getRowValuesByColumnID(rowA, rowB, columnId) + + const replaceNonNumeric = /[^0-9.]/gi + + a = Number(String(a).replace(replaceNonNumeric, '')) + b = Number(String(b).replace(replaceNonNumeric, '')) return compareBasic(a, b) } @@ -76,8 +117,8 @@ function compareBasic(a, b) { return a === b ? 0 : a > b ? 1 : -1 } -function getRowValueByColumnID(row, columnId) { - return row.values[columnId] +function getRowValuesByColumnID(row1, row2, columnId) { + return [row1.values[columnId], row2.values[columnId]] } function toString(a) {