在外部单击时隐藏组件
Hide a component when clicked outside
处理应该隐藏此组件的单个组件外部的点击的正确方法是什么?
此类组件的示例可能是下拉菜单、日期选择器等。我们通常希望它们在我们点击外部时隐藏起来。但要做到这一点,我们似乎必须执行一些 "impure" 我不确定如何在 FRP 风格中避免的 hack。
我搜索了相关的 React 示例以寻找想法并找到了 this,但它们似乎都依赖于将回调附加到全局对象,然后修改内部组件的状态。
以下示例与您描述的内容类似。
modal
显示地址(发送 'dismiss' 事件)、当前 window 维度和 elm-html Html
组件(这是要重点关注的事情,例如日期选择器或表格)。
我们将点击处理程序附加到周围的元素;给它一个合适的 id 后,我们可以计算出收到的点击是否适用于它或 child,并适当地转发它们。唯一真正聪明的一点是部署 customDecoder
来过滤掉对 child 元素的点击。
在其他地方,在接收到 'dismiss' 事件时,我们的模型状态发生变化,因此我们不再需要调用 modal
.
这是一个相当大的代码示例,它使用了相当少的 elm 包,所以请询问是否需要进一步解释
import Styles exposing (..)
import Html exposing (Attribute, Html, button, div, text)
import Html.Attributes as Attr exposing (style)
import Html.Events exposing (on, onWithOptions, Options)
import Json.Decode as J exposing (Decoder, (:=))
import Result
import Signal exposing (Message)
modal : (Signal.Address ()) -> (Int, Int) -> Html -> Html
modal addr size content =
let modalId = "modal"
cancel = targetWithId (\_ -> Signal.message addr ()) "click" modalId
flexCss = [ ("display", "flex")
, ("align-items", "center")
, ("justify-content", "center")
, ("text-align", "center")
]
in div (
cancel :: (Attr.id modalId) :: [style (flexCss ++ absolute ++ dimensions size)]
) [content]
targetId : Decoder String
targetId = ("target" := ("id" := J.string))
isTargetId : String -> Decoder Bool
isTargetId id = J.customDecoder targetId (\eyed -> if eyed == id then Result.Ok True else Result.Err "nope!")
targetWithId : (Bool -> Message) -> String -> String -> Attribute
targetWithId msg event id = onWithOptions event stopEverything (isTargetId id) msg
stopEverything = (Options True True)
现有答案在 elm v0.18 中不起作用(Signal
在 0.17 中被删除),所以我想更新它。这个想法是在下拉菜单后面添加一个顶级透明背景。如果您愿意,这具有能够使菜单后面的所有内容变暗的额外效果。
这个示例模型有一个单词列表,任何单词都可能有一个打开的下拉列表(和一些相关信息),所以我映射它们以查看是否有任何一个是打开的,在这种情况下我显示背景div 在其他一切之前:
主视图功能中有背景:
view : Model -> Html Msg
view model =
div [] <|
[ viewWords model
] ++ backdropForDropdowns model
backdropForDropdowns : Model -> List (Html Msg)
backdropForDropdowns model =
let
dropdownIsOpen model_ =
List.any (isJust << .menuMaybe) model.words
isJust m =
case m of
Just _ -> True
Nothing -> False
in
if dropdownIsOpen model then
[div [class "backdrop", onClick CloseDropdowns] []]
else
[]
CloseDropdowns
在应用程序的顶级更新功能中处理:
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
CloseDropdowns ->
let
newWords = List.map (\word -> { word | menuMaybe = Nothing } ) model.words
in
({model | words = newWords}, Cmd.none)
并使用 scss 设置样式:
.popup {
z-index: 100;
position: absolute;
box-shadow: 0px 2px 3px 2px rgba(0, 0, 0, .2);
}
.backdrop {
z-index: 50;
position: absolute;
background-color: rgba(0, 0, 0, .4);
top: 0;
right: 0;
bottom: 0;
left: 0;
}
这里的聚会有点晚了,但我一直在努力解决完全相同的问题,slack 上的 elm 社区提出了一种检测元素外部点击的好方法(比方说,下拉菜单)。
我们的想法是,您可以通过 BrowserEvents.onMouseDown
将全局侦听器附加到 mousedown
,并向其传递一个自定义解码器,该解码器将从事件中解码 target
DOM 节点目的。 "decoding DOM node" 我的意思是只解码节点的 id
和 parentNode
属性。 parentNode
将允许递归遍历 DOM 树,并为每个节点检查其 id
是否与下拉列表的 id 相同。
此代码(在 elm 0.19 中)如下所示:
-- the result answers the question: is the node outside of the dropdown?
isOutsideDropdown : String -> Decode.Decoder Bool
isOutsideDropdown dropdownId =
Decode.oneOf
[ Decode.field "id" Decode.string
|> Decode.andThen
(\id ->
if dropdownId == id then
-- found match by id
Decode.succeed False
else
-- try next decoder
Decode.fail "continue"
)
, Decode.lazy
(\_ -> isOutsideDropdown dropdownId |> Decode.field "parentNode")
-- fallback if all previous decoders failed
, Decode.succeed True
]
-- sends message Close if target is outside the dropdown
outsideTarget : String -> Decode.Decoder Msg
outsideTarget dropdownId =
Decode.field "target" (isOutsideDropdown "dropdown")
|> Decode.andThen
(\isOutside ->
if isOutside then
Decode.succeed Close
else
Decode.fail "inside dropdown"
)
-- subscribes to the global mousedown
subscriptions : Model -> Sub Msg
subscriptions _ =
Browser.Events.onMouseDown (outsideTarget "dropdown")
代码使用了Json-Decode包,需要通过elm install elm/json
安装。
我也写了一个article explaining in details how this works, and have an example of a dropdown on github.
处理应该隐藏此组件的单个组件外部的点击的正确方法是什么?
此类组件的示例可能是下拉菜单、日期选择器等。我们通常希望它们在我们点击外部时隐藏起来。但要做到这一点,我们似乎必须执行一些 "impure" 我不确定如何在 FRP 风格中避免的 hack。
我搜索了相关的 React 示例以寻找想法并找到了 this,但它们似乎都依赖于将回调附加到全局对象,然后修改内部组件的状态。
以下示例与您描述的内容类似。
modal
显示地址(发送 'dismiss' 事件)、当前 window 维度和 elm-html Html
组件(这是要重点关注的事情,例如日期选择器或表格)。
我们将点击处理程序附加到周围的元素;给它一个合适的 id 后,我们可以计算出收到的点击是否适用于它或 child,并适当地转发它们。唯一真正聪明的一点是部署 customDecoder
来过滤掉对 child 元素的点击。
在其他地方,在接收到 'dismiss' 事件时,我们的模型状态发生变化,因此我们不再需要调用 modal
.
这是一个相当大的代码示例,它使用了相当少的 elm 包,所以请询问是否需要进一步解释
import Styles exposing (..)
import Html exposing (Attribute, Html, button, div, text)
import Html.Attributes as Attr exposing (style)
import Html.Events exposing (on, onWithOptions, Options)
import Json.Decode as J exposing (Decoder, (:=))
import Result
import Signal exposing (Message)
modal : (Signal.Address ()) -> (Int, Int) -> Html -> Html
modal addr size content =
let modalId = "modal"
cancel = targetWithId (\_ -> Signal.message addr ()) "click" modalId
flexCss = [ ("display", "flex")
, ("align-items", "center")
, ("justify-content", "center")
, ("text-align", "center")
]
in div (
cancel :: (Attr.id modalId) :: [style (flexCss ++ absolute ++ dimensions size)]
) [content]
targetId : Decoder String
targetId = ("target" := ("id" := J.string))
isTargetId : String -> Decoder Bool
isTargetId id = J.customDecoder targetId (\eyed -> if eyed == id then Result.Ok True else Result.Err "nope!")
targetWithId : (Bool -> Message) -> String -> String -> Attribute
targetWithId msg event id = onWithOptions event stopEverything (isTargetId id) msg
stopEverything = (Options True True)
现有答案在 elm v0.18 中不起作用(Signal
在 0.17 中被删除),所以我想更新它。这个想法是在下拉菜单后面添加一个顶级透明背景。如果您愿意,这具有能够使菜单后面的所有内容变暗的额外效果。
这个示例模型有一个单词列表,任何单词都可能有一个打开的下拉列表(和一些相关信息),所以我映射它们以查看是否有任何一个是打开的,在这种情况下我显示背景div 在其他一切之前:
主视图功能中有背景:
view : Model -> Html Msg
view model =
div [] <|
[ viewWords model
] ++ backdropForDropdowns model
backdropForDropdowns : Model -> List (Html Msg)
backdropForDropdowns model =
let
dropdownIsOpen model_ =
List.any (isJust << .menuMaybe) model.words
isJust m =
case m of
Just _ -> True
Nothing -> False
in
if dropdownIsOpen model then
[div [class "backdrop", onClick CloseDropdowns] []]
else
[]
CloseDropdowns
在应用程序的顶级更新功能中处理:
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
CloseDropdowns ->
let
newWords = List.map (\word -> { word | menuMaybe = Nothing } ) model.words
in
({model | words = newWords}, Cmd.none)
并使用 scss 设置样式:
.popup {
z-index: 100;
position: absolute;
box-shadow: 0px 2px 3px 2px rgba(0, 0, 0, .2);
}
.backdrop {
z-index: 50;
position: absolute;
background-color: rgba(0, 0, 0, .4);
top: 0;
right: 0;
bottom: 0;
left: 0;
}
这里的聚会有点晚了,但我一直在努力解决完全相同的问题,slack 上的 elm 社区提出了一种检测元素外部点击的好方法(比方说,下拉菜单)。
我们的想法是,您可以通过 BrowserEvents.onMouseDown
将全局侦听器附加到 mousedown
,并向其传递一个自定义解码器,该解码器将从事件中解码 target
DOM 节点目的。 "decoding DOM node" 我的意思是只解码节点的 id
和 parentNode
属性。 parentNode
将允许递归遍历 DOM 树,并为每个节点检查其 id
是否与下拉列表的 id 相同。
此代码(在 elm 0.19 中)如下所示:
-- the result answers the question: is the node outside of the dropdown?
isOutsideDropdown : String -> Decode.Decoder Bool
isOutsideDropdown dropdownId =
Decode.oneOf
[ Decode.field "id" Decode.string
|> Decode.andThen
(\id ->
if dropdownId == id then
-- found match by id
Decode.succeed False
else
-- try next decoder
Decode.fail "continue"
)
, Decode.lazy
(\_ -> isOutsideDropdown dropdownId |> Decode.field "parentNode")
-- fallback if all previous decoders failed
, Decode.succeed True
]
-- sends message Close if target is outside the dropdown
outsideTarget : String -> Decode.Decoder Msg
outsideTarget dropdownId =
Decode.field "target" (isOutsideDropdown "dropdown")
|> Decode.andThen
(\isOutside ->
if isOutside then
Decode.succeed Close
else
Decode.fail "inside dropdown"
)
-- subscribes to the global mousedown
subscriptions : Model -> Sub Msg
subscriptions _ =
Browser.Events.onMouseDown (outsideTarget "dropdown")
代码使用了Json-Decode包,需要通过elm install elm/json
安装。
我也写了一个article explaining in details how this works, and have an example of a dropdown on github.