2015-10-17 159 views
8

在應該隱藏此組件的單個組件之外處理點擊的正確方法是什麼?在外部點擊時隱藏組件

此類組件的示例可能是下拉菜單,日期選擇器等。我們通常希望他們在外面點擊時隱藏。但要這樣做,似乎我們必須執行一些「不純」的黑客攻擊,而我不確定如何避免使用FRP風格。

我搜索了想法的相關React示例並找到了this,但它們似乎都依賴於將回調附加到全局對象,然後修改內部組件的狀態。

+1

你不能嘗試類似於你找到的React的東西嗎?一個全局的點擊處理程序發送一個'Action',它在組件的父級(或組件本身,如果你認爲它是知道何時被隱藏的任務的一部分)的'update'函數中處理,並且評估是否需要隱藏起來,然後在你的'模型'中反映出來。 – Apanatshka

+0

感謝您的建議,我會嘗試做這樣的事情,如果它能工作,我會最我的解決方案(我只需要先了解更多關於Native js模塊)。我主要關心的是,如果我們打算打破強制性限制,那麼這種方式可能會挫敗FRP的目的(假設您將處理程序附加到掛載/卸載事件上,如React解決方案)。但是我意識到,如果僅在諸如此類的罕見情況下使用它,它可能沒問題。 – ave

+0

我不知道正確的in-Elm解決方案,所以我建議您現在複製React解決方案來解決您的問題。但請在郵件列表上打開關於此問題的討論。應該有一個適當的解決方案,不需要這些類型的黑客;) – Apanatshka

回答

2

以下示例與您所描述的內容類似。提供了一個地址(發送「解散」事件到),當前窗口尺寸和一個elm-html組件(這是要聚焦的東西,比如日期選擇器或表單)。

modal

我們將點擊處理程序附加到周圍的元素;給它一個適當的ID,我們可以計算出是否收到的點擊適用於它或孩子,並適當地轉發它們。唯一真正聰明的是部署customDecoder來過濾掉子元素上的點擊。

在其他地方,在接收到'解僱'事件後,我們的模型狀態發生變化,我們不再需要致電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被刪除),所以我想更新它。這個想法是在下拉菜單後面添加一個頂級透明背景。如果你願意,這可以使菜單後面的所有內容變暗。

此示例模型有一個單詞列表,任何單詞都可能有一個打開的下拉列表(以及一些關聯的信息),因此我將它們映射以查看它們中的任何一個是否已打開,在這種情況下,我會顯示背景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; 
}