Скрыть компонент при нажатии

каким будет правильный способ обработки щелчка за пределами одного компонента, который должен скрыть этот компонент?

примером такого компонента может быть выпадающее меню, datepicker и тому подобное. Мы обычно ожидаем, что они спрячутся, когда мы щелкаем снаружи. Но для этого, похоже, нам нужно выполнить некоторые "нечистые" хаки, которые я не уверен, как избежать в стиле FRP.

Я искал соответствующие примеры реакции для идей и нашел этой но они все, похоже, полагаются на присоединение обратных вызовов к глобальным объектам, которые затем изменяют состояние внутреннего компонента.

2 ответов


следующий пример, который делает что-то похожее на то, что вы описываете.

modal представлен адрес (для отправки события "уволить"), текущие размеры окна и elm-html Html компонент (который должен быть сфокусирован, как datepicker или форма).

мы прикрепляем обработчик щелчка к окружающему элементу; дав ему соответствующий идентификатор, мы можем работать, если полученные щелчки применяются к нему или ребенку, и пересылать их на соответственно. Единственный действительно умный бит-это развертывание customDecoder чтобы отфильтровать щелчки по дочернему элементу.

в другом месте, при получении события "уволить", наше модельное состояние изменяется так, что нам больше не нужно звонить 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;
}