nunntom/elm-ui-select/2.0.0

NOTE: the latest version is 4.0.0

Select

A select widget for elm-ui.

Type

type alias Select a =
Model a

The main Select type

init : String -> Select a

Initialise the Select. You must provide a unique id. The id will be used for getting DOM elements etc.

Set

setItems : List a -> Select a -> Select a

Set the list of items

You can do this on init:

init : List String -> ( Model, Cmd Msg )
init things =
    ( { select =
            Select.init "thing-select"
                |> Select.setItems things
      }
    , Cmd.none
    )

Or you can do it in your view if you prefer to keep your items in your own model.

view : Model -> Element Msg
view model =
    Select.view []
        { onChange = SelectMsg
        , label = Input.labelAbove [] (text "Choose a thing")
        , placeholder = Just (Input.placeholder [] (text "Type to search"))
        , itemToString = identity
        }
        |> Select.toElement (Select.setItems model.things model.select)
setSelected : Maybe a -> Select a -> Select a

Set the selected item

setInputValue : String -> Select a -> Select a

Set the input value

closeMenu : Select a -> Select a

Close the menu

Get

toValue : Select a -> Maybe a

Get the selected item

toInputValue : Select a -> String

Get the value of the input

toInputElementId : Select a -> String

Get the id of the DOM input element. Useful in tests or to associate the provided label with the input

toMenuElementId : Select a -> String

Get the id of the DOM menu container. Useful for testing

Check

isMenuOpen : Select a -> Bool

Is the menu open?

isLoading : Select a -> Bool

Is there a request currently loading? Could be used to add loading styling.

isRequestFailed : Select a -> Bool

Did a request fail?

Update and Requests

type alias Msg a =
Msg a

The Msg type

update : (Msg a -> msg) -> Msg a -> Select a -> ( Select a, Cmd msg )

Update the Select

update : Msg -> Model -> ( Model, Cmd Msg )
    update msg model =
        case msg of
            SelectMsg subMsg ->
                Select.update SelectMsg subMsg model.select
                    |> Tuple.mapFirst (\select -> { model | select = select })
updateWithRequest : Request a -> (Msg a -> msg) -> Msg a -> Select a -> ( Select a, Cmd msg )

Update with an HTTP request to retrieve matching remote results. Note that in order to avoid an elm/http dependency in this package, you will need to provide the request Cmd yourself.

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Request SelectMsg subMsg ->
            Select.updateWithRequest (Select.request fetchThings) SelectMsg subMsg model.select
                |> Tuple.mapFirst (\select -> { model | select = select })


fetchThings : String -> Cmd (Select.Msg Thing)
fetchThings query =
    Http.get
        { url = "https://awesome-thing.api/things?search=" ++ query
        , expect = Http.expectJson (Select.gotRequestResponse query) (Decode.list thingDecoder)
        }
type alias Request a =
Request (Cmd (Msg a))

A Request. See Select.Request for configuration options.

request : (String -> Cmd (Msg a)) -> Request a

Create a request. Provide a function that takes the input value and returns a Cmd. Update will return this Cmd when the user types in the input subject to a debounce delay and minimum number of characters which can be configured in the Select.Request module.

gotRequestResponse : String -> Result err (List a) -> Msg a

Hook the request Cmd result back into update with this Msg. You need to pass in the string query (input value) that was used for the request. This is used to match up the correct response to most recent request in case of race conditions.

Configure View

type ViewConfig a msg

The View Configuration

view : Model -> Element Msg
view model =
    Select.view []
        { onChange = SelectMsg
        , label = Input.labelAbove [] (text "Choose a thing")
        , placeholder = Just (Input.placeholder [] (text "Type to search"))
        , itemToString = .name
        }
        |> Select.toElement model.thingsSelect
List (Attribute msg)
-> { onChange : Msg a -> msg
, itemToString : a -> String
, label : Label msg
, placeholder : Maybe (Placeholder msg)
}
-> ViewConfig a msg

Initialise the default ViewConfig

(MenuPlacement -> List (Attribute msg))
-> ViewConfig a msg
-> ViewConfig a msg

Set arbitrary attributes for the menu element. You can call this multiple times and it will accumulate attributes. You can define different attributes based on whether the menu appears above or below the input.

Select.view []
    { onChange = SelectMsg
    , label = Input.labelAbove [] (Element.text "Choose a thing")
    , placeholder = Just (Input.placeholder [] (Element.text "Type to search"))
    , itemToString = .name
    }
    |> Select.withMenuAttributes
        (\placement ->
            [ Element.Font.size 16
            , Element.Border.width 2
            ]
                ++ (case placement of
                        Select.MenuAbove ->
                            [ Element.moveUp 10 ]


                        Select.MenuBelow ->
                            [ Element.moveDown 10 ]
                   )
        )
    |> Select.toElement model.select
withMenuMaxHeight : Maybe Int -> ViewConfig a msg -> ViewConfig a msg

Set a maximum height for the menu

withMenuMaxWidth : Maybe Int -> ViewConfig a msg -> ViewConfig a msg

Set a maximum width for the menu

withNoMatchElement : Element msg -> ViewConfig a msg -> ViewConfig a msg

Provide your own element to show when there are no matches based on the filter and input value. This appears below the input.

(OptionState -> a -> Element msg)
-> ViewConfig a msg
-> ViewConfig a msg

Provide your own element for the options in the menu, based on the current state of the option.

Select.view []
    { onChange = SelectMsg
    , label = Input.labelAbove [] (Element.text "Choose a thing")
    , placeholder = Just (Input.placeholder [] (Element.text "Type to search"))
    , itemToString = .name
    }
    |> Select.withOptionElement
        (\state item ->
            Element.el
                [ Element.width Element.fill
                , Element.paddingXY 14 10
                , Background.color <|
                    case optionState of
                        Idle ->
                            Element.rgb 1 1 1


                        Highlighted ->
                            Element.rgb 0.95 0.95 0.95


                        Selected ->
                            Element.rgba 0.64 0.83 0.97 0.8


                        SelectedAndHighlighted ->
                            Element.rgba 0.64 0.83 0.97 1
                ]
                (Element.text item.name)
        )
    |> Select.toElement model.select

Option state for use with custom option element

withClearButton : Maybe (ClearButton msg) -> ViewConfig a msg -> ViewConfig a msg

Add a button to clear the input. This element is positioned as Element.inFront.

Select.view []
    { onChange = SelectMsg
    , label = Input.labelAbove [] (Element.text "Choose a thing")
    , placeholder = Just (Input.placeholder [] (Element.text "Type to search"))
    , itemToString = .name
    }
    |> Select.withClearButton
        (Just
            (Select.clearButton
                [ Element.alignRight
                , Element.centerY
                , Element.moveLeft 12
                ]
                (Element.el [ Element.Region.description "clear selection" ] (Element.text "❌"))
            )
        )
    |> Select.toElement model.select
type ClearButton msg

A button to clear the input

clearButton : List (Attribute msg) -> Element msg -> ClearButton msg

Create a clear button

withFilter : Maybe (Filter a) -> ViewConfig a msg -> ViewConfig a msg

Customise the filtering of the menu based on input value. See Select.Filter. Default is startsWithThenContains.

Force the menu to always appear above the input. You may use this for example if you have issues with an input inside a scrollable transformed container.

Force the menu to always appear below the input. You may use this for example if you have issues with an input inside a scrollable transformed container. By default the menu will try to detect whether there is more space above or below and appear there, preferring below.

Menu decides whether to appear above or below based on how much space is available. This is the default. You'd only use this function if you're passing around a config and need to reset this option.

Use style: position fixed for the menu. This can be used if the select is inside a scrollable container to allow the menu to overflow the parent. Note that if any transforms (e.g. Element.moveUp/Element.moveLeft) are applied to the parent, this no longer works and the menu will be clipped. This is due to a feature of the current CSS spec. Also if the container or window is scrolled or resized without the input losing focus, the menu will appear detached from the input! To overcome this you may want to listen to scroll and resize events on the parent and window and use closeMenu to hide the menu.

Should the input value be cleared when the input loses focus if nothing is selected?

If nothing is selected, but the input value matches exactly one of the options (case insensitive), should we select it automatically when the input loses focus?

Element

toElement : Select a -> ViewConfig a msg -> Element msg

Turn the ViewConfig into an Element.

Effect

type alias Effect effect msg =
Effect effect msg

For use with the Effect pattern and elm-program-test, see Select.Effect.