Elm onInput 字符后一位

Elm onInput character one behind

我做错了什么导致 onInput 报告的值后面有一个字符?

例如,在文本字段中键入“mil”以筛选到里程碑行。然后将其删除,恢复为空,你会看到它仍在过滤里程碑(也可以查看浏览器控制台,看到该值仍然是“m”,即使文本字段明显是“”)

module Main exposing (main)

import Browser
import Html exposing (Html, a, button, div, input, li, span, text, ul)
import Html.Attributes exposing (checked, class, classList, placeholder, style, type_, value)
import Html.Events exposing (custom, onBlur, onClick, onFocus, onInput)
import Json.Decode as Json


type alias Layer =
    { name : String
    , description : String
    , selected : Bool
    }


main : Program () Model Msg
main =
    Browser.element
        { init = init
        , update = update
        , view = view
        , subscriptions = \_ -> Sub.none
        }


type alias Model =
    { open : Bool
    , layers : List Layer
    , filtered : List Layer
    , searchText : String
    , highlightedIndex : Int
    }


init : () -> ( Model, Cmd Msg )
init _ =
    let
        layers =
            [ { name = "Parcels", description = "Show parcel lines", selected = False }
            , { name = "Mileposts", description = "Show Mile post markers", selected = False }
            ]
    in
    ( { open = False, layers = layers, filtered = layers, searchText = "", highlightedIndex = 0 }, Cmd.none )


type Msg
    = Open
    | Close
    | Change String
    | Up
    | Down
    | Toggle


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    let
        lastIndex =
            List.length model.filtered - 1
    in
    case msg of
        Open ->
            ( { model | open = True }, Cmd.none )

        Close ->
            ( { model | open = False }, Cmd.none )

        Change value ->
            let
                filtered =
                    model.layers
                        |> List.filter
                            (\{ name } ->
                                let
                                    _ =
                                        Debug.log "name" name

                                    _ =
                                        Debug.log "searchText" model.searchText
                                in
                                String.contains (String.toLower model.searchText) (String.toLower name) |> Debug.log "contains"
                            )
            in
            ( { model | searchText = value, filtered = filtered }, Cmd.none )

        Up ->
            if model.highlightedIndex == 0 then
                ( { model | highlightedIndex = lastIndex }, Cmd.none )

            else
                ( { model | highlightedIndex = model.highlightedIndex - 1 }, Cmd.none )

        Down ->
            if model.highlightedIndex == lastIndex then
                ( { model | highlightedIndex = 0 }, Cmd.none )

            else
                ( { model | highlightedIndex = model.highlightedIndex + 1 }, Cmd.none )

        Toggle ->
            let
                highlightedLayer =
                    model.filtered
                        |> List.indexedMap Tuple.pair
                        |> List.filterMap
                            (\( idx, layer ) ->
                                if idx == model.highlightedIndex then
                                    Just layer

                                else
                                    Nothing
                            )

                updatedFiltered =
                    model.filtered
                        |> List.indexedMap
                            (\idx layer ->
                                if idx == model.highlightedIndex then
                                    { layer | selected = not layer.selected }

                                else
                                    layer
                            )

                updatedLayers =
                    model.layers
                        |> List.map
                            (\layer ->
                                if [ layer ] == highlightedLayer then
                                    { layer | selected = not layer.selected }

                                else
                                    layer
                            )
            in
            ( { model | filtered = updatedFiltered, layers = updatedLayers }, Cmd.none )


view model =
    div []
        [ span [ class "mapboxgl-ctrl-geocoder--icon mapboxgl-ctrl-geocoder--icon-search" ]
            [ span [ class "ds-badge ds-badge--red ds-badge--circle", style "margin-top" "-2px" ] [ text "3" ]
            ]
        , input
            [ type_ "text"
            , class "mapboxgl-ctrl-geocoder--input"
            , placeholder "Search layers"
            , value model.searchText
            , onFocus Open
            , style "padding-left" "45px"
            , onInput Change

            --, onKey [(38, Up), (40, Down), (13, Toggle)]
            ]
            []
        , if model.open then
            div [ class "suggestions-wrapper" ]
                [ ul [ class "suggestions", style "display" "block" ]
                    (model.filtered
                        |> List.indexedMap
                            (\idx { name, description, selected } ->
                                li [ classList [ ( "active", model.highlightedIndex == idx ) ] ]
                                    [ a []
                                        [ div [ class "mapboxgl-ctrl-geocoder--suggestion flex flex-column" ]
                                            [ div [] [ input [ type_ "checkbox", style "margin-top" "5px", checked selected ] [] ]
                                            , div [ class "ml-1" ]
                                                [ div [ class "mapboxgl-ctrl-geocoder--suggestion-title" ] [ text name ]
                                                , div [ class "mapboxgl-ctrl-geocoder--suggestion-address" ] [ text description ]
                                                ]
                                            ]
                                        ]
                                    ]
                            )
                    )
                ]

          else
            text ""
        ]


onKey : List ( Int, Msg ) -> Html.Attribute Msg
onKey codes =
    let
        isEnterKey keyCode =
            case codes |> List.filter (\( code, _ ) -> code == keyCode) of
                [ ( _, msg ) ] ->
                    Json.succeed
                        { message = msg
                        , stopPropagation = True
                        , preventDefault = True
                        }

                _ ->
                    Json.fail "silent failure :)"
    in
    custom "keydown" <|
        Json.andThen isEnterKey Html.Events.keyCode


options =
    { stopPropagation = True
    , preventDefault = True
    }

https://ellie-app.com/fcgHCF2z5sza1

问题就在这里,在处理来自onInputChange消息时:

        Change value ->
            let
                filtered =
                    model.layers
                        |> List.filter
                            (\{ name } ->
                                let
                                    _ =
                                        Debug.log "name" name

                                    _ =
                                        Debug.log "searchText" model.searchText
                                in
                                String.contains (String.toLower model.searchText) (String.toLower name) |> Debug.log "contains"
                            )
            in
            ( { model | searchText = value, filtered = filtered }, Cmd.none )

您正在使用 model.searchText 过滤列表,将结果绑定到 filtered,然后使用新的 searchTextfiltered 更新 model列表。 model.searchText 筛选时仍具有之前的值。过滤时使用 value 代替,然后它按预期工作。