Restricted text input in Elm

· Allanderek's blog

#Elm #programming

A little tip for restricted text input in Elm. I've coded up the general idea in an ellie. This is really a general HTML tip, that I didn't know existed, but for some reason I feel it works pretty well in Elm.

The sitation is that you wish to restrict a text input to one of several possibilities, but there are many possibilities, so a select element is probably not correct. You may or may not wish to prevent any output not in the list of suggestions. What I didn't know is that you can define a datalist element that has all the suggestions and the browser will interpret that appropriately provided you set the list attribute on the input element. As for invalidating it, you could use a pattern attribute, but I think in Elm you're probably doing your own invalidation anyway.

So, supposing you have an input, and some list of appropriate inputs - maybe they are dynamic, or a list of countries or something - the key point is to define the datalist element:

 1import Html exposing (Html)
 2import Html.Attributes as Attributes
 3
 4allowableDataList : String -> List String -> Html msg
 5allowableDataList id allowableInputs =
 6    let
 7        makeOption input =
 8            Html.option
 9                [ Attributes.value input
10                , Attributes.id id 
11                ]
12                []
13    in
14    List.map makeOption allowableInputs
15        |> Html.datalist []

Now, you might wish to define this in your main view function if, for example, it's not often changing and used frequently. But you can always define it when you need it. Of course you have to be careful with the value of the id. To use this, you only have to set the list attribute on an input:

 1import Html exposing (Html)
 2import Html.Attributes as Attributes
 3import Html.Events as Events
 4
 5restrictedInput : String -> (String -> Msg) -> String -> Html Msg
 6restrictedInput currentInput onInput allowInputsId =
 7    Html.input
 8        [ Attributes.value currentInput
 9        , Attributes.list allowInputsId
10        , Events.onInput onInput
11        ]
12        [] 

The key point here is Attributes.list allowInputsId. As I said above you could define the datalist in your main function, or you could also define it here very locally, even within the input works:

 1import Html exposing (Html)
 2import Html.Attributes as Attributes
 3import Html.Events as Events
 4
 5type alias RestrictedInput =
 6    { onInput : String -> Msg
 7    , id : String
 8    , allowableInputs : List String
 9    , currentValue : String
10    }
11
12restrictedInput : RestrictedInput -> Html Msg
13restrictedInput config =
14    let
15        dataListId =
16            String.append config.id "-data-list"
17    in
18    Html.input
19        [ Attributes.value config.currentInput
20        , Attributes.id config.id
21        , Attributes.list dataListId
22        , Events.onInput onInput
23        ]
24        [ allowableDataList dataListId config.allowableInputs ]

Then if you also want to display an error message in the case that the current input is not one of the allowable inputs then you can simply check List.member config.currentInput config.allowableInputs. Obviously you might wish to augment that with whether the input has been blurred. Anyway I thought this was a nice little tip worth sharing, one of those little not-well-known corners of HTML. As I said before there is an ellie-app for this.