It would be great if I could figure out a way to not pass so many strings around. Is there another way to get the user input from the element in a safer way. I had to have a lot of fromString toString methods.
To avoid the String -> a conversion functions with an Html.select, you could think that onClick would work on individual options, and actually it does, but unfortunately only with Firefox (https://ellie-app.com/7WqmTQtwKvRa1).
But there is another trick that will work everywhere. You can use the option index in the options list as the value and use it to return the selected option:
type Msg
= ChangeRoot Root
| ChangeQuality Quality
view : Model -> Html Msg
view model =
div
[ SSA.class "uke-chord-container" ]
[ ukeRoot model.root model.quality
, div
[ SSA.class "uke-chord-inputs" ]
[ select ChangeRoot rootToString C rootList
, select ChangeQuality qualityToString Major qualityList
]
]
select : (a -> msg) -> (a -> String) -> a -> List a -> Html msg
select toMsg toString default options =
let
toOption idx a =
Html.option [ value (String.fromInt idx) ] [ text (toString a) ]
in
Html.select
[ onInput (selectOption toMsg default options) ]
(List.indexedMap toOption options)
selectOption : (a -> msg) -> a -> List a -> String -> msg
selectOption toMsg default options input =
case String.toInt input |> Maybe.andThen (\idx -> List.Extra.getAt idx options) of
Just option ->
toMsg option
Nothing ->
toMsg default
Note that you have to provide a default value in case the index is not found in the list, but you could use a custom decoder that would fail instead if you prefer to avoid a default value:
select : (a -> msg) -> (a -> String) -> List a -> Html msg
select toMsg toString options =
let
toOption idx a =
Html.option [ value (String.fromInt idx) ] [ text (toString a) ]
in
Html.select
[ stopPropagationOn "input" (selectOptionDecoder toMsg options) ]
(List.indexedMap toOption options)
selectOptionDecoder : (a -> msg) -> List a -> Decoder ( msg, Bool )
selectOptionDecoder toMsg options =
Decode.at [ "target", "value" ] Decode.string
|> Decode.andThen
(\input ->
case String.toInt input |> Maybe.andThen (\idx -> List.Extra.getAt idx options) of
Just option ->
Decode.succeed ( toMsg option, True )
Nothing ->
Decode.fail "Unexpected value"
)
Lastly I used List.Extra.getAt to get the element in the list, but you could convert your lists to Array instead if you prefer to avoid an additional dependency.