How to structure message and update functions with a variable number of UI elements in Elm?

308 Views Asked by At

Greetings StackOverflow!

Suppose I have an Elm app with a variable number of text input fields. I'd like to reflect the state of these input fields in the model.

Model and view are easy enough: View just has a Array String field in it somewhere.

The view is then computed simply by calling List.map (HTML input ...) on that list of strings.

However, I'm a bit lost how to do the update function and message type.

The message could be somethign like this:

type Msg = InputFieldUpdated Int String

Here, the Int refers to the position in the Array that the string to be updated has. However, if I do it this way, I can create messages that refer to non-existant array positions simply by setting the Int to something that is out of range.

For a fixed number of input elements, one can solve this problem very elegantly by simply using a union type with a different value for each input, but what about my situation? In the domain of "making impossible states impossible", is there some trick for that that I'm missing?

1

There are 1 best solutions below

0
On

However, if I do it this way, I can create messages that refer to non-existant array positions simply by setting the Int to something that is out of range.

According to the documentation of Array.set, the array stays untouched if the index is out of range.

Please, have a look at this ellie-app example. It mostly models the problem, that you've described. Array of elements is rendered and the elements can be added dynamically:

view : Model -> Html Msg
view model =
    div []
        [ div [] (toList (Array.indexedMap viewElement model.elements))
        , button [ onClick Add ] [ text "Add" ]
        ]

In order to update a particular element, you need to get it by index. Since the type of the result from Array.get is Maybe a, type system will force you to process all the cases (when the element exists and doesn't):

Increment index ->
    case get index model.elements of
        Just element ->
            { model | elements = set index (element + 1) model.elements }

        Nothing ->
            model

The example is just for demonstration purposes, if you don't need the current element, then Array.set function can be safely used.