Cells
A cell is one row crossed with one leaf column. This page is the port of TanStack's Cells guide.
Unlike Row, Column, and Header, Cell is not opaque. It is a plain
record with four fields, built on demand:
type alias Cell =
{ id : String
, columnId : String
, rowId : String
, value : Value
}
There is no type parameter: a cell holds a resolved
Value, not your record, so Cell is the same type for
every table.
Where to get cells from
Cells come from rows. Nothing stores them, so each of these functions computes the cells it returns.
| TanStack | elm-table |
|---|---|
row.getAllCells() |
getAllCells |
row.getVisibleCells() |
visibleCells |
row.getVisibleCellsByColumnId() |
visibleCellsByColumnId |
row.getStartVisibleCells() |
leftVisibleCells |
row.getCenterVisibleCells() |
centerVisibleCells |
row.getEndVisibleCells() |
rightVisibleCells |
All of them take the Config, the State, and the row.
getAllCells gives one cell per leaf
column in column order, hidden columns included.
visibleCells drops the hidden ones
and puts the left-pinned cells first, then the unpinned ones, then the
right-pinned. That ordering is why visibleCells is what you render:
viewRow : Table.State -> Table.Row Person -> Html msg
viewRow state row =
tr [] (List.map viewCell (Table.visibleCells config state row))
The three pinned lists are for laying the regions out separately, for instance in three scroll containers:
viewPinnedRow : Table.State -> Table.Row Person -> Html msg
viewPinnedRow state row =
tr []
(List.map viewCell (Table.leftVisibleCells config state row)
++ List.map viewCell (Table.centerVisibleCells config state row)
++ List.map viewCell (Table.rightVisibleCells config state row)
)
visibleCellsByColumnId
returns the same cells as a Dict when you want one specific cell rather
than the row:
salaryCell : Table.State -> Table.Row Person -> Maybe Table.Cell
salaryCell state row =
Dict.get "salary" (Table.visibleCellsByColumnId config state row)
Cell objects
Cell ids
cell.id is the row id and the column id joined with an underscore, the same
construction TanStack uses:
{ id = rowId ++ "_" ++ columnId }
Grouping changes the row id, so group and aggregated cells get the longer id their row carries.
allCellIds : Table.State -> Table.Row Person -> List String
allCellIds state row =
List.map .id (Table.getAllCells config state row)
Cell parent objects
TanStack cells hold references to their parent row and column objects.
An Elm Cell holds ids instead: cell.rowId and cell.columnId. Get back
to the objects with Table.findRow and
Table.findColumn when you need them.
Usually you do not. You already have the row in scope when you map over its cells, so pass it down instead of looking it up.
Access cell values
cell.value is the value, already resolved. There is no cell.getValue()
call to make and no cache to warm, because the cell was built by reading the
accessor once.
cell.value is a Value.
Value.toString applies TanStack's
String(value) coercion; pattern matching gives you your own formatting:
viewTypedCell : Table.Cell -> Html msg
viewTypedCell cell =
case cell.value of
Value.Number n ->
td [ class "numeric" ] [ text (String.fromFloat n) ]
Value.Bool True ->
td [] [ text "yes" ]
Value.Bool False ->
td [] [ text "no" ]
other ->
td [] [ text (Value.toString other) ]
renderValue has no counterpart. There is no renderFallbackValue option:
a missing value is Value.Null, and the case above decides what it looks
like.
To read a different column's value while rendering a cell, go through the
row: Table.getValue config row otherColumnId. See Rows.
Access other row data from any cell
TanStack reaches the original datum with cell.row.original. A Cell has no
row reference, so keep the row in scope while you render:
viewRow : Table.State -> Table.Row Person -> Html msg
viewRow state row =
tr [] (List.map viewCell (Table.visibleCells config state row))
viewRow already has the row; if a cell renderer needs the whole record,
give it Table.rowOriginal row as an argument rather than looking the row up
by cell.rowId.
Cell rendering
TanStack renders cells with flexRender(cell.column.columnDef.cell, cell.getContext()), because a column def can carry a cell renderer that is
a string, JSX, or a function. There is no cell option on an Elm column and
no flexRender: you write the Html yourself.
viewCell : Table.Cell -> Html msg
viewCell cell =
td [ class ("cell-" ++ cell.columnId) ] [ text (Value.toString cell.value) ]
That is the whole rendering story. Branch on cell.columnId for per-column
markup, or on cell.value for per-type markup, in ordinary Elm.
cell.getContext() has no counterpart because the arguments it packages
(the table, the row, the column, the cell) are the arguments you already have
or can pass.
More cell APIs
The state queries about a cell live with their features. Each takes what it needs rather than hanging off the cell:
cellIsGrouped,cellIsPlaceholderin Grouping. Both take theState, the row, and a column id.cellIsAggregatedin Aggregation.cellIsSelected,cellIsFocused,cellCanSelect,cellTabIndexin Cell Selection. These take aCell.cellRowSpan,cellColSpan,cellIsCoveredin Cell Spanning. These take aCellSpanIndexbuilt once withcellSpanIndex.
Cell spanning
Adjacent cells can merge into one rendered cell. A cell with a span of 0 is
covered by another cell's span and must be skipped rather than rendered. The
index is an explicit value here rather than a memoized table method, so build
it once for the rows you are about to draw and pass it down. See
Cell Spanning.