Skip to content

Grid sorting misbehaves for columns using value instead of / together with field #1314

Description

@nebojsa-peric

Grid sorting misbehaves for columns using value instead of / together with field

Version: cx 26.7.5 (reproduced on master)
Repro: litmus/bugs/grid-sorting-without-field.js (see PR #1313) — three grids, one per issue below.

Columns whose displayed content comes from a value selector (tpl/expr/computable) interact
badly with the sorting machinery, which mostly assumes a sort is identified by a field name.


Issue 1 — Sort indicator lights up on every field-less column

Setup: plain header-click sorting, no sortField/sortDirection/sorters bindings.
Two sortable columns defined with only a value (no field).

Actual: sorting itself works (the comparer correctly falls back to the value selector),
but after clicking one value-only column, both value-only headers get the sorted style and
icon (cxs-sorted-asc/desc).

Why: renderHeader decides "is this the sorted column" via
data.sorters[0].field == (hdwidget.sortField || hdwidget.field) (Grid.tsx ~line 1310).
For a value-only sorter both sides are undefined, so the check passes for every
field-less sortable column.


Issue 2 — With sortField/sortDirection bindings, value-only columns can't be sorted at all (and can crash the grid)

Setup: same columns, but the grid defines sortField-bind / sortDirection-bind.

Actual:

  1. If the bound values are initially undefined, the grid crashes on first render with
    TypeError: Cannot read properties of undefined (reading 'toLowerCase'). prepareData
    fabricates a sorter { field: undefined, direction: undefined }, the header check from
    Issue 1 matches it against any field-less column (undefined == undefined), and
    direction.toLowerCase() throws.
  2. With initialized bindings, clicking a value-only header never sorts. Instead it clears
    the sortField binding and merely toggles the direction of the previously active sort.

Why:

  • prepareData (Grid.tsx ~lines 920–952) rebuilds data.sorters from data.sortField alone,
    discarding the value selector captured on header click. The fix-up loop that re-attaches
    value/comparer/sortOptions can only find a column by matching c.field == sortField
    impossible when the column has no field. The resulting sorter has neither field nor value,
    so the comparer selector is () => null and the order never changes.
  • In onHeaderClick (Grid.tsx ~line 1492), the current-sorter check falls back to
    data.sortField when the column has no field, so a click on a value-only column always
    "matches" the currently sorted field and toggles its direction instead of starting a new sort.
  • Two smaller defects in the same fix-up loop: it iterates line1line9 (skipping line0,
    which createRowTemplate supports), and matches only c.field, never c.sortField.

Issue 3 — value silently overrides field and even explicit sortField; docs say the opposite

Setup: records with a numeric priority field (1, 2, 10), displayed via
value: { tpl: "P{$record.priority}" }. One column with field: "priority" + value,
another with sortField: "priority" + value.

Actual: both columns sort as strings by the displayed label — P1, P10, P2 — instead of
numerically by the field (1, 2, 10). The explicit sortField has no effect.

Why: onHeaderClick (Grid.tsx ~lines 1483–1484) puts both field = sortField || field and
value = sortValue || value on the sorter, and getComparer (data/comparer.ts) always prefers
value when it is defined. So the effective precedence today is:

sortValue  >  value  >  sortField  >  field

This contradicts the documentation, which states (docs, Grid page, "Columns" section):

Use value for computed display values while keeping field for sorting

The docs example (field: "active" with a Yes/No expr) happens to produce the same order
either way, which likely hid the discrepancy. Additionally, the sortValue column property is
not documented at all.

Question for maintainers: what is the intended precedence? Intuitively sortField and
sortValue exist specifically to override the sorting behavior, which suggests something like
sortValue > sortField > value > field — but the current code and the current docs each imply
a different rule, and all three disagree. Whatever the intended rule is, it would be great to
have it implemented consistently and documented explicitly (including sortValue) on the Grid
docs page.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions