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?
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.
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
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"
]
]