Search code examples
elm

How to update specific element in a list in Elm


There are couple apples ( in type of List ) which will expose themselvies in the web view. User can update any size attribute of an Apple. I have a msg type UpdateSize which will be triggered via onInput.

Editing any of the apples will only just trigger the message without knowing which apple to be updated.

Is that possible to pass an id attribute to UpdateSize message?

Thank you for reading this, Elm is great !

module Main exposing (main)

import Browser
import Html exposing (Html, button, div, text, input)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick,onInput)
import String

type alias Apple = {
   size: Int}

type alias Model = {
    apples: List(Apple)}


initialModel : Model
initialModel =
    { apples = [ Apple 10, Apple 11, Apple 12] }


type Msg
    = UpdateSize String


update : Msg -> Model -> Model
update msg model =
    case msg of
        UpdateSize s -> {model | apples = ??? } -- how to update a single Apple with new size
        _ -> model

viewApple : Apple -> Html Msg
viewApple a =
    input [ type_ "text" ,placeholder ""
          , value (String.fromInt a.size)
          , onInput UpdateSize]
          []



view : Model -> Html Msg
view model =
    div []
        (List.map viewApple model.apples)


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

Code link: https://ellie-app.com/ghd9jrcjKQQa1


Solution

  • With your current implementation it's not possible to know which apple to update since there's no unique attribute about the apples. What if two apples have the same size? If would be better if apples had IDs, or you used a dictionary type to keep track of the apples.

    However, for the sake of demonstration, you could say that the list indeces of the apples are unique and you find them accordingly. In real life this will be a fragile solution.

    Here's a naive approach using some helper functions from List.Extra.

    -- ...
    
    type alias Size =
        Int
    
    
    type Msg
        = UpdateSize Int String
    
    
    update : Msg -> Model -> Model
    update msg model =
        case msg of
            UpdateSize index sizeStr ->
                let
                    maybeSize =
                        String.toInt sizeStr
                in
                maybeSize
                    |> Maybe.withDefault (\size -> { model | apples = updateApple index size model.apples })
                    |> model
    
            _ ->
                model
    
    
    updateApple : Int -> Size -> List Apple -> List Apple
    updateApple index size apples =
        let
            maybeApple =
                List.Extra.getAt index apples
        in
        maybeApple
            |> Maybe.map (\apple -> List.Extra.setAt index { apple | size = size } apples)
            |> Maybe.withDefault apples
    
    -- ...
    
    viewApple : Int -> Apple -> Html Msg
    viewApple index a =
        input
            [ type_ "text"
            , placeholder ""
            , value (String.fromInt a.size)
            , onInput (UpdateSize index)
            ]
            []
    
    
    view : Model -> Html Msg
    view model =
        div []
            (List.indexedMap viewApple model.apples)