【问题标题】:How to submit a form in Elm?如何在 Elm 中提交表单?
【发布时间】:2016-04-03 15:34:32
【问题描述】:

这是一个非常基本的问题,但我没有找到任何示例。
我有这样的看法:

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
        ]
    ]

因此它会显示一个包含内容的表单。
因此,如果我在输入和 textarea 中写入内容以更新内容,我如何在按钮上的 onClick 事件上“捕捉”我更新的模型以发送它?

【问题讨论】:

    标签: forms submit elm


    【解决方案1】:

    在 Elm 中处理表单的标准方法是每当表单上的任何内容发生更改时触发对模型的更新。您通常会看到某种on 事件属性附加到每个表单元素。

    对于您的示例,您需要使用 on "input" 触发事件,以使用最新值更新您的模型。但在我们这样做之前,我们需要创建一些响应来自任一字段的更新的操作。

    type Action
      = SubmitPost
      | UpdateTitle String
      | UpdateContent String
    

    我冒昧地将您的 SubmitPost Model 操作更改为 SubmitPost。由于我们将您的代码更改为始终是最新的,因此除了操作 SubmitPost 来触发执行提交的事件之外,您不需要任何其他操作。

    现在您有了其他操作,您需要在 update 函数中处理它们:

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

    我们现在可以将on 属性添加到您的文本字段中,以便在发生任何变化时触发更新。 "input" 是当文本内容更改时浏览器将触发的事件,它为您提供更多的覆盖范围,而不仅仅是观看 keypress 之类的事件。

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

    targetValue 解码器是一个 Json 解码器,它检查触发的 javascript 事件,深入到 javascript 对象内的 event.target.value 字段,其中包含文本字段的完整值。

    【讨论】:

    • @BoumTAC 这不是“黑客”,这是你在 Elm 中编码的方式。
    • @halfzebra 我明白,但我也明白我们是在 2016 年而不是 2005 年。我也明白人们永远不会像这样使用 elm
    • @BoumTAC stackoverflow 是人们前来查看或询问各种复杂问题答案的地方。当然,欢迎您发表意见,但是,这不是此类事情的平台,像您这样的 cmets 可能会导致知识较少的人跟随您的领导。 halfzebra 对这不是“黑客攻击”做出了有效的评论,听起来你的挫败感已经让你变得更好了。 Elm 采用不同的思维方式,但与双向绑定相比,它确实是一种有价值的数据流推理方式。坚持住。
    • @BoumTAC 有趣的是,2017 年最流行的 JavaScript 状态管理库 redux 的 influence 来自 Elm。 Elm 架构工作的约束引导您朝着一个方向,使构建可扩展/可维护的应用程序变得容易,可能以看起来复杂的琐碎表单绑定为代价。
    • 数据绑定无论如何都被高估了。您在编写显式处理程序时获得的控制和概念一致性、无缺陷性和无需重新学习所有新的 UI 工具包非常值得在样板文件上花费几分钟。
    【解决方案2】:

    Full example on ellie 用于 elm-0.18,基于 http://musigma.org/elm/2016/11/28/elm.html

    将下面的文件另存为 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 )
            }
    

    然后运行:

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

    在浏览器中打开http://localhost:8000/Main.elm

    【讨论】:

    • 非常好!但是我花了一些时间才弄清楚onInput &lt;| (toUpdateComment &lt;&lt; \title -&gt; NewComment 1 title newComment.body) 中的“标题”将直接来自onInput
    • 我已将其更改为onInput (\v -&gt; toUpdateComment { newComment | title = v })。应该更清楚
    • 对于任何想知道(\v -&gt; toUpdateComment { newComment | title = v }) 含义的人。这是一个带有单个参数v 的 lambda 函数。这是 lambda (\ x y -&gt; x * y ) 的另一个示例
    【解决方案3】:

    这是我发现在 Elm (0.18) 中定义 HTML 表单的“最新”方式如下。请注意,它与表单标签的 onSubmit 属性挂钩,而不是特定按钮的 onClick。

    view : Model -> Html Msg
    view model =
        Html.form
            [ class "my-form"
            , onWithOptions
                "submit"
                { preventDefault = True, stopPropagation = False }
                (Json.Decode.succeed SubmitPost)
            ]
            [ button []
                [ text "Submit"
                ]
            ]
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-10-31
    • 2017-09-22
    • 2016-09-26
    • 2014-09-10
    • 2014-10-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多