Skip to content

Commit

Permalink
refactor sorting (#776)
Browse files Browse the repository at this point in the history
  • Loading branch information
jbetancur authored Feb 22, 2021
1 parent ed6068f commit f95f329
Show file tree
Hide file tree
Showing 12 changed files with 2,914 additions and 4,361 deletions.
36 changes: 34 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[![Build Status](https://travis-ci.com/jbetancur/react-data-table-component.svg?branch=master)]( ) [![npm version](https://badge.fury.io/js/react-data-table-component.svg)](https://badge.fury.io/js/react-data-table-component) [![codecov](https://codecov.io/gh/jbetancur/react-data-table-component/branch/master/graph/badge.svg)](https://codecov.io/gh/jbetancur/react-data-table-component) [![Storybook](https://cdn.jsdelivr.net/gh/storybookjs/brand@master/badge/badge-storybook.svg)](https://jbetancur.github.io/react-data-table-component)
[![Build Status](https://travis-ci.com/jbetancur/react-data-table-component.svg?branch=master)]( ) [![npm version](https://badge.fury.io/js/react-data-table-component.svg)](https://badge.fury.io/js/react-data-table-component) [![codecov](https://codecov.io/gh/jbetancur/react-data-table-component/branch/master/graph/badge.svg)](https://codecov.io/gh/jbetancur/react-data-table-component) [![Storybook](https://cdn.jsdelivr.net/gh/storybookjs/brand@master/badge/badge-storybook.svg)](https://jbetancur.github.io/react-data-table-component)

<!-- TOC -->

Expand Down Expand Up @@ -34,6 +34,7 @@
- [Using Custom Checkboxes and Indeterminate State](#using-custom-checkboxes-and-indeterminate-state)
- [Custom Cells](#custom-cells)
- [Expandable Rows](#expandable-rows)
- [Custom Sort Function](#custom-sort-function)
- [UI Library Integration](#ui-library-integration)
- [Optimizing for Performance and Caveats](#optimizing-for-performance-and-caveats)
- [Passing non-primitive props (objects, arrays and functions)](#passing-non-primitive-props-objects-arrays-and-functions)
Expand Down Expand Up @@ -112,7 +113,7 @@ Nothing new here - we are using an array of object literals and properties to de
| name | string, component or number | no | the display name of our Column e.g. 'Name' |
| selector | string or (row, index) => {} | no | a data set property in dot notation. e.g. <br /> `property1.nested1.nested2` <br /> `property1.items[0].nested2` <br /> or as a function e.g. <br /> `row => row.timestamp`. A `selector` is required anytime you want to display data but can be ommitted if your column does not require showing data (e.g. an actions column) |
| sortable | bool | no | if the column is sortable. <br /><br />**Note:** `selector` is required for the column to sort |
| sortFunction | function | no | custom sorting function e.g. `(rowA, rowB) => rowA.myIndex - rowB.myIndex` (see [Array.prototype.sort()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)) |
| sortFunction | function | no | by default RDT uses lodash `lodash.orderBy`, however, you can override the default behavior by passing in a custom sort function. [defining a custom sort function](#-custom-sort-function) |
| format | (row, index) => {} | no | apply formatting to the selector e.g. `row => moment(row.timestamp).format('lll')` without changing the actual selector value. |
| cell | (row, index, column, id) => {} | no | for ultimate control use `cell` to render your own custom component!<br />e.g `row => <h2>{row.title}</h2>`. <br /> <br />if you are using properties such as: `onRowClicked`, `onRowDoubleClicked`, `expandOnRowClicked` or `expandOnRowDoubleClicked` then those click events will be ignored when clicking on your custom cell. To allow `RowClicked` events you can add `data-tag="allowRowEvents"` to your custom cell component elements. If your custom cell component is more complex and has nested elements you want to add `data-tag="allowRowEvents"` to the innermost element or on the elements you want to propagate the click event to. <br />e.g `row => <h2 data-tag="allowRowEvents">{row.title}</h2>` <br /><br />**Note:** that using `cell` **negates `format`**. |
| grow | number | no | [flex-grow](https://developer.mozilla.org/en-US/docs/Web/CSS/flex-grow) of the column. This is useful if you want a column to take up more width than its relatives (without having to set widths explicitly). this will be affected by other columns where you have explicitly set widths |
Expand Down Expand Up @@ -697,6 +698,37 @@ class MyComponent extends Component {
};
```

## Custom Sort Function

By default RDT uses lodash.orderBy, however, if you wish to override the internal sorting you can pass in your own sorting algorithm. The callback signature will give you access to RDT internal fields. Here is an example of how to use `Array.sort`;

- `rows` your data rows
- `selector` is the function you defined on your column.selector. We're going to call this function and pass in the row data in the example below.
- `direction` RDT's current sorting position.

```js
const customSort = (rows, selector, direction) => {
return rows.sort((rowA, rowB) => {
// use the selector function to resolve your field names by passing the sort comparitors
const aField = selector(rowA)
const bField = selector(rowB)

let comparison = 0;

if (aField > bField) {
comparison = 1;
} else if (aField < bField) {
comparison = -1;
}

return direction === 'desc' ? comparison * -1 : comparison;
});
};

...
<DataTable .... sortFunction={customSort} />
```

## UI Library Integration

React Data Table Component makes it easy to incorporate ui components from other libraries for overriding things like the sort icon, select checkbox.
Expand Down
40 changes: 20 additions & 20 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,31 +41,31 @@
"devDependencies": {
"@material-ui/core": "^4.11.3",
"@material-ui/icons": "^4.11.2",
"@storybook/addon-a11y": "^6.1.15",
"@storybook/addon-actions": "^6.1.15",
"@storybook/addon-console": "^1.2.2",
"@storybook/addon-links": "^6.1.15",
"@storybook/addon-storysource": "^6.1.15",
"@storybook/addon-a11y": "^6.1.18",
"@storybook/addon-actions": "^6.1.18",
"@storybook/addon-console": "^1.2.3",
"@storybook/addon-links": "^6.1.18",
"@storybook/addon-storysource": "^6.1.18",
"@storybook/react": "^6.1.15",
"@testing-library/react": "^11.2.3",
"@testing-library/react": "^11.2.5",
"@types/faker": "^5.1.5",
"@types/jest": "^26.0.20",
"@types/lodash-es": "^4.17.4",
"@types/lodash.orderby": "^4.6.6",
"@types/node": "^14.14.22",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@types/node": "^14.14.31",
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.1",
"@types/styled-components": "^5.1.7",
"@typescript-eslint/eslint-plugin": "^4.14.1",
"@typescript-eslint/parser": "^4.14.1",
"@typescript-eslint/eslint-plugin": "^4.15.1",
"@typescript-eslint/parser": "^4.15.1",
"axios": "^0.21.1",
"babel-eslint": "^10.1.0",
"codecov": "^3.8.1",
"eslint": "^7.18.0",
"eslint-config-prettier": "^7.2.0",
"eslint": "^7.20.0",
"eslint-config-prettier": "^8.0.0",
"eslint-config-react-app": "^6.0.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jest": "^24.1.3",
"eslint-plugin-jest": "^24.1.5",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-react": "^7.22.0",
Expand All @@ -74,27 +74,27 @@
"jest": "^26.6.3",
"jest-styled-components": "^7.0.3",
"jest-watch-typeahead": "^0.6.1",
"lodash-es": "^4.17.20",
"lodash-es": "^4.17.21",
"memoize-one": "^5.1.1",
"moment": "^2.29.1",
"prettier": "^2.2.1",
"react": "^17.0.1",
"react-app-polyfill": "^2.0.0",
"react-dom": "^17.0.1",
"rimraf": "^3.0.2",
"rollup": "^2.38.0",
"rollup": "^2.39.0",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.29.0",
"rollup-plugin-typescript2": "^0.30.0",
"rollup-plugin-visualizer": "^4.2.0",
"styled-components": "^5.0.1",
"stylelint": "^13.9.0",
"stylelint": "^13.11.0",
"stylelint-config-recommended": "^3.0.0",
"stylelint-config-styled-components": "^0.1.1",
"stylelint-processor-styled-components": "^1.10.0",
"ts-jest": "^26.4.4",
"typescript": "^4.1.3"
"ts-jest": "^26.5.1",
"typescript": "^4.1.5"
},
"dependencies": {
"deepmerge": "^4.2.2",
Expand Down
8 changes: 2 additions & 6 deletions src/DataTable/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -332,12 +332,7 @@ function DataTable<T extends RowRecord>(props: TableProps<T>): JSX.Element {
<Table disabled={disabled} className="rdt_Table" role="table">
{showTableHead() && (
<TableHead className="rdt_TableHead" role="rowgroup">
<TableHeadRow
className="rdt_TableHeadRow"
role="row"
dense={dense}
disabled={progressPending || rows.length === 0}
>
<TableHeadRow className="rdt_TableHeadRow" role="row" dense={dense}>
{selectableRows &&
(showSelectAll ? (
<CellBase style={{ flex: '0 0 48px' }} />
Expand All @@ -359,6 +354,7 @@ function DataTable<T extends RowRecord>(props: TableProps<T>): JSX.Element {
<TableCol
key={column.id}
column={column}
disabled={progressPending || rows.length === 0}
rows={rows}
pagination={pagination}
paginationServer={paginationServer}
Expand Down
2 changes: 1 addition & 1 deletion src/DataTable/TableBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const TableBody = styled.div<{
fixedHeader &&
css`
max-height: ${hasOffset ? `calc(${fixedHeaderScrollHeight} - ${offset})` : fixedHeaderScrollHeight};
overflow-y: scroll;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
`};
`;
Expand Down
72 changes: 46 additions & 26 deletions src/DataTable/TableCol.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import styled from 'styled-components';
import styled, { css } from 'styled-components';
import { Cell, CellProps } from './Cell';
import NativeSortIcon from '../icons/NativeSortIcon';
import { sort } from './util';
Expand All @@ -10,16 +10,12 @@ const TableColStyle = styled(Cell)<CellProps>`
`;

interface ColumnSortableProps {
sortable: boolean | undefined;
disabled: boolean;
sortActive: boolean;
sortable: boolean | undefined;
}

const ColumnSortable = styled.div<ColumnSortableProps>`
display: inline-flex;
align-items: center;
height: 100%;
line-height: 1;
user-select: none;
const sortableCSS = css<ColumnSortableProps>`
${({ theme, sortActive }) => (sortActive ? theme.headCells.activeSortStyle : theme.headCells.inactiveSortStyle)};
span.__rdt_custom_sort_icon__ {
Expand All @@ -43,19 +39,38 @@ const ColumnSortable = styled.div<ColumnSortableProps>`
}
&:hover {
${({ sortable }) => sortable && 'cursor: pointer'};
${({ sortable, theme }) => sortable && theme.headCells.activeStyle};
cursor: pointer;
${({ theme }) => theme.headCells.activeStyle};
span,
span.__rdt_custom_sort_icon__ * {
${({ sortActive, sortable }) => !sortActive && sortable && 'opacity: 1'};
${({ sortActive }) => !sortActive && 'opacity: 1'};
}
}
`;

const ColumnSortable = styled.div<ColumnSortableProps>`
align-items: center;
height: 100%;
line-height: 1;
outline: none;
user-select: none;
display: inline-flex;
overflow: hidden;
${({ disabled, sortable }) => sortable && !disabled && sortableCSS};
`;

const ColumnText = styled.div`
overflow: hidden;
font-weight: 500;
white-space: nowrap;
text-overflow: ellipsis;
`;

type TableColProps<T> = {
rows: T[];
column: TableColumn<T>;
disabled: boolean;
sortIcon?: React.ReactNode;
pagination: boolean;
paginationServer: boolean;
Expand All @@ -71,6 +86,7 @@ type TableColProps<T> = {
function TableCol<T>({
rows,
column,
disabled,
selectedColumn,
sortDirection,
sortFunction,
Expand Down Expand Up @@ -103,16 +119,19 @@ function TableCol<T>({
direction = sortDirection === 'asc' ? 'desc' : 'asc';
}

let sortedRows = sort<T>(rows, column.selector, direction, sortFunction);
let sortedRows = rows;

if (!sortServer) {
sortedRows = sort(rows, column.selector, direction, sortFunction);

// declare this as a const since ts throws strict null check on a ternary line 108
// sortFn is still checked for undefined or null
const sortFn = column.sortFunction;
// colCustomSortFn is still checked for undefined or null
const colCustomSortFn = column.sortFunction;

if (sortFn) {
const customSortFunction = direction === 'asc' ? sortFn : (a: T, b: T) => sortFn(a, b) * -1;
if (colCustomSortFn) {
const customSortFunction = direction === 'asc' ? colCustomSortFn : (a: T, b: T) => colCustomSortFn(a, b) * -1;

sortedRows = [...rows].sort(customSortFunction);
sortedRows = [...rows].sort(customSortFunction);
}
}

onSort({
Expand Down Expand Up @@ -169,16 +188,17 @@ function TableCol<T>({
role="columnheader"
tabIndex={0}
className="rdt_TableCol_Sortable"
onClick={handleSortChange}
onKeyPress={handleKeyPress}
onClick={!disabled ? handleSortChange : undefined}
onKeyPress={!disabled ? handleKeyPress : undefined}
sortActive={!disabled && sortActive}
sortable={column.sortable}
sortActive={sortActive}
disabled={disabled}
>
{customSortIconRight && renderCustomSortIcon()}
{nativeSortIconRight && renderNativeSortIcon(sortActive)}
<div>{column.name}</div>
{customSortIconLeft && renderCustomSortIcon()}
{nativeSortIconLeft && renderNativeSortIcon(sortActive)}
{!disabled && customSortIconRight && renderCustomSortIcon()}
{!disabled && nativeSortIconRight && renderNativeSortIcon(sortActive)}
<ColumnText>{column.name}</ColumnText>
{!disabled && customSortIconLeft && renderCustomSortIcon()}
{!disabled && nativeSortIconLeft && renderNativeSortIcon(sortActive)}
</ColumnSortable>
)}
</TableColStyle>
Expand Down
7 changes: 1 addition & 6 deletions src/DataTable/TableHeadRow.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import styled, { css } from 'styled-components';

const disabledCSS = css`
pointer-events: none;
`;
import styled from 'styled-components';

const TableHeadRow = styled.div<{
dense?: boolean;
Expand All @@ -13,7 +9,6 @@ const TableHeadRow = styled.div<{
width: 100%;
${({ theme }) => theme.headRow.style};
${({ dense, theme }) => dense && theme.headRow.denseStyle};
${({ disabled }) => disabled && disabledCSS};
`;

export default TableHeadRow;
Loading

0 comments on commit f95f329

Please sign in to comment.