在外部单击时隐藏组件

时间:2015-10-17 10:23:42

标签: elm

在应该隐藏此组件的单个组件之外处理点击的正确方法是什么?

此类组件的示例可以是下拉菜单,日期选择器等。当我们点击外面时,我们通常希望它们隐藏起来。但要做到这一点,似乎我们必须执行一些“不纯”的黑客攻击,我不确定如何避免FRP风格。

我搜索了相关的React示例并找到了this,但它们似乎都依赖于将回调附加到全局对象,然后修改内部组件的状态。

3 个答案:

答案 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节点”是指仅解码节点的targetid属性。 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") 安装。

我还写了article,详细说明了它是如何工作的,并提供了github下拉菜单的示例。