Add multi-columns rows sorting with customizable comparison function for each column.
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
parent
5ac1e4e7b8
commit
2513e711b1
3 changed files with 75 additions and 11 deletions
|
@ -1,6 +1,6 @@
|
||||||
import React, {startTransition, useEffect, useRef, useState} from "react";
|
import React, { useEffect, useRef, useState} from "react";
|
||||||
import {ColumnKey} from "./Column";
|
import {ColumnKey} from "./Column";
|
||||||
import {normalizeRowDefinition, RowCells, RowData, RowDefinition} from "./Row";
|
import {normalizeRowDefinition, RowData, RowDefinition} from "./Row";
|
||||||
import {CellDefinition} from "./Cell";
|
import {CellDefinition} from "./Cell";
|
||||||
import {SmartableData} from "./Smartable";
|
import {SmartableData} from "./Smartable";
|
||||||
import {Modify, Promisable} from "@kernelui/core";
|
import {Modify, Promisable} from "@kernelui/core";
|
||||||
|
@ -84,7 +84,7 @@ class AsyncManager<CK extends ColumnKey>
|
||||||
*/
|
*/
|
||||||
protected promisedRowsCells: Partial<Record<CK, Promised<CellDefinition>>>[] = [];
|
protected promisedRowsCells: Partial<Record<CK, Promised<CellDefinition>>>[] = [];
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rows data.
|
* Rows data.
|
||||||
* @protected
|
* @protected
|
||||||
|
|
|
@ -25,7 +25,7 @@ export function createColumns<K extends ColumnKey>(...columns: [K, Column][]): C
|
||||||
/**
|
/**
|
||||||
* Smartable column definition.
|
* Smartable column definition.
|
||||||
*/
|
*/
|
||||||
export interface Column
|
export interface Column<T = any>
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Column title element.
|
* Column title element.
|
||||||
|
@ -36,6 +36,13 @@ export interface Column
|
||||||
* Column cell default element.
|
* Column cell default element.
|
||||||
*/
|
*/
|
||||||
cellElement?: React.ReactElement;
|
cellElement?: React.ReactElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cells data comparison in the column.
|
||||||
|
* @param a First data to compare.
|
||||||
|
* @param b Second data to compare.
|
||||||
|
*/
|
||||||
|
compare?: (a: T, b: T) => number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import React from "react";
|
import React, {useMemo} from "react";
|
||||||
import {AutoColumnContextProvider, ColumnHeading, ColumnKey, Columns} from "./Column";
|
import {AutoColumnContextProvider, ColumnHeading, ColumnKey, Columns, SortState, SortType} from "./Column";
|
||||||
import {SmartableProperties, useTable} from "./Smartable";
|
import {SmartableProperties, useTable} from "./Smartable";
|
||||||
import {RowInstance, RowLoader} from "./Row";
|
import {RowInstance, RowLoader} from "./Row";
|
||||||
import {useAsyncManager} from "./AsyncManager";
|
import {CurrentRowData, useAsyncManager} from "./AsyncManager";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Smartable instance component properties.
|
* Smartable instance component properties.
|
||||||
|
@ -53,14 +53,19 @@ export function ColumnsHeadings<CK extends ColumnKey>({columns}: {columns: Colum
|
||||||
export function TableBody<CK extends ColumnKey>()
|
export function TableBody<CK extends ColumnKey>()
|
||||||
{
|
{
|
||||||
// Get data from table.
|
// Get data from table.
|
||||||
const {data} = useTable();
|
const {data, columns, columnsSortState} = useTable<CK>();
|
||||||
|
|
||||||
// Get current data state from the async table value.
|
// Get current data state from the async table value.
|
||||||
const {currentDataState} = useAsyncManager(data);
|
const {currentDataState} = useAsyncManager<CK>(data);
|
||||||
|
|
||||||
|
// Memorize sorted rows.
|
||||||
|
const sortedRows = useMemo(() => (
|
||||||
|
sortRows<CK>(currentDataState.rows, columns, columnsSortState)
|
||||||
|
), [currentDataState.rows, columns, columnsSortState]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
Array.isArray(currentDataState?.rows) ? (
|
sortedRows ? (
|
||||||
currentDataState.rows.map((rowData, index) => (
|
sortedRows.map((rowData, index) => (
|
||||||
// Rendering each row from its definition.
|
// Rendering each row from its definition.
|
||||||
<RowInstance row={rowData} />
|
<RowInstance row={rowData} />
|
||||||
))
|
))
|
||||||
|
@ -69,3 +74,55 @@ export function TableBody<CK extends ColumnKey>()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort the given rows from the columns sort state.
|
||||||
|
* @param rows Rows to sort.
|
||||||
|
* @param columns Columns definition.
|
||||||
|
* @param columnsSortState Columns current sort state.
|
||||||
|
*/
|
||||||
|
export function sortRows<CK extends ColumnKey>(rows: CurrentRowData<CK>[], columns: Columns<CK>, columnsSortState: Record<CK, SortState>): CurrentRowData<CK>[]
|
||||||
|
{
|
||||||
|
// Normalize value to undefined when rows are not loaded.
|
||||||
|
if (!Array.isArray(rows)) return undefined;
|
||||||
|
|
||||||
|
// Get the sort column states following their priority order.
|
||||||
|
const orderedSortColumns = (Object.entries(columnsSortState) as [CK, SortState][]).sort((a, b) => (
|
||||||
|
a[1].order - b[1].order
|
||||||
|
));
|
||||||
|
|
||||||
|
// Sort the given rows.
|
||||||
|
return rows.toSorted((a, b) => {
|
||||||
|
for (const [columnKey, columnSort] of orderedSortColumns)
|
||||||
|
{
|
||||||
|
// Get the current comparison result for the given cells' data.
|
||||||
|
const comparison =
|
||||||
|
(columnSort.type == SortType.DESC ? 1 : -1) *
|
||||||
|
(columns[columnKey]?.compare ?? genericCompare)(a.cells[columnKey]?.data, b.cells[columnKey]?.data);
|
||||||
|
|
||||||
|
if (comparison != 0)
|
||||||
|
// If an order can be determined from the current comparison, returning it.
|
||||||
|
return comparison;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The comparisons always returned 0.
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic comparison function for table data.
|
||||||
|
* @param a First data to compare.
|
||||||
|
* @param b Second data to compare.
|
||||||
|
*/
|
||||||
|
export function genericCompare(a: any, b: any): number
|
||||||
|
{
|
||||||
|
if (typeof a == "number" && typeof a == typeof b)
|
||||||
|
{ // Compare numbers.
|
||||||
|
return b - a;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // Compare data as strings.
|
||||||
|
return String(b).localeCompare(String(a));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue