2015-10-17 4 views
8

Was wäre der richtige Weg, um einen Klick außerhalb einer einzelnen Komponente zu behandeln, der diese Komponente verbergen soll?Ausblenden einer Komponente beim Klicken außerhalb von

Beispiel für eine solche Komponente könnte ein Dropdown-Menü, ein Datepicker und dergleichen sein. Wir erwarten normalerweise, dass sie sich verstecken, wenn wir nach draußen klicken. Aber dazu müssen wir einige "unreine" Hacks spielen, von denen ich nicht weiß, wie sie im FRP-Stil vermieden werden können.

Ich suchte nach relevanten React Beispiele für Ideen und gefunden this, aber sie alle scheinen darauf angewiesen Callbacks an globale Objekte anhängen, die dann den Zustand der internen Komponente ändern.

+1

Können Sie nicht etwas Ähnliches wie die Sachen Reagieren versuchen Sie gefunden? Ein globaler Click-Handler sendet eine "Action", die in der 'update' -Funktion des Parents der Komponente (oder der Komponente selbst, wenn Sie es als Teil ihrer Aufgabe betrachten, wann sie ausgeblendet werden soll) behandelt wird und bewertet, ob etwas benötigt wird versteckt werden, dann reflektiere das in deinem 'Model'. – Apanatshka

+0

Danke für den Vorschlag, ich werde versuchen, so etwas zu tun, und wenn es funktioniert, werde ich die meisten meine Lösung (ich muss nur mehr über Native js Module zuerst lernen). Mein Hauptanliegen, diesen Weg einzuschlagen (vorausgesetzt, Sie meinen, die Handler an Mount-/Unmount-Events wie bei den React-Lösungen anzuhängen), war, dass dies den Zweck von FRP zunichte machen könnte, wenn wir die auferlegten Beschränkungen sowieso brechen.Aber ich erkannte, dass es in Ordnung sein könnte, wenn es nur in seltenen Fällen wie diesem verwendet wird. – ave

+0

Ich kenne keine richtige In-Elm-Lösung, daher schlage ich vor, dass Sie die React-Lösung replizieren, um Ihr Problem jetzt zu beheben. Aber öffne eine Diskussion auf der Mailingliste zu diesem Thema. Es sollte eine richtige Lösung geben, die diese Art von Hacks nicht erfordert;) – Apanatshka

Antwort

2

Das folgende Beispiel, das etwas ähnlich wie Sie beschreibt.

modal mit einer Adresse präsentiert wird, um die aktuellen Fensterabmessungen und ELM-html Html Komponente (was die Sache, wie ein Datumsauswahl oder eine Form zu fokussiert) (a ‚entlassen‘ Ereignis zu senden).

Wir fügen einen Click-Handler an das umgebende Element an; Nachdem wir ihm eine passende ID gegeben haben, können wir herausfinden, ob empfangene Klicks auf ihn oder das Kind zutreffen, und diese entsprechend weiterleiten. Das einzige wirklich clevere Bit ist die Bereitstellung von customDecoder, um Klicks auf das untergeordnete Element herauszufiltern.

Anderswo ändert sich unser Modellzustand bei Empfang des 'entlassen' -Ereignisses so, dass wir nicht mehr modal anrufen müssen.

Dies ist eine recht große Codebeispiel, das die Verwendung eines fairen paar Ulme Pakete macht so fragen Sie bitte, ob alles weitere Erklärung erfordert

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, vielen Dank, das ist sehr hilfreich ! Auch ein bisschen ähnlich wie ich es bisher versucht habe. Der Dekodierungsteil von "target" ist interessant. Es ist wahrscheinlich außerhalb des Bereichs der Frage, aber wollte nur darauf hinweisen, dass es mir scheint, dass es unvermeidlich ist, JS native Aufrufe zu verwenden, um die Position des angeklickten Elements relativ zu dem Dokument und den generierten modalen Dimensionen herauszufinden (zumindest habe ich das in meinem Fall getan, indem ich Typsicherheitsüberprüfungen umgangen habe und js native Funktionen aufgerufen habe, die 'target.getBoundingClientRect()' intern verwenden, so dass Modal in der Nähe des Ziels platziert werden kann. – ave

+1

Sie könnten die nativen Aufrufe, die Sie haben, durch Bits von http://package.elm-lang.org/packages/TheSeamau5/elm-html-decoder/1.0.1/Html-Decoder ersetzen (Anmerkung: nicht von mir). – grumpyjames

1

Die bestehende Antwort funktioniert nicht in Ulme V0.18 (Signal wurde in 0.17 entfernt), also wollte ich es updaten. Die Idee ist, einen transparenten Hintergrund auf oberster Ebene hinter dem Dropdown-Menü hinzuzufügen. Dies hat den Bonuseffekt, dass Sie alles hinter dem Menü verdunkeln können, wenn Sie möchten.

Dieses Beispielmodell hat eine Liste von Wörtern, und jedes Wort kann ein offenes Dropdown (und einige zugehörige Informationen) haben, also übergebe ich sie, um zu sehen, ob einige von ihnen offen sind. In diesem Fall zeige ich das Hintergrund-div vor allem anderen:

Es gibt eine Kulisse in der Hauptansicht Funktion:

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) 
:

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 in der App-Top-Level-Update-Funktion behandelt wird

Und die Dinge mit SCSS gestylt:

.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; 
}