Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to submit a form in Elm?

Tags:

forms

submit

elm

It's a really basic question but I didn't find any example.
I have a view like this :

view address model =
  div []
    [ div [] [ text <|"ID : " ++ toString model.id ]
    , form
        []
        [ input [ value model.title ] []
        , textarea [ value model.content ] []
        , button [ onClick address ( SubmitPost model ) ] [ text "Submit" ] // Here is the issue, I want to send my updated model
        ]
    ]

So it display a form with the content inside.
So if I write in my input and textarea to update the content, how do I "catch" my updated model on the onClick event on the button to send it?

like image 969
BoumTAC Avatar asked Apr 03 '16 15:04

BoumTAC


3 Answers

The standard way to handle forms in Elm is to trigger updates to your model whenever anything changes on the form. You will typically see some kind of on event attribute attached to each form element.

For your example, you'll want to use on "input" to fire events that update your model with the latest value. But before we can do that, we'll need to create some actions that respond to updates from either field.

type Action
  = SubmitPost
  | UpdateTitle String
  | UpdateContent String

I took the liberty of changing your SubmitPost Model action to just SubmitPost. Since we're changing your code to always be up to date, you don't need anything other than the action SubmitPost to trigger an event that does the submission.

Now that you have the additional actions, you'll need to handle them in the update function:

update action model =
  case action of
    UpdateTitle s -> 
      ({ model | title = s }, Effects.none)
    UpdateContent s -> 
      ({ model | content = s }, Effects.none)
    ...

We can now add the on attributes onto your text fields to trigger updates whenever anything changes. "input" is the event that browsers will fire when text content changes, and it gives you more coverage than just watching for something like keypress events.

view address model =
  div []
    [ div [] [ text <| "ID : " ++ toString model.id ]
    , form
      []
      [ input
        [ value model.title
        , on "input" targetValue (Signal.message address << UpdateTitle)
        ]
        []
      , textarea
        [ value model.content
        , on "input" targetValue (Signal.message address << UpdateContent)
        ]
        []
      , button [ onClick address SubmitPost ] [ text "Submit" ]
      ]
    ]

The targetValue decoder is a Json Decoder which inspects the javascript event that was fired, drilling down to the event.target.value field inside the javascript object, which contains the full value of the text field.

like image 171
Chad Gilbert Avatar answered Nov 07 '22 21:11

Chad Gilbert


Full example on ellie for elm-0.18, based on http://musigma.org/elm/2016/11/28/elm.html

Save below file as Main.elm

module Main exposing (main)

import Html exposing (Html, div, text, form, textarea, button, input)
import Html.Attributes exposing (type_, action, value, disabled)
import Html.Events exposing (onSubmit, onInput)
import Http
import Json.Decode as Json
import Json.Encode


type alias Model =
    { newComment : NewComment
    , comments : List Comment
    }


emptyModel : Model
emptyModel =
    { newComment = emptyNewComment
    , comments = []
    }


emptyNewComment =
    NewComment -1 "" ""


type alias NewComment =
    { userId : Int
    , title : String
    , body : String
    }


type Msg
    = AddComment
    | UpdateComment NewComment
    | AddCommentHttp (Result Http.Error Comment)


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        AddComment ->
            let
                newComment =
                    Debug.log "model.newComment" model.newComment
            in
                ( { model | newComment = emptyNewComment }, postComment newComment )

        UpdateComment newComment ->
            ( { model | newComment = newComment }, Cmd.none )

        AddCommentHttp (Ok response) ->
            let
                _ =
                    Debug.log "response" response
            in
                ( { model | comments = model.comments ++ [ response ] }, Cmd.none )

        AddCommentHttp (Err err) ->
            let
                _ =
                    Debug.log "err" err
            in
                ( model, Cmd.none )


postComment newComment =
    Http.send AddCommentHttp
        (Http.post "https://jsonplaceholder.typicode.com/posts"
            (encodeNewComment newComment)
            decodeComment
        )


encodeNewComment : NewComment -> Http.Body
encodeNewComment newComment =
    Http.jsonBody <|
        Json.Encode.object
            [ ( "title", Json.Encode.string newComment.title )
            , ( "body", Json.Encode.string newComment.body )
            , ( "userId", Json.Encode.int newComment.userId )
            ]


type alias Comment =
    { title : String
    , body : String
    , userId : Int
    , id : Int
    }


decodeComment : Json.Decoder Comment
decodeComment =
    Json.map4 Comment
        (Json.field "title" Json.string)
        (Json.field "body" Json.string)
        (Json.field "userId" Json.int)
        (Json.field "id" Json.int)


view : Model -> Html Msg
view model =
    div [] <|
        [ viewForm model.newComment UpdateComment AddComment
        ]
            ++ List.map (\comment -> div [] [ text <| toString comment ]) model.comments


viewForm : NewComment -> (NewComment -> msg) -> msg -> Html msg
viewForm newComment toUpdateComment addComment =
    form
        [ onSubmit addComment, action "javascript:void(0);" ]
        [ div []
            [ input
                [ value newComment.title
                , onInput (\v -> toUpdateComment { newComment | title = v })
                ]
                []
            ]
        , textarea
            [ value newComment.body
            , onInput (\v -> toUpdateComment { newComment | body = v })
            ]
            []
        , div []
            [ button
                [ type_ "submit"
                , disabled <| isEmpty newComment.title || isEmpty newComment.body
                ]
                [ text "Add Comment" ]
            ]
        ]


isEmpty : String -> Bool
isEmpty =
    String.isEmpty << String.trim


main : Program Never Model Msg
main =
    Html.program
        { view = view
        , update = update
        , subscriptions = \_ -> Sub.none
        , init = ( emptyModel, Cmd.none )
        }

and run:

elm package install -y elm-lang/http
elm-reactor

Open in web browser http://localhost:8000/Main.elm

like image 42
rofrol Avatar answered Nov 07 '22 21:11

rofrol


This is the "newest" way that I've found to define an HTML form in Elm (0.18) is below. Notice it hooks into the onSubmit property of the form tag rather than an onClick of a particular button.

view : Model -> Html Msg
view model =
    Html.form
        [ class "my-form"
        , onWithOptions
            "submit"
            { preventDefault = True, stopPropagation = False }
            (Json.Decode.succeed SubmitPost)
        ]
        [ button []
            [ text "Submit"
            ]
        ]
like image 4
Don Park Avatar answered Nov 07 '22 22:11

Don Park