Rows

A row is one entry of a row model: your original datum plus the id, depth, and grouping information the pipeline gave it. This page is the port of TanStack's Rows guide.

Row row is an opaque type. In TanStack a row is an object with properties and methods (row.id, row.original, row.getValue(...)); here you read it through functions that take the row as their last data argument. The type parameter is your own record type, so Row Person is a row over a Person.

Where to get rows from

Table.findRow

To get one row by its id, use Table.findRow. It is TanStack's table.getRow, and it returns a Maybe rather than throwing:

lookupRow : Table.State -> String -> Maybe (Table.Row Person)
lookupRow state wanted =
    Table.findRow (Table.rowsFromList config state people) wanted

findRow takes the row model, not the config, because ids only exist once rows have been built. It reads rowsById.

Row models

The pipeline builds the rows and hands them back in three shapes: model.rows (the tree), model.flatRows (everything flattened), and model.rowsById. The Row Models guide covers the stages; these are the two common ways to reach the rows.

Render rows

Render from Table.rowsInDisplayOrder rather than model.rows directly. It is the page in the order you draw it, with expanded descendants inserted when Config.paginateExpandedRows is off.

displayRows : Table.State -> List (Table.Row Person)
displayRows state =
    Table.rowsInDisplayOrder config state (Table.rowsFromList config state people)

Get selected rows

Table.selectedRowModel filters any stage's row model down to the selected rows. See Row Selection.

Row objects

Every reader is a function of the row. Nothing needs the table.

TanStack elm-table
row.id rowId
row.index rowIndex
row.depth rowDepth
row.original rowOriginal
row.subRows rowSubRows
row.parentId rowParentId
row.originalSubRows rowOriginalSubRows
row.groupingColumnId rowGroupingColumnId
row.groupingValue rowGroupingValue
row.leafRows rowLeafRows
row._valuesCache on a group row rowAggregatedValues
row.getIsGrouped() rowIsGrouped
row.getValue(columnId) getValue
row.getUniqueValues(columnId) getUniqueValues
row.getLeafRows() getLeafRows
row.getParentRow() getParentRow
row.getParentRows() getParentRows
row.getAllCells() getAllCells
row.getDisplayIndex() displayIndex
table.getRow(id) findRow
table.getMaxSubRowDepth() maxSubRowDepth
row.renderValue(columnId) none

The first group takes only the row. The second group needs more: getValue and getUniqueValues take the Config (they run your accessors); getParentRow, getParentRows, findRow, and maxSubRowDepth take a RowModel (they look other rows up); getAllCells takes both the Config and the State (it needs the column order); displayIndex takes the Config, the State, and a RowModel.

renderValue has no counterpart because there is no renderFallbackValue option: an absent value reads back as Value.Null and you decide what to draw. See Values.

Row ids

Every row has an id that is unique in its row model. By default a root row's id is its index ("0", "1") and a child's id is its parent's id plus its own index ("0.1"). Override that with Table.withGetRowId, which receives the datum, its index among its siblings, and its parent's row id:

config : Table.Config Person
config =
    Table.config
        [ Table.column "firstName" (.firstName >> Value.String)
            |> Table.withHeader "First name"
        , Table.column "department" (.department >> Value.String)
            |> Table.withHeader "Department"
        , Table.column "salary" (.salary >> Value.Number)
            |> Table.withHeader "Salary"
        ]
        |> Table.withGetRowId (\person _ _ -> person.id)

Grouping appends to the id: a group row's id is columnId:value, and nested groups chain with >. Value.Null keys land under "null".

Row numbers and display indexes

For a row-number column, use Table.displayIndex, not rowIndex. rowIndex is the row's position among its siblings at the time it was built, so it does not follow filtering, grouping, sorting, or expansion. displayIndex is the zero-based position in rowsInDisplayOrder, or -1 when the row is not on the current page.

rowNumber : Table.State -> Table.RowModel Person -> Table.Row Person -> String
rowNumber state model row =
    let
        position : Int
        position =
            Table.displayIndex config state model row
    in
    if position < 0 then
        ""

    else
        String.fromInt (position + 1)

There is no cache behind it, so it walks the display list once per call. If you are numbering every row, number the list you already mapped over instead of calling this per row.

Access row values

Table.getValue reads one cell of one row by column id. It returns a Value, not a String:

firstNameOf : Table.Row Person -> String
firstNameOf row =
    Value.toString (Table.getValue config row "firstName")

An unknown column id, or a column with no accessor, gives Value.Null rather than an error. On a group row, getValue reads the aggregated value when the column has one.

Table.getUniqueValues is the list form that faceting and grouping use. A column with withGetUniqueValues decides its own; otherwise the cell value is wrapped in a one-item list.

departmentValues : Table.Row Person -> List Value.Value
departmentValues row =
    Table.getUniqueValues config row "department"

Access original row data

Table.rowOriginal gives back the exact record you put in, untouched by any accessor. This is the way to reach fields you never defined a column for, and it is typed, so no casting:

originalOf : Table.Row Person -> String
originalOf row =
    (Table.rowOriginal row).firstName

On a group row, rowOriginal is the first leaf row's datum. Check rowIsGrouped before treating it as a real record.

Sub rows

Grouping and expanding give rows children. Tell the core row model how to reach the children of your own nested data with Table.withSubRows.

  • rowSubRows: the row's children, as rows.
  • rowOriginalSubRows: the raw children Config.getSubRows returned.
  • rowDepth: 0 for root rows, 1 for their children, and so on. Useful for indenting:
indent : Table.Row Person -> String
indent row =
    String.repeat (Table.rowDepth row) "    "
ancestorIds : Table.RowModel Person -> Table.Row Person -> List String
ancestorIds model row =
    List.map Table.rowId (Table.getParentRows model row)
  • getLeafRows: every descendant, depth first, not including the row itself.
  • maxSubRowDepth: the deepest depth in a row model. A flat model is 0.
deepestRow : Table.State -> Int
deepestRow state =
    Table.maxSubRowDepth (Table.coreRowModelFromList config state people)

Group rows

The grouped stage inserts rows that stand for a group rather than a datum. rowIsGrouped tells them apart, rowGroupingColumnId says which column the group is on, rowGroupingValue is the value it groups by, and rowLeafRows is what it was built from:

groupLabel : Table.Row Person -> String
groupLabel row =
    if Table.rowIsGrouped row then
        Value.toString (Table.rowGroupingValue row)
            ++ " ("
            ++ String.fromInt (List.length (Table.rowLeafRows row))
            ++ ")"

    else
        ""

rowAggregatedValues is the group row's aggregates keyed by column id. Every leaf column gets an entry, Value.Null included, so getValue on a group row never falls through to a leaf's accessor. See Grouping and Aggregation.

More row APIs

The per-feature row functions live with their features: getIsRowSelected and toggleRowSelected in Row Selection, getIsExpanded and toggleExpanded in Expanding, getIsRowPinned and pinRow in Row Pinning.

To get from a row to the cells you render, see Cells.