diff --git a/homedocs/src/pages/docs/tables/grid.mdx b/homedocs/src/pages/docs/tables/grid.mdx index e28fc5018..60b02134b 100644 --- a/homedocs/src/pages/docs/tables/grid.mdx +++ b/homedocs/src/pages/docs/tables/grid.mdx @@ -359,8 +359,10 @@ See also: [Row Drag and Drop](/docs/tables/row-drag-and-drop) | `header` | `string \| object` | Text or configuration object for the column header. | | `format` | `string` | Format string for cell values. | | `align` | `string` | Column alignment: `left`, `right`, or `center`. | +| `value` | `selector` | Selector (binding, template, expression or computable) for the displayed cell value. Use it for computed values or columns without a direct record field. | | `sortable` | `boolean` | Set to `true` to make the column sortable. | | `sortField` | `string` | Alternative field used for sorting. | +| `sortValue` | `selector` | Selector used for sorting instead of the field or the displayed value. Takes precedence over all other sort sources. | | `primarySortDirection` | `string` | Initial sort direction on first click: `ASC` or `DESC`. | | `aggregate` | `string` | Aggregate function: `sum`, `count`, `distinct`, `avg`. | | `aggregateField` | `string` | Field used for aggregation if different from `field`. | diff --git a/packages/cx/src/widgets/grid/Grid.tsx b/packages/cx/src/widgets/grid/Grid.tsx index 157abe289..1db9b0e27 100644 --- a/packages/cx/src/widgets/grid/Grid.tsx +++ b/packages/cx/src/widgets/grid/Grid.tsx @@ -919,10 +919,13 @@ export class Grid extends ContainerBase, GridInstance let sortField = null; - if (isDefined(this.sortField) && isDefined(this.sortDirection)) { + // rebuild sorters from the sortField/sortDirection bindings only if a sort field + // is actually set; sorts identified by a value selector (columns without a field) + // live in data.sorters/state.sorters and cannot round-trip through a field name + if (isDefined(this.sortField) && isDefined(this.sortDirection) && data.sortField) { let sorter = { field: data.sortField, - direction: data.sortDirection, + direction: data.sortDirection || "ASC", }; sortField = data.sortField; data.sorters = [sorter]; @@ -939,11 +942,11 @@ export class Grid extends ContainerBase, GridInstance } if (sortField) { - for (let l = 1; l < 10; l++) { + for (let l = 0; l < 10; l++) { let line = instance.row[`line${l}`]; - let sortColumn = line && line.columns && line.columns.find((c: any) => c.field == sortField); + let sortColumn = line && line.columns && line.columns.find((c: any) => (c.sortField || c.field) == sortField); if (sortColumn) { - data.sorters[0].value = sortColumn.sortValue || sortColumn.value; + data.sorters[0].value = isDefined(sortColumn.sortValue) ? sortColumn.sortValue : sortColumn.value; data.sorters[0].comparer = sortColumn.comparer; data.sorters[0].sortOptions = sortColumn.sortOptions; break; @@ -1307,8 +1310,18 @@ export class Grid extends ContainerBase, GridInstance if (hdwidget.sortable && header.widget.allowSorting) { mods.push("sortable"); - if (data.sorters && data.sorters[0].field == (hdwidget.sortField || hdwidget.field)) { - mods.push("sorted-" + data.sorters[0].direction.toLowerCase()); + let sorter = data.sorters && data.sorters[0]; + let sortColumnField = hdwidget.sortField || hdwidget.field; + let sortColumnValue = isDefined(hdwidget.sortValue) ? hdwidget.sortValue : hdwidget.value; + // a sort is identified by its (field, value selector) pair, so columns + // sorting by the same field through different value selectors don't both match + let sorted = + sorter && + !!sorter.direction && + (sortColumnField ? sorter.field == sortColumnField : !sorter.field && isDefined(sortColumnValue)) && + sorter.value === sortColumnValue; + if (sorted) { + mods.push("sorted-" + sorter.direction.toLowerCase()); sortIcon = ; } } @@ -1481,17 +1494,16 @@ export class Grid extends ContainerBase, GridInstance let header = column.components[`header${headerLine + 1}`]; let field = column.sortField || column.field; - let value = column.sortValue || column.value; + let value = isDefined(column.sortValue) ? column.sortValue : column.value; let comparer = column.comparer; let sortOptions = column.sortOptions; - if (header && header.allowSorting && column.sortable && (field || value || data.sortField)) { + if (header && header.allowSorting && column.sortable && (field || isDefined(value))) { let direction = column.primarySortDirection ?? "ASC"; - if ( - isNonEmptyArray(data.sorters) && - ((!!data.sorters[0].field && data.sorters[0].field == (field || data.sortField)) || - (!!value && data.sorters[0].value == value)) - ) { + // the column matches the active sorter only if both the field and the value + // selector are the same; two columns may sort by the same field through + // different value selectors and represent different sorts + if (isNonEmptyArray(data.sorters) && data.sorters[0].field == field && data.sorters[0].value === value) { if (data.sorters[0].direction == "ASC" && (!this.clearableSort || direction == "ASC")) direction = "DESC"; else if (data.sorters[0].direction == "DESC" && (!this.clearableSort || direction == "DESC")) direction = "ASC";