在应该隐藏此组件的单个组件之外处理点击的正确方法是什么?
此类组件的示例可以是下拉菜单,日期选择器等。当我们点击外面时,我们通常希望它们隐藏起来。但要做到这一点,似乎我们必须执行一些“不纯”的黑客攻击,我不确定如何避免FRP风格。
我搜索了相关的React示例并找到了this,但它们似乎都依赖于将回调附加到全局对象,然后修改内部组件的状态。
答案 0 :(得分:2)
以下示例与您描述的内容类似。
modal
会显示一个地址(发送'dismiss'事件),当前窗口尺寸以及elm-html Html
组件(这是需要关注的事情,如日期选择器或表格。
我们将点击处理程序附加到周围的元素;如果收到的点击适用于它或孩子,我们可以给它一个合适的ID,并适当地转发它们。唯一真正聪明的是部署customDecoder
来过滤子元素的点击。
在其他地方,在收到'dismiss'事件时,我们的模型状态发生变化,我们不再需要致电modal
。
这是一个非常大的代码示例,它使用了很少的榆树包,所以请询问是否需要进一步解释
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)
答案 1 :(得分:2)
现有的答案在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;
}
答案 2 :(得分:0)
在这里参加聚会有点晚了,但是我在完全相同的问题上苦苦挣扎,而榆树社区在松弛方面提出了一种检测元素外部点击的好方法(比如下拉菜单)。
这个想法是,您可以通过void _settingModalBottomSheet(context) {
showModalBottomSheet(
context: context,
//elevation: 8.0,
builder: (BuildContext bc) {
return Directionality(
textDirection: TextDirection.rtl,
child: Container(
padding: EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 5.0),
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Text(
'لطفا یکی از گزینه های مربوط به ابزارها را انتخاب کنید',
style: TextStyle(
fontFamily: 'IranSansBold',
fontSize: 13.0,
),
),
),
),
Card(
elevation: 8.0,
child: Column(
children: <Widget>[
ConstrainedBox(
constraints: BoxConstraints(
minWidth: 100.0,
minHeight: 100.0
),
child: Container(
margin: EdgeInsets.symmetric(horizontal: 10.0),
child: Column(
children: <Widget>[
Container(
width: 100.0,
height: 100.0,
decoration: BoxDecoration(
color: Colors.white,
borderRadius:
BorderRadius.all(Radius.circular(5.0)),
boxShadow: [
BoxShadow(
color: Colors.grey,
offset: Offset(0.0, 0.0),
blurRadius: 1.0)
],
),
child: Text('TEST'),
)
],
),
),
)
],
),
)
],
),
),
);
});
}
将全局侦听器附加到mousedown
并将其传递给自定义解码器,该解码器将从事件对象中解码BrowserEvents.onMouseDown
DOM节点。 “解码DOM节点”是指仅解码节点的target
和id
属性。 parentNode
将允许递归遍历DOM树,并为每个节点检查其parentNode
是否与下拉列表的ID相同。
此代码(在Elm 0.19中)如下所示:
id
代码使用Json-Decode软件包,需要通过-- 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")
安装。