gdotdesign / elm-ui

UI library for making web applications with Elm
https://elm-ui.netlify.com
BSD 2-Clause "Simplified" License
920 stars 39 forks source link

Input List update value #90

Closed rande closed 6 years ago

rande commented 6 years ago

Hello,

I would like to generate a form with a list of emails with an add and remove button. Every thing is working properly. However, I cannot update the value as there is no way (as far as I know) to create a Msg with a reference to find the correct input.

screen shot 2017-09-25 at 23 43 25 screen shot 2017-09-25 at 23 44 54

Did I forgot something ? Thanks.

module Main exposing (..)

import Debug
import Html
import Ui.Button
import Ui.Input
import Ui.Ratings

type alias Model =
    { ratings : Ui.Ratings.Model
    , username : Ui.Input.Model
    , password : Ui.Input.Model
    , emails : List ( Ui.Input.Model, Ui.Button.Model )
    , addEmail : Ui.Button.Model
    }

type Msg
    = Ratings Ui.Ratings.Msg
    | InputUsername Ui.Input.Msg
    | InputPassword Ui.Input.Msg
    | InputEmail Ui.Input.Msg
    | AddEmail
    | RemoveEmail String

init : Model
init =
    { ratings =
        Ui.Ratings.init ()
            |> Ui.Ratings.size 10
    , username = Ui.Input.init ()
    , password = Ui.Input.init ()
    , emails = []
    , addEmail = Ui.Button.model "add email" "primary" "medium"
    }

update : Msg -> Model -> ( Model, Cmd Msg )
update msg_ model =
    case Debug.log "msg" msg_ of
        Ratings msg ->
            let
                ( ratings, cmd ) =
                    Ui.Ratings.update msg model.ratings
            in
            ( { model | ratings = ratings }, Cmd.map Ratings cmd )

        InputUsername msg ->
            let
                ( username, cmd ) =
                    Ui.Input.update msg model.username
            in
            ( { model | username = username }, Cmd.map InputUsername cmd )

        InputPassword msg ->
            let
                ( password, cmd ) =
                    Ui.Input.update msg model.password
            in
            ( { model | password = password }, Cmd.map InputPassword cmd )

        AddEmail ->
            let
                emails =
                    List.append model.emails [ ( Ui.Input.init (), Ui.Button.model "x" "primary" "medium" ) ]
            in
            ( { model | emails = emails }, Cmd.none )

        InputEmail msg ->
            ( model, Cmd.none )

        RemoveEmail uid ->
            let
                emails =
                    List.filter
                        (\email ->
                            let
                                ( input, _ ) =
                                    Debug.log "email" email
                            in
                            Debug.log "email.result" (Debug.log "email.equal" uid /= Debug.log "input.equal" input.uid)
                        )
                        model.emails
            in
            ( { model | emails = emails }, Cmd.none )

viewEmails : List ( Ui.Input.Model, Ui.Button.Model ) -> List (Html.Html Msg)
viewEmails emails =
    List.map
        (\email ->
            let
                ( input, button ) =
                    email
            in
            Html.div []
                [ Html.map InputEmail (Ui.Input.view input)
                , Ui.Button.render (RemoveEmail input.uid) button
                , Html.text input.uid
                ]
        )
        emails

view : Model -> Html.Html Msg
view model =
    Html.div []
        [ Html.map InputUsername (Ui.Input.view model.username)
        , Html.text model.username.value
        , Html.br [] []
        , Html.map InputPassword (Ui.Input.view model.password)
        , Html.text model.password.value
        , Html.div [] (viewEmails model.emails)
        , Html.br [] []
        , Ui.Button.render AddEmail model.addEmail
        ]

subscriptions : Model -> Sub Msg
subscriptions model =
    Sub.none

main =
    Html.program
        { init = ( init, Cmd.none )
        , update = update
        , view = view
        , subscriptions = subscriptions
        }
gdotdesign commented 6 years ago

You can identify an input by it's uid field, so you can do:

In the messages:

InputEmail String Ui.Input.Msg

In the view:

Html.map (InputEmail input.uid) (Ui.Input.view input)

And in the update:

InputEmail uid msg ->
  let
    foldEmail ( commands, emails ) input =
      if input.uid == uid then
        let
          ( command, updatedInput ) = Ui.Input.update msg input
        in
          ( Sub.batch [ commands, command ], updatedInput :: emails )
      else
        ( commands, input :: emails )

    ( cmd, emails ) = 
      List.foldl foldEmail ( Cmd.none, [] ) model.emails

  in
    ( { model | emails = emails }, cmd )
rande commented 6 years ago

@gdotdesign thanks for your reply, I adjust your code to match the demo. However there is an issue with the view update, the DOM is correctly updated, however the page still display the wrong value, see the small demo:

demo

Here the code : https://gist.github.com/rande/1827b8760a8bff39bed6a588ff7eeb66#file-test-elm-L89-L104

gdotdesign commented 6 years ago

Ui.Input˛just tracks the value it does not set it, in the case above when you remove an input it does not actually remove it from the DOM (it actually removes the last row) so the value remains the same.

You need to use Html.Keyed http://package.elm-lang.org/packages/elm-lang/html/2.0.0/Html-Keyed which tracks the elements with an ID (the first tuple) so when the element with that ID is removed it actually removes the corresponding DOM elements:

Html.Keyed.node "div" []
 ("row-" ++ input.uid
  , node "div" [] [Html.map (InputEmail input.uid) (Ui.Input.view input), button])
rande commented 6 years ago

@gdotdesign thanks for help

for the record, here the final version https://gist.github.com/rande/556e7f920f2a3791103cb56968dc5a18