Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Elm: Conditional preventDefault (with contentEditable)

Tags:

elm

I'm trying to make a content editable tag that uses enter to update the model.

My code is below, and here is a version that you can play around with on Ellie.

The on "blur" attribute works and updates the model when you click away. But I want to get the same 'update' functionality when an enter is pressed.

view : Model -> Html Msg
view model =
    let
        attrs =
            [ contenteditable True
              --, on "blur" (Json.map UpdateTitle targetTextContent)
            , onInput2 UpdateTitle
            , onEnter EnterPressed
            , id "title"
            , class "title"
            ]
    in
        div []
            [ h1 attrs [ text model.existing ]
            , text "Click above to start editing. Blur to save the value. The aim is to capture an <enter> and interpret that as a blur, i.e. to save the value and blur the field"
            , p [] [ text <| "(" ++ model.existing ++ ")" ]
            ]


targetTextContent : Json.Decoder String
targetTextContent =
    Json.at [ "target", "textContent" ] Json.string


onInput2 : (String -> msg) -> Attribute msg
onInput2 msgCreator =
    on "input" (Json.map msgCreator targetTextContent)


onEnter : (Bool -> msg) -> Attribute msg
onEnter enterMsg =
    onWithOptions "keydown"
        { stopPropagation = False
        , preventDefault = False
        }
        (keyCode
            |> Json.andThen
                (\ch ->
                    let
                        _ =
                            Debug.log "on Enter" ch
                    in
                        Json.succeed (enterMsg <| ch == 13)
                )
        )

This code seems to be updating the model ok, but the DOM is getting messed up. For example if I enter enter after "blast" I see this

enter image description here

I tried switching to Html.Keyed and using "keydown" but it did not make any difference or just created different issues.

like image 441
Simon H Avatar asked Feb 22 '17 11:02

Simon H


1 Answers

Solved! The key point is the filter function that uses Json.Decode.fail so that only <enter> is subject to preventDefault. See https://github.com/elm-lang/virtual-dom/issues/18#issuecomment-273403774 for the idea.

view : Model -> Html Msg
view model =
    let
        attrs =
            [ contenteditable True
            , on "blur" (Json.map UpdateTitle targetTextContent)
            , onEnter EnterPressed
            , id "title"
            , class "title"
            ]
    in
        div []
            [ h1 attrs [ text model.existing ]
            , text "Click above to start editing. Blur to save the value. The aim is to capture an <enter> and interpret that as a blur, i.e. to save the value and blur the field"
            , p [] [ text <| "(" ++ model.existing ++ ")" ]
            ]


targetTextContent : Json.Decoder String
targetTextContent =
    Json.at [ "target", "textContent" ] Json.string


onEnter : msg -> Attribute msg
onEnter msg =
    let
        options =
            { defaultOptions | preventDefault = True }

        filterKey code =
            if code == 13 then
                Json.succeed msg
            else
                Json.fail "ignored input"

        decoder =
            Html.Events.keyCode
                |> Json.andThen filterKey
    in
        onWithOptions "keydown" options decoder
like image 172
Simon H Avatar answered Sep 20 '22 02:09

Simon H