Expanding Sub Components
Expand a row to reveal arbitrary content instead of sub-rows. Ports examples/react/sub-components from TanStack Table.
SubComponents.elm
module SubComponents exposing (main)
{-| Ports `examples/react/sub-components/src/main.tsx`.
Expanding without sub-rows: `Table.withRowCanExpand (always True)` makes
every row expandable, and an expanded row gets a second `<tr>` under it whose
single cell spans the table and shows the original datum.
-}
import Browser
import Html exposing (Html, button, code, div, pre, table, tbody, td, text, th, thead, tr)
import Html.Attributes exposing (class, colspan, style)
import Html.Events exposing (onClick)
import Json.Encode as Encode
import Shared.Controls as Controls
import Shared.People as People exposing (Person)
import Table
import Table.Value as Value exposing (Value)
-- CONFIG
config : Table.Config Person
config =
Table.config
[ Table.display "expander"
, Table.column "firstName" (.firstName >> Value.String)
|> Table.withHeader "First Name"
|> Table.withFooter "firstName"
, Table.column "lastName" (.lastName >> maybeString)
|> Table.withHeader "Last Name"
|> Table.withFooter "lastName"
, Table.column "age" (.age >> toFloat >> Value.Number)
|> Table.withHeader "Age"
|> Table.withFooter "age"
, Table.column "visits" (.visits >> maybeNumber)
|> Table.withHeader "Visits"
|> Table.withFooter "visits"
, Table.column "status" (.status >> People.statusToString >> Value.String)
|> Table.withHeader "Status"
|> Table.withFooter "status"
, Table.column "progress" (.progress >> toFloat >> Value.Number)
|> Table.withHeader "Profile Progress"
|> Table.withFooter "progress"
]
-- `getRowCanExpand: () => true`
|> Table.withRowCanExpand (always True)
|> Table.withGetRowId (\person _ _ -> person.id)
maybeString : Maybe String -> Value
maybeString =
Maybe.map Value.String >> Maybe.withDefault Value.Null
maybeNumber : Maybe Int -> Value
maybeNumber =
Maybe.map (toFloat >> Value.Number) >> Maybe.withDefault Value.Null
-- MODEL
rowCount : Int
rowCount =
10
type alias Model =
{ state : Table.State
, seed : Int
, data : List Person
}
init : Model
init =
{ state = Table.initialState
, seed = 42
, data = People.makeData 42 [ rowCount ]
}
rowModel : Model -> Table.RowModel Person
rowModel model =
Table.rowsFromList config model.state model.data
-- UPDATE
type Msg
= RegenerateData
| ExpandToggled (Table.Row Person)
update : Msg -> Model -> Model
update msg model =
case msg of
RegenerateData ->
let
seed : Int
seed =
model.seed + 1
in
{ model | seed = seed, data = People.makeData seed [ rowCount ] }
ExpandToggled row ->
{ model
| state = Table.toggleExpanded config (rowModel model) row Nothing model.state
}
-- VIEW
view : Model -> Html Msg
view model =
let
current : Table.RowModel Person
current =
rowModel model
in
div [ class "demo-root" ]
[ div [ class "button-row" ]
[ button [ onClick RegenerateData ] [ text "Regenerate Data" ] ]
, div [ class "demo-root" ]
[ div [ class "spacer-sm" ] []
, table []
[ thead []
[ tr [] (List.map viewHeaderCell (Table.flatHeaders config model.state)) ]
, tbody [] (List.concatMap (viewRow model) current.rows)
]
, div [ class "spacer-sm" ] []
, div [] [ text (Controls.formatInt (List.length current.rows) ++ " Rows") ]
]
]
viewHeaderCell : Table.Header Person -> Html Msg
viewHeaderCell header =
let
columnId : String
columnId =
Table.headerColumnId header
in
th [ colspan (Table.headerColSpan header) ]
[ div []
[ text
(Table.findColumn config columnId
|> Maybe.andThen Table.columnHeader
|> Maybe.withDefault ""
)
]
]
viewRow : Model -> Table.Row Person -> List (Html Msg)
viewRow model row =
let
cells : List Table.Cell
cells =
Table.getAllCells config model.state row
in
tr [] (List.map (viewCell model row) cells)
:: (if Table.getIsExpanded config model.state row then
-- 2nd row is a custom 1 cell row
[ tr []
[ td [ colspan (List.length cells) ]
[ viewSubComponent row ]
]
]
else
[]
)
viewCell : Model -> Table.Row Person -> Table.Cell -> Html Msg
viewCell model row cell =
if cell.columnId == "expander" then
td []
[ if Table.getCanExpand config row then
button [ class "sortable", onClick (ExpandToggled row) ]
[ text
(if Table.getIsExpanded config model.state row then
"👇"
else
"👉"
)
]
else
text "🔵"
]
else if cell.columnId == "firstName" then
td []
[ div
[ class "indent"
, style "padding-left" (String.fromInt (2 * Table.rowDepth row) ++ "rem")
]
[ text (Value.toString cell.value) ]
]
else
td [] [ text (Controls.valueToString cell.value) ]
{-| `renderSubComponent`: the row's own datum as JSON.
-}
viewSubComponent : Table.Row Person -> Html Msg
viewSubComponent row =
let
person : Person
person =
Table.rowOriginal row
in
pre [ style "font-size" "10px" ]
[ code []
[ text
(Encode.encode 2
(Encode.object
[ ( "id", Encode.string person.id )
, ( "firstName", Encode.string person.firstName )
, ( "lastName"
, case person.lastName of
Just name ->
Encode.string name
Nothing ->
Encode.null
)
, ( "email", Encode.string person.email )
, ( "age", Encode.int person.age )
, ( "visits"
, case person.visits of
Just visits ->
Encode.int visits
Nothing ->
Encode.null
)
, ( "progress", Encode.int person.progress )
, ( "status", Encode.string (People.statusToString person.status) )
, ( "rank", Encode.int person.rank )
, ( "createdAt", Encode.string (Controls.isoDate person.createdAt) )
]
)
)
]
]
main : Program () Model Msg
main =
Browser.sandbox { init = init, update = update, view = view }