2015-10-17 27 views
8

このコンポーネントを非表示にする単一のコンポーネントの外でクリックを処理するにはどうすればよいでしょうか?クリックしたときにコンポーネントを非表示にする

このようなコンポーネントの例は、ドロップダウンメニュー、日付ピッカーなどです。私たちは通常、外をクリックしたときにそれらを非表示にすると考えています。しかし、そうするには、FRPスタイルで回避する方法がわからない「不公平な」ハッキングを実行する必要があるようです。

アイデアの関連する実例を検索したところ、thisが見つかりましたが、それらはすべて内部コンポーネントの状態を変更するグローバルオブジェクトへのコールバックの接続に依存しているようです。

+1

あなたが見つけたReactのものに似たものを試すことはできませんか?グローバルクリックハンドラは、コンポーネントの親の 'update'関数で処理された' Action'を送信します(コンポーネント自体を非表示にすることをタスクの一部とみなす場合、コンポーネント自体を必要とするかどうか評価します)隠しておいて、それをあなたの 'Model'に反映させます。 – Apanatshka

+0

提案をいただきありがとうございます。そのようなことをやろうとします。それがうまくいく場合、私はほとんどのソリューションを提供します(最初にネイティブjsモジュールについてもっと学ぶ必要があります)。私が主張しているのは、この解決策を実行することです(Reactソリューションのようなマウント/マウント解除イベントにハンドラを取り付けることを前提としています)と、強制された制限を破棄する場合は、しかし、まれにしか使用されないと大丈夫かもしれないことに気付きました。 – ave

+0

私はElmの適切なソリューションについてはわかりません。そのため、Reactソリューションを複製して今すぐ問題を解決することをお勧めします。しかし、この問題についてのメーリングリストに関する議論を開く。 )これらの種類のハッキングを必要としない適切な解決方法があるはずです; – Apanatshka

答えて

2

次の例では、説明するものと似たようなことを行います。

modalは、アドレスが提示される(日付ピッカーまたはフォームのように、集束されるものである)、現在のウィンドウの大きさ、及び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) 
+0

うわー、ありがとう、これはとても役に立ちます!これまでに試したことと少し似ています。 'target 'のデコード部分は面白いです。おそらく問題の範囲外ですが、JSネイティブコールを使用して、ドキュメントに対するクリックされた要素の位置と生成されたモーダルディメンションを把握することは不可避であると私は思っています少なくとも私は私の場合、タイプセーフティチェックをバイパスし、target.getBoundingClientRect()を内部的に使用するjsネイティブ関数を呼び出して、モーダルをターゲットの近くに置くことができます)。 – ave

+1

ネイティブコールをhttp://package.elm-lang.org/packages/TheSeamau5/elm-html-decoder/1.0.1/Html-Decoderのビットで置き換えることができます(注:私のものではありません) – grumpyjames

1

を必要とする場合、既存の答えはニレv0.18では動作しませんSignalを依頼してください、公正少数のニレのパッケージを利用した、非常に大規模なコードサンプルです0.17で削除されたので)、私はそれを更新したいと思いました。考え方は、ドロップダウンメニューの後ろにトップレベルの透明な背景を追加することです。あなたが望むなら、これはメニューの後ろのすべてを暗くすることができるというボーナス効果を持っています。

このモデル例では単語のリストがあり、単語には開いているドロップダウン(および関連する情報)がある場合があります。そのため、そのうちのいずれかが開いているかどうかを調べて、他のすべての前に:

メインビュー機能の背景があります:

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; 
} 
関連する問題