Grassdog
Grassdog

Reputation: 73

How to get the selected options of a multiselect in Elm?

I've seen what is required for a getting the selected index of a single select but I'm interested in getting all of the selected options from a multi select. I haven't been able to work out how to do this.

I've attempted the following but I suspect the Json decoder is failing. I'm not 100% sure of that though, because the decoding happens in the virtual dom code and any errors there are thrown away.

type Msg
= SetMultipleInts (List Int)

-- I'm not seeing the SetMultipleInts message when I click on the multiselect
view model =
    div []
        [ select (onSelect SetMultipleInts) (List.map myOption [1..4]) ]

myOption : Int -> Html Msg
myOption id =
    option [ value (toString id) ] [ text <| "Option " ++ (toString id) ]

-- I'm not seeing anything happen in the onchange
onMultiSelect : (List Int -> msg) -> List (Html.Attribute msg)
onMultiSelect msg =
    [ on "change" (Json.map msg targetSelectedOptions), multiple True ]

targetSelectedOptions : Json.Decoder (List Int)
targetSelectedOptions =
    Json.at [ "target", "selectedOptions" ] (Json.list (Json.at [ "value" ] Json.int))

Can I do this without having to use ports?

Upvotes: 5

Views: 856

Answers (2)

Lucamug
Lucamug

Reputation: 802

In case someone need a multiselect in Elm, I rewrote a fully working example in Elm 0.19:

https://ellie-app.com/g7WrS9cV4zVa1

module Main exposing (main)

import Browser
import Html exposing (..)
import Html.Attributes
import Html.Events
import Json.Decode


type alias Model =
    { value : List ( String, Maybe String ) }


init : Model
init =
    { value = [] }


type Msg
    = SetMultipleInts (List ( String, Maybe String ))


update : Msg -> Model -> Model
update msg model =
    case msg of
        SetMultipleInts value ->
            { model | value = value }


view : Model -> Html Msg
view model =
    div []
        [ select
            [ Html.Events.on "change"
                (Json.Decode.map SetMultipleInts targetSelectedOptions)
            , Html.Attributes.multiple True
            ]
            (List.map myOption (List.range 1 4))
        , div []
            [ text <|
                Debug.toString
                    (model
                        |> .value
                        |> List.map Tuple.second
                        |> List.filterMap identity
                    )
            ]
        ]


targetSelectedOptions : Json.Decode.Decoder (List ( String, Maybe String ))
targetSelectedOptions =
    Json.Decode.at [ "target", "selectedOptions" ] <|
        Json.Decode.keyValuePairs <|
            Json.Decode.maybe (Json.Decode.at [ "value" ] Json.Decode.string)


myOption : Int -> Html Msg
myOption id =
    option [ Html.Attributes.value (String.fromInt id) ]
        [ text <| "Option " ++ String.fromInt id ]


main : Program () Model Msg
main =
    Browser.sandbox
        { init = init
        , view = view
        , update = update
        }

Upvotes: 0

Tosh
Tosh

Reputation: 36030

The decoder fails because event.target.selectedOptions is not a javascript array. When you cannot use Json.Decode.list, you can use Json.Decode.keyValuePairs.

Here is the example how you can use it. You may want to change extractValues below depending on how you want to react to empty selection and such.

targetSelectedOptions : Json.Decoder (List String)
targetSelectedOptions =
  let
    maybeValues =
      Json.at [ "target", "selectedOptions" ]
        <| Json.keyValuePairs
        <| Json.maybe ("value" := Json.string)
    extractValues mv =
      Ok (List.filterMap snd mv)
  in Json.customDecoder maybeValues extractValues

Upvotes: 4

Related Questions