kirchner / elm-selectize / Selectize

This is a dropdown menu whose entries can be filtered. You can select entries using the mouse or with the keyboard (arrow up/down and enter).

The dropdown menu manages the keyboard and mouse focus, as well as the open/closed state itself. The (unfiltered) list of possible entries and the eventually selected entry have to live in the model of the actual application.

If you want to use it, your model should look something like this

type alias Model =
    { selection : Maybe Tree
    , menu : Selectize.State Tree
    }

type alias Tree =
    { name : String
    , latinName : String
    }

The state of the dropdown menu is instanciated via

menu =
    Selectize.closed "unique-menu-id"
        (\tree -> tree.name ++ " - " ++ tree.latinName)
        (trees |> List.map Selectize.entry)

with

trees : List Tree

And you have to hook it up in your update function like so

type Msg
    = MenuMsg (Selectize.Msg Tree)
    | SelectTree (Maybe Tree)

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        MenuMsg selectizeMsg ->
            let
                ( newMenu, menuCmd, maybeMsg ) =
                    Selectize.update SelectTree
                        model.selection
                        model.menu
                        selectizeMsg

                newModel =
                    { model | menu = newMenu }

                cmd =
                    menuCmd |> Cmd.map MenuMsg
            in
            case maybeMsg of
                Just nextMsg ->
                    update nextMsg newModel
                        |> andDo cmd

                Nothing ->
                    ( newModel, cmd )

        SelectTree newSelection ->
            ( { model | selection = newSelection }, Cmd.none )

andDo : Cmd msg -> ( model, Cmd msg ) -> ( model, Cmd msg )
andDo cmd ( model, cmds ) =
    ( model
    , Cmd.batch [ cmd, cmds ]
    )

Finally, the menu can be rendered like this

view : Model -> Html Msg
view model =
    Html.div []
        [ Selectize.view viewConfig
            model.selection
            model.menu
            |> Html.map MenuMsg
        ]

with the view configuration given by

viewConfig : Selectize.ViewConfig Tree
viewConfig =
    Selectize.viewConfig
        { container =
            [ Attributes.class "selectize__container" ]
        , menu =
            [ Attributes.class "selectize__menu" ]
        , ul =
            [ Attributes.class "selectize__list" ]
        , entry =
            \tree mouseFocused keyboardFocused ->
                { attributes =
                    [ Attributes.class "selectize__item"
                    , Attributes.classList
                        [ ( "selectize__item--mouse-selected"
                          , mouseFocused
                          )
                        , ( "selectize__item--key-selected"
                          , keyboardFocused
                          )
                        ]
                    ]
                , children =
                    [ Html.text
                        (tree.name ++ " - " ++ tree.latinName)
                    ]
                }
        , divider =
            \title ->
                { attributes =
                    [ Attributes.class "selectize__divider" ]
                , children =
                    [ Html.text title ]
                }
        , input = styledInput
        }

and an input given by, for example,

styledInput : Selectize.Input Tree
styledInput =
    Selectize.autocomplete <|
        { attrs =
            \sthSelected open ->
                [ Attributes.class "selectize__textfield"
                , Attributes.classList
                    [ ( "selectize__textfield--selection", sthSelected )
                    , ( "selectize__textfield--no-selection", not sthSelected )
                    , ( "selectize__textfield--menu-open", open )
                    ]
                ]
        , toggleButton =
            Just <|
                \open ->
                    Html.i
                        [ Attributes.class "material-icons"
                        , Attributes.class "selectize__icon"
                        ]
                        [ if open then
                            Html.text "arrow_drop_up"
                          else
                            Html.text "arrow_drop_down"
                        ]
        , clearButton = Nothing
        , placeholder = "Select a Tree"
        }

Types


type alias State a =
State a

The internal state of the dropdown menu. This lives in your model.

closed : String -> (a -> String) -> List (Entry a) -> State a

Use this function to initialize your dropdown menu, for example by

menu =
    Selectize.closed "unique-menu-id"
        entryToLabel
        entries

It will have the provided entries and be closed. The provided id should be unique. If for some reason the entries change, just reinstantiate your dropdown state with this function.


type alias Entry a =
Entry a

Each entry of the menu has to be wrapped in this type. We need this, as an entry can either be selectable (and therefore also focusable) or not. You can construct these using entry and divider.

entry : a -> Entry a

Create a selectable Entry a.

divider : String -> Entry a

Create a divider, which can neither be selected nor focused. It is therefore skipped while traversing the list via up/down keys.

Update


type alias Msg a =
Msg a

The dropdown menu produces these messages.

update : (Maybe a -> msg) -> Maybe a -> State a -> Msg a -> ( State a, Platform.Cmd.Cmd (Msg a), Maybe msg )

The dropdown's update function. Take a look at the beginning of this module documentation to see what boilerplate is needed in your main update.

View

view : ViewConfig a -> Maybe a -> State a -> Html (Msg a)

The dropdown's view function. You have to provide the current selection (along with the configuration and the its actual state).


type alias ViewConfig a =
ViewConfig a

The configuration for Selectize.view.

viewConfig : { container : List (Html.Attribute Basics.Never), menu : List (Html.Attribute Basics.Never), ul : List (Html.Attribute Basics.Never), entry : a -> Basics.Bool -> Basics.Bool -> HtmlDetails Basics.Never, divider : String -> HtmlDetails Basics.Never, input : Input a } -> ViewConfig a

Create the view configuration, for example

viewConfig : Selectize.ViewConfig String
viewConfig =
    Selectize.viewConfig
        { container = [ ... ]
        , menu = [ ... ]
        , ul = [ ... ]
        , entry =
            \entry mouseFocused keyboardFocused ->
                { attributes = ...
                , children = ...
                }
        , divider =
            \title ->
                { attributes = ...
                , children = ...
                }
        , input = someInput
        }


type alias HtmlDetails msg =
{ attributes : List (Html.Attribute msg)
, children : List (Html msg) 
}

entry and divider should return this.


type alias Input a =
Input a

You have to choose an Input in your view configuration. This decides if you have a simple dropdown or an autocompletion version.

simple : { attrs : Basics.Bool -> Basics.Bool -> List (Html.Attribute Basics.Never), toggleButton : Maybe (Basics.Bool -> Html Basics.Never), clearButton : Maybe (Html Basics.Never), placeholder : String } -> Input a

An input for displaying a simple dropdown.

autocomplete : { attrs : Basics.Bool -> Basics.Bool -> List (Html.Attribute Basics.Never), toggleButton : Maybe (Basics.Bool -> Html Basics.Never), clearButton : Maybe (Html Basics.Never), placeholder : String } -> Input a

An input for an autocompletion dropdown.