Column Ordering
Column Ordering
Columns render in the order you defined them in Table.config.
State.columnOrder overrides that with an explicit list of column ids, which
is what a "move this column left" button or a drag-and-drop header row writes.
This page ports TanStack Table's Column Ordering (React) Guide.
Column order is not only yours to set. Grouping can move a grouped column to the front, Column Visibility drops hidden columns, and Column Pinning splits the result into three regions. All of that happens in a fixed sequence, described next.
What affects column order
One function, orderColumns, applies
the ordering rules, and every leaf column list, header list, and cell list in
the package goes through it. The sequence is:
allColumns -> State.columnOrder -> Config.groupedColumnMode -> visibility -> pin split
Reading that left to right:
allColumnsis the columns as defined, each group column before its children.State.columnOrderreorders them. Ids you list come first, in your order; columns you leave out keep their definition order behind the listed ones.Config.groupedColumnModemoves grouped columns to the front, removes them, or leaves them alone. This is the only place that rule lives, inorderGroupedColumns, so a grouped column is never moved twice.- Visibility drops the hidden columns, giving
visibleLeafColumns. - The pin split partitions that list into left, center, and right. It
happens in the header builder and in
visibleCells, not insideorderColumns.
TanStack's page lists pinning first. The rendered result is the same: because
left and right columns are ordered by State.columnPinning itself, your
columnOrder only decides the order of the unpinned ("center") columns.
Ordering composing in one place is the reason headers, columns, and cells
never disagree. If you write your own column list, run it through
orderColumns and you get the same order the package uses everywhere else.
State
Column ordering owns one state slice: the column ids you want, in the order you want them.
-- in Table.State
columnOrder : List String
-- in Table.initialState
columnOrder = []
An empty list means definition order. The list is a preference, not a
constraint: ids that match no column are ignored, duplicates are dropped, and
columns you leave out are appended behind the ones you listed. So
[ "age" ] on a three-column table means "age first, then the rest as
defined".
Config options
Column ordering adds no Config field of its own. One field of
Grouping is applied by orderColumns, so it belongs here
too:
| Option | Type | Default | Description |
|---|---|---|---|
groupedColumnMode |
GroupedColumnMode |
groupedColumnsReorder |
What the leaf column list does with a grouped column: move it to the front, drop it, or leave it. |
Config is a plain record, so you set it with a record update:
{ config | groupedColumnMode = Table.groupedColumnsIgnore }.
Column options
None. There is no per-column builder for ordering; a column's position comes
entirely from State.columnOrder and the rules above.
Transitions
| Function | Signature | Description |
|---|---|---|
setColumnOrder |
List String -> State -> State |
Replace State.columnOrder. |
resetColumnOrder |
State -> State |
Empty State.columnOrder, restoring definition order. |
Both are the whole API for changing the order. A "move one column earlier" button reads the current rendered order, swaps two ids, and writes the result back:
update : Msg -> Model -> Model
update msg model =
case msg of
SetOrder order ->
{ model | state = Table.setColumnOrder order model.state }
ResetOrder ->
{ model | state = Table.resetColumnOrder model.state }
MoveEarlier columnId ->
{ model
| state =
Table.setColumnOrder
(swapEarlier columnId (renderedOrder model.state))
model.state
}
The current order is whatever orderColumns produces, which is the right
starting point even when State.columnOrder is still empty:
renderedOrder : Table.State -> List String
renderedOrder state =
Table.orderColumns config state (Table.allColumns config)
|> List.map Table.columnId
swapEarlier is ordinary list code with nothing table-specific in it:
swapEarlier : String -> List String -> List String
swapEarlier columnId order =
case order of
first :: second :: rest ->
if second == columnId then
second :: first :: rest
else
first :: swapEarlier columnId (second :: rest)
_ ->
order
Queries
| Function | Signature | Description |
|---|---|---|
orderColumns |
Config row -> State -> List (Column row) -> List (Column row) |
Put a column list in table order: State.columnOrder, then the grouped-column rules. TanStack's getOrderColumnsFn. |
orderGroupedColumns |
Config row -> State -> List (Column row) -> List (Column row) |
Apply Config.groupedColumnMode alone. TanStack's orderColumns. |
columnIndex |
Config row -> State -> ColumnRegion -> Column row -> Int |
The column's position in one region of the visible leaf columns, or -1. |
columnIsFirst |
Config row -> State -> ColumnRegion -> Column row -> Bool |
Is it the first visible column of that region? |
columnIsLast |
Config row -> State -> ColumnRegion -> Column row -> Bool |
Is it the last visible column of that region? |
The last three take a ColumnRegion, which replaces TanStack's optional
position argument. Pass
allColumnsRegion where TanStack
would pass nothing, or
leftColumnsRegion,
centerColumnsRegion, or
rightColumnsRegion to ask
about one pinned region. See Column Pinning.
columnIsFirst and columnIsLast are what you want for the border or shadow
on the edge of a region:
viewHeaderCell : Table.State -> Table.Column Person -> Html Msg
viewHeaderCell state column =
let
id : String
id =
Table.columnId column
in
th
[ onClick (MoveEarlier id)
, style "border-left"
(if Table.columnIsFirst config state Table.allColumnsRegion column then
"none"
else
"1px solid #ddd"
)
]
[ text (Maybe.withDefault id (Table.columnHeader column)) ]
columnIndex answers the same question as a number, which is what a drop
target needs:
positionLabel : Table.State -> Table.Column Person -> String
positionLabel state column =
String.fromInt (Table.columnIndex config state Table.centerColumnsRegion column)
++ (if Table.columnIsLast config state Table.centerColumnsRegion column then
" (last)"
else
""
)
Drag and drop reordering
The package has no drag-and-drop code, and no opinion about which approach you
use: a drag ends in one setColumnOrder call, and everything before that is
your own Html.Events wiring or a port to a JavaScript drag library. The
Column DnD example does it
with native HTML5 drag events and no dependencies.
Not covered
TanStack's page also describes owning the columnOrder state through the
atoms option or state.columnOrder plus onColumnOrderChange. There is
nothing to port: State.columnOrder is always yours, held in your own model.
resetColumnOrder here is TanStack's resetColumnOrder(true): it clears the
slice. There is no table.initialState to restore, so to go back to an order
you started with, call setColumnOrder with the list you remembered.
Example
Column Ordering, ported from TanStack's Column Ordering example.