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
}
问题就在这里,在处理来自onInput
的Change
消息时:
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
,然后使用新的 searchText
和 filtered
更新 model
列表。 model.searchText
筛选时仍具有之前的值。过滤时使用 value
代替,然后它按预期工作。
我做错了什么导致 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
}
问题就在这里,在处理来自onInput
的Change
消息时:
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
,然后使用新的 searchText
和 filtered
更新 model
列表。 model.searchText
筛选时仍具有之前的值。过滤时使用 value
代替,然后它按预期工作。