reflex-dom-contrib-0.4: src/Reflex/Dom/Contrib/Widgets/Modal.hs
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE RecursiveDo #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}
module Reflex.Dom.Contrib.Widgets.Modal where
------------------------------------------------------------------------------
import Data.Bifunctor
import Data.Either
import Data.Map (Map)
import Data.Monoid
import Reflex
import Reflex.Contrib.Utils
import Reflex.Dom
import Reflex.Dom.Contrib.Utils
------------------------------------------------------------------------------
------------------------------------------------------------------------------
-- | The hiding strategies DisplayNone and VisibilityInvisible control
-- visibility by setting the style to @display:none@ or @visibility:invisible@
-- respectively. @DisplayNone@ causes the elements to remain in the DOM but
-- be taken out of the document flow. This means that widgets in the modal
-- will only be able to get things like height when the modal is visible.
-- Using @VisibilityInvisible@ makes height information available even when
-- modal is not visible.
data HidingStrategy = DisplayNone
| VisibilityInvisible
deriving (Eq,Show,Ord,Enum,Bounded)
data ModalConfig = ModalConfig
{ modalAttributes :: Map String String
-- ^ Attributes to put on the modal's outermost div
}
------------------------------------------------------------------------------
-- | Implements a modal that stays in the DOM but is hidden with either
-- visibility:hidden or display:none when not displayed.
hidingModal
:: MonadWidget t m
=> HidingStrategy
-> ModalConfig
-> Event t Bool
-- ^ Event to open and/or close the model
-> m (a, Event t ())
-- ^ Widget rendering the body of the modal. Returns an event with a
-- success value and an event triggering the close of the modal.
-> m a
hidingModal strategy cfg showm body = do
rec let visE = leftmost [showm, False <$ closem]
(resE, closem) <- go =<< holdDyn False visE
return resE
where
go vis = do
attrs <- mapDyn (\b -> modalAttributes cfg <> visibility b) vis
elDynAttr "div" attrs body
visibility True = "style" =: "display:block;"
visibility False =
case strategy of
VisibilityInvisible -> "style" =: "visibility:hidden; display:block;"
DisplayNone -> "style" =: "display:none;"
------------------------------------------------------------------------------
-- | Implements a modal that is removed from the DOM when not displayed. This
-- involves a widgetHold and therefore this widget uses a different signature
-- than hidingModal that makes the value inside the event available to the
-- function constructing the modal.
removingModal
:: MonadWidget t m
=> ModalConfig
-> Event t a
-- ^ Event to open the model
-> (a -> m (b, Event t ()))
-- ^ Widget rendering the body of the modal. Returns an event with a
-- success value and an event triggering the close of the modal.
-> m (Dynamic t (Maybe b))
removingModal cfg showm body = do
rec let visE = leftmost [Just <$> showm, Nothing <$ closem]
(resE, closem) <- do
res <- widgetHoldHelper removeFromDOMWrapper Nothing visE
a <- mapDyn fst res
b <- extractEvent snd res
return (a,b)
return resE
where
removeFromDOMWrapper Nothing = return (Nothing, never)
removeFromDOMWrapper (Just a) =
elAttr "div" (modalAttributes cfg) $
first Just <$> body a
------------------------------------------------------------------------------
-- | Template for a modal with a header, body, and footer where the header has
-- a close icon and the footer has a cancel and save button.
mkModalBody
:: MonadWidget t m
=> m (Event t ())
-- ^ A header widget returning an event that closes the modal.
-> (Dynamic t (Either e a) -> m (Event t (), Event t ()))
-- ^ Footer widget that takes the current state of the body and returns
-- a pair of a cancel event and an ok event.
-> m (Dynamic t (Either e a))
-> m (Event t (Either e a), Event t ())
mkModalBody header footer body = do
divClass "modal-dialog" $ divClass "modal-content" $ do
dismiss <- header
bodyRes <- divClass "modal-body" body
(cancel, ok) <- footer bodyRes
let resE1 = tagDyn bodyRes ok
let closem1 = leftmost
[dismiss, cancel, () <$ ffilter isRight resE1]
return (resE1, closem1)