Data in rows and columns.
Mirrors Table and its parts (Table.Head, Table.Body, …) from
@digdir/designsystemet-react. Each part is a function with the same shape as
Html, so a table reads like the HTML it renders:
Table.view [ Table.zebra, Table.border ]
[ Html.caption [] [ Html.text "Byggesaker" ]
, Table.head []
[ Table.row []
[ Table.headerCell [] [ Html.text "Saksnummer" ]
, Table.headerCell [] [ Html.text "Adresse" ]
]
]
, Table.body []
(List.map
(\case_ ->
Table.row []
[ Table.cell [] [ Html.text case_.number ]
, Table.cell [] [ Html.text case_.address ]
]
)
cases
)
]<Table> → Table.view []zebra / stickyHeader / border / hover → Table.zebra / Table.stickyHeader / Table.border / Table.hover<Table.Head> / Body / Foot → Table.head [] / Table.body [] / Table.foot []<Table.Row> / Cell / HeaderCell → Table.row [] / Table.cell [] / Table.headerCell []<Table.HeaderCell sort="ascending" onClick> → Table.sortableHeaderCell { sort, column, onSort } []Stripe every other row.
Keep the header in view while the body scrolls. Give the table a
max-height for it to scroll within.
A rounded border around the table.
Highlight the body row under the pointer.
A <thead>.
A <tbody>.
A <tbody> with keyed rows, for rows that get sorted, filtered, added or
removed. Each row keeps its DOM node as it moves, so focus and input state stay
with the right row.
Table.keyedBody []
(List.map (\case_ -> ( case_.number, viewRow case_ )) (sortCases model.sort model.cases))A <tfoot>, for sums and totals.
A <tr>.
A <td>.
A <th>. In the body, add Attr.scope "row" to make it a row header.
For a header that sorts its column, use
sortableHeaderCell.
In React each header cell has its own sort prop, so two columns can both
claim to be sorted. Here the sort is one Sort value in your model,
shared by every sortable header. At most one column is sorted, and each
header's aria-sort and button follow from it.
type Column
= Number
| Address
type alias Model =
{ sort : Table.Sort Column, cases : List Case }
type Msg
= SortChanged (Table.Sort Column)
viewHeader : Table.Sort Column -> Html Msg
viewHeader sort =
Table.head []
[ Table.row []
[ Table.sortableHeaderCell { sort = sort, column = Number, onSort = SortChanged }
[]
[ Html.text "Saksnummer" ]
, Table.sortableHeaderCell { sort = sort, column = Address, onSort = SortChanged }
[]
[ Html.text "Adresse" ]
]
]Sorting the rows is up to you. The table only shows which column is sorted and asks for a new sort when a header is clicked.
How the table is sorted: not at all, or by one column in one direction.
column is your own type for the table's columns.
Which way a sorted column is ordered: aria-sort="ascending" or
"descending".
A header cell with a sort button in it: <th aria-sort="…"><button
type="button">…</button></th>.
sort is the table's current sort, the same value for every header.column is the column this header is for.onSort gets the sort from nextSort when the header is
clicked.aria-sort is "ascending" or "descending" on the sorted column and
"none" on the other sortable ones. The attributes go on the <th>.
The sort after a click on a column's header: a new column starts ascending, and clicking the sorted column again flips its direction.
nextSort Address Unsorted == SortedBy Address Ascending
nextSort Address (SortedBy Number Descending) == SortedBy Address Ascending
nextSort Address (SortedBy Address Ascending) == SortedBy Address Descending
nextSort Address (SortedBy Address Descending) == SortedBy Address AscendingTo have a third click go back to Unsorted, handle it in update. The
message carries the column either way.
A
<table class="ds-table">. Put anHtml.captionfirst to give it a visible name, styled as a heading.