2015-10-17 11 views
8

Jaki byłby właściwy sposób obsługi kliknięcia poza pojedynczym komponentem, który ma ukryć ten składnik?Ukryj komponent po kliknięciu na zewnątrz.

Przykładem takiego komponentu może być menu rozwijane, datepicker i tym podobne. Zwykle oczekujemy, że ukryją się, gdy klikniemy na zewnątrz. Ale aby to zrobić, wydaje się, że musimy wykonać kilka "nieczystych" hacków, których nie jestem pewien w stylu FRP.

Szukałem odpowiednich przykładów odpowiedzi na pomysły i znalazłem this, ale wszystkie zdają się polegać na dołączaniu wywołań zwrotnych do obiektów globalnych, które następnie modyfikują stan składnika wewnętrznego.

+1

Nie możesz wypróbować czegoś podobnego do znalezionych rzeczy React? Globalny moduł obsługi kliknięć wysyła "Akcja", która jest obsługiwana w funkcji 'update' nadrzędnej składnika (lub samego komponentu, jeśli uważasz, że jest to część jego zadania, aby wiedzieć, kiedy ma być ukryty) i ocenia, czy coś potrzebuje być ukrytym, a następnie odzwierciedlić to w swoim "Modelu". – Apanatshka

+0

Dzięki za sugestię, postaram się zrobić coś takiego, a jeśli to zadziała, to w większości znajdę rozwiązanie (po prostu muszę się najpierw dowiedzieć więcej o modułach j. Macierzystych). Moją główną troską związaną z pójściem tą ścieżką (zakładając, że mówisz o dołączaniu opiekunów podczas montowania/odmontowywania wydarzeń, jak w przypadku rozwiązań React), było to, że może to pokonać cel FRP, jeśli i tak zamierzamy przełamać nałożone ograniczenia.Ale zdałem sobie sprawę, że może to być w porządku, jeśli jest używane tylko w takich rzadkich okazjach. – ave

+0

Nie znam prawidłowego rozwiązania In-Elm, dlatego sugeruję, abyś zreplikował rozwiązanie React, aby rozwiązać problem. Ale otwórz dyskusję na temat listy mailingowej na temat tego problemu. Powinno być odpowiednie rozwiązanie, które nie wymaga tego rodzaju hackowania;) – Apanatshka

Odpowiedz

2

Poniższy przykład przedstawia coś, co można opisać.

modal prezentowane z adresem (aby wysłać Odrzuć zdarzenie), bieżące okno wymiary, a składnik wiąz-html Html (co jest rzeczą być skoncentrowany, jak datepicker lub formularza).

Dołączamy obsługę kliknięcia do otaczającego elementu; po podaniu odpowiedniego identyfikatora możemy ustalić, czy otrzymane kliknięcia mają zastosowanie do niego lub dziecka i odpowiednio je przesłać. Jedynym naprawdę sprytnym rozwiązaniem jest wdrożenie customDecoder w celu odfiltrowania kliknięć elementu potomnego.

W innym miejscu, po odebraniu zdarzenia "Odrzuć" nasz stan modelu zmienia się tak, że nie musimy już dzwonić pod numer modal.

Jest to dość duża próbka kodu, który korzysta z kilku pakietów targowych wiąz, więc proszę pytać jeśli coś wymaga dalszych wyjaśnień

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) 
+0

Wow, wielkie dzięki, to jest bardzo pomocne ! Również trochę podobne do tego, co do tej pory próbowałem. Część dekodująca "target" jest interesująca. Jest to prawdopodobnie poza zakresem pytania, ale chciałem tylko zwrócić uwagę, że wydaje mi się, że nieuniknione jest korzystanie z połączeń natywnych JS, aby móc określić położenie elementu klikniętego względem dokumentu i wygenerowane wymiary modalne (przynajmniej tak zrobiłem w moim przypadku, pomijając kontrole bezpieczeństwa typów i wywołując funkcje natywne js, które wykorzystują wewnętrznie 'cel.getBoundingClientRect()', aby modal mógł zostać umieszczony w pobliżu celu). – ave

+1

Być może będziesz w stanie zastąpić natywne połączenia z bitami http://package.elm-lang.org/packages/TheSeamau5/elm-html-decoder/1.0.1/Html-Decoder (uwaga: nie moje) – grumpyjames

1

Istniejący odpowiedź nie działa w wiąz v0.18 (Signal został usunięty w 0.17), więc chciałem go zaktualizować. Pomysł polega na dodaniu przezroczystego tła najwyższego poziomu za menu rozwijanym. Ma to dodatkowy efekt, dzięki któremu możesz przyciemnić wszystko za menu, jeśli chcesz.

Ten przykładowy model ma listę słów, a każde słowo może mieć otwarte menu rozwijane (i niektóre powiązane informacje), więc mapuję je, aby sprawdzić, czy którekolwiek z nich są otwarte, w którym to przypadku wyświetlam tło div przed wszystkim innym:

jest zasłona w głównej funkcji widoku:

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 jest obsługiwana w funkcji aktualizacji najwyższego poziomu w aplikacji:

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) 

I stylizowane elementy za pomocą 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; 
} 
Powiązane problemy