Скрыть компонент при нажатии
каким будет правильный способ обработки щелчка за пределами одного компонента, который должен скрыть этот компонент?
примером такого компонента может быть выпадающее меню, 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;
}