{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecursiveDo #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} module Reflex.Dom.Widget.Basic ( partitionMapBySetLT , listHoldWithKey , ChildResult (..) -- * Displaying Values , text , dynText , display , button , dyn , widgetHold -- * Working with Maps , diffMapNoEq , diffMap , applyMap , mapPartitionEithers , applyMapKeysSet -- * Widgets on Collections , listWithKey , listWithKey' , listWithKeyShallowDiff , listViewWithKey , selectViewListWithKey , selectViewListWithKey_ -- * Creating DOM Elements , el , elAttr , elClass , elDynAttr , elDynClass -- ** With Element Results , el' , elAttr' , elClass' , elDynAttr' , elDynClass' , elDynAttrNS' , dynamicAttributesToModifyAttributes -- * List Utils , list , simpleList -- * Specific DOM Elements , Link (..) , linkClass , link , divClass , dtdd , blank -- * Workflows , Workflow (..) , workflow , workflowView , mapWorkflow -- * Tables and Lists , tableDynAttr , tabDisplay , HasAttributes (..) ) where import Reflex.Class import Reflex.Dom.Builder.Class import Reflex.Dom.Class import Reflex.Dom.Internal.Foreign () import Reflex.Dynamic import Reflex.PostBuild.Class import Control.Arrow import Control.Lens hiding (children, element) import Control.Monad.Reader hiding (forM, forM_, mapM, mapM_, sequence, sequence_) import Data.Align import Data.Default import Data.Either import Data.Foldable import Data.Functor.Misc import Data.Map (Map) import qualified Data.Map as Map import Data.Maybe import Data.Set (Set) import qualified Data.Set as Set import Data.Text (Text) import qualified Data.Text as T import Data.These import Data.Traversable import Prelude hiding (mapM, mapM_, sequence, sequence_) widgetHoldInternal :: forall t m a b. DomBuilder t m => m a -> Event t (m b) -> m (a, Event t b) widgetHoldInternal = runWithReplace -- | Breaks the given Map into pieces based on the given Set. Each piece will contain only keys that are less than the key of the piece, and greater than or equal to the key of the piece with the next-smaller key. There will be one additional piece containing all keys from the original Map that are larger or equal to the largest key in the Set. -- Either k () is used instead of Maybe k so that the resulting map of pieces is sorted so that the additional piece has the largest key. -- No empty pieces will be included in the output. --TODO: This can probably be done more efficiently by dividing and conquering, re-using the structure of the Set instead of going through the Set linearally partitionMapBySetLT :: forall k v. Ord k => Set k -> Map k v -> Map (Either k ()) (Map k v) partitionMapBySetLT s m0 = Map.fromDistinctAscList $ go (Set.toAscList s) m0 where go :: [k] -> Map k v -> [(Either k (), Map k v)] go [] m = if Map.null m then [] else [(Right (), m)] go (h:t) m = let (lt, eq, gt) = Map.splitLookup h m geq = maybe id (Map.insert h) eq gt in if Map.null lt then go t geq else (Left h, lt) : go t geq newtype ChildResult t k a = ChildResult { unChildResult :: (a, Event t (Map k (Maybe (ChildResult t k a)))) } listHoldWithKey :: forall t m k v a. (Ord k, DomBuilder t m, MonadHold t m) => Map k v -> Event t (Map k (Maybe v)) -> (k -> v -> m a) -> m (Dynamic t (Map k a)) listHoldWithKey m0 m' f = do let dm0 = mapWithFunctorToDMap $ Map.mapWithKey f m0 dm' = fmap (PatchDMap . mapWithFunctorToDMap . Map.mapWithKey (\k v -> ComposeMaybe $ fmap (f k) v)) m' (a0, a') <- sequenceDMapWithAdjust dm0 dm' fmap dmapToMap . incrementalToDynamic <$> holdIncremental a0 a' --TODO: Move the dmapToMap to the righthand side so it doesn't get fully redone every time text :: DomBuilder t m => Text -> m () text t = void $ textNode $ def & textNodeConfig_initialContents .~ t dynText :: forall t m. (PostBuild t m, DomBuilder t m) => Dynamic t Text -> m () dynText t = do postBuild <- getPostBuild void $ textNode $ (def :: TextNodeConfig t) & textNodeConfig_setContents .~ leftmost [ updated t , tag (current t) postBuild ] display :: (PostBuild t m, DomBuilder t m, Show a) => Dynamic t a -> m () display = dynText . fmap (T.pack . show) button :: DomBuilder t m => Text -> m (Event t ()) button t = do (e, _) <- element "button" def $ text t return $ domEvent Click e --TODO: Should this be renamed to 'widgetView' for consistency with 'widgetHold'? -- | Given a Dynamic of widget-creating actions, create a widget that is recreated whenever the Dynamic updates. -- The returned Event of widget results occurs when the Dynamic does. -- Note: Often, the type 'a' is an Event, in which case the return value is an Event-of-Events that would typically be flattened (via 'switchPromptly'). dyn :: (DomBuilder t m, PostBuild t m) => Dynamic t (m a) -> m (Event t a) dyn child = do postBuild <- getPostBuild let newChild = leftmost [updated child, tag (current child) postBuild] snd <$> widgetHoldInternal (return ()) newChild -- | Given an initial widget and an Event of widget-creating actions, create a widget that is recreated whenever the Event fires. -- The returned Dynamic of widget results occurs when the Event does. -- Note: Often, the type 'a' is an Event, in which case the return value is a Dynamic-of-Events that would typically be flattened. widgetHold :: (DomBuilder t m, MonadHold t m) => m a -> Event t (m a) -> m (Dynamic t a) widgetHold child0 newChild = do (result0, newResult) <- widgetHoldInternal child0 newChild holdDyn result0 newResult diffMapNoEq :: (Ord k) => Map k v -> Map k v -> Map k (Maybe v) diffMapNoEq olds news = flip Map.mapMaybe (align olds news) $ \case This _ -> Just Nothing These _ new -> Just $ Just new That new -> Just $ Just new diffMap :: (Ord k, Eq v) => Map k v -> Map k v -> Map k (Maybe v) diffMap olds news = flip Map.mapMaybe (align olds news) $ \case This _ -> Just Nothing These old new | old == new -> Nothing | otherwise -> Just $ Just new That new -> Just $ Just new applyMap :: Ord k => Map k (Maybe v) -> Map k v -> Map k v applyMap patch old = insertions `Map.union` (old `Map.difference` deletions) where (deletions, insertions) = mapPartitionEithers $ maybeToEither <$> patch maybeToEither = \case Nothing -> Left () Just r -> Right r mapPartitionEithers :: Map k (Either a b) -> (Map k a, Map k b) mapPartitionEithers m = (fromLeft <$> ls, fromRight <$> rs) where (ls, rs) = Map.partition isLeft m fromLeft (Left l) = l fromLeft _ = error "mapPartitionEithers: fromLeft received a Right value; this should be impossible" fromRight (Right r) = r fromRight _ = error "mapPartitionEithers: fromRight received a Left value; this should be impossible" -- | Apply a map patch to a set -- > applyMapKeysSet patch (Map.keysSet m) == Map.keysSet (applyMap patch m) applyMapKeysSet :: Ord k => Map k (Maybe v) -> Set k -> Set k applyMapKeysSet patch old = Map.keysSet insertions `Set.union` (old `Set.difference` Map.keysSet deletions) where (insertions, deletions) = Map.partition isJust patch --TODO: Something better than Dynamic t (Map k v) - we want something where the Events carry diffs, not the whole value listWithKey :: forall t k v m a. (Ord k, DomBuilder t m, PostBuild t m, MonadFix m, MonadHold t m) => Dynamic t (Map k v) -> (k -> Dynamic t v -> m a) -> m (Dynamic t (Map k a)) listWithKey vals mkChild = do postBuild <- getPostBuild let childValChangedSelector = fanMap $ updated vals rec sentVals :: Dynamic t (Map k v) <- foldDyn applyMap Map.empty changeVals let changeVals :: Event t (Map k (Maybe v)) changeVals = attachWith diffMapNoEq (current sentVals) $ leftmost [ updated vals , tag (current vals) postBuild --TODO: This should probably be added to the attachWith, not to the updated; if we were using diffMap instead of diffMapNoEq, I think it might not work ] listHoldWithKey Map.empty changeVals $ \k v -> mkChild k =<< holdDyn v (select childValChangedSelector $ Const2 k) {-# DEPRECATED listWithKey' "listWithKey' has been renamed to listWithKeyShallowDiff; also, its behavior has changed to fix a bug where children were always rebuilt (never updated)" #-} listWithKey' :: (Ord k, DomBuilder t m, MonadFix m, MonadHold t m) => Map k v -> Event t (Map k (Maybe v)) -> (k -> v -> Event t v -> m a) -> m (Dynamic t (Map k a)) listWithKey' = listWithKeyShallowDiff -- | Display the given map of items (in key order) using the builder function provided, and update it with the given event. 'Nothing' update entries will delete the corresponding children, and 'Just' entries will create them if they do not exist or send an update event to them if they do. listWithKeyShallowDiff :: (Ord k, DomBuilder t m, MonadFix m, MonadHold t m) => Map k v -> Event t (Map k (Maybe v)) -> (k -> v -> Event t v -> m a) -> m (Dynamic t (Map k a)) listWithKeyShallowDiff initialVals valsChanged mkChild = do let childValChangedSelector = fanMap $ fmap (Map.mapMaybe id) valsChanged sentVals <- foldDyn applyMap Map.empty $ fmap (fmap void) valsChanged let relevantPatch patch _ = case patch of Nothing -> Just Nothing -- Even if we let a Nothing through when the element doesn't already exist, this doesn't cause a problem because it is ignored Just _ -> Nothing -- We don't want to let spurious re-creations of items through listHoldWithKey initialVals (attachWith (flip (Map.differenceWith relevantPatch)) (current sentVals) valsChanged) $ \k v -> mkChild k v $ select childValChangedSelector $ Const2 k --TODO: Something better than Dynamic t (Map k v) - we want something where the Events carry diffs, not the whole value -- | Create a dynamically-changing set of Event-valued widgets. -- This is like listWithKey, specialized for widgets returning (Event t a). listWithKey would return 'Dynamic t (Map k (Event t a))' in this scenario, but listViewWithKey flattens this to 'Event t (Map k a)' via 'switch'. listViewWithKey :: (Ord k, DomBuilder t m, PostBuild t m, MonadHold t m, MonadFix m) => Dynamic t (Map k v) -> (k -> Dynamic t v -> m (Event t a)) -> m (Event t (Map k a)) listViewWithKey vals mkChild = switch . fmap mergeMap <$> listViewWithKey' vals mkChild listViewWithKey' :: (Ord k, DomBuilder t m, PostBuild t m, MonadHold t m, MonadFix m) => Dynamic t (Map k v) -> (k -> Dynamic t v -> m a) -> m (Behavior t (Map k a)) listViewWithKey' vals mkChild = current <$> listWithKey vals mkChild -- | Create a dynamically-changing set of widgets, one of which is selected at any time. selectViewListWithKey :: forall t m k v a. (DomBuilder t m, Ord k, PostBuild t m, MonadHold t m, MonadFix m) => Dynamic t k -- ^ Current selection key -> Dynamic t (Map k v) -- ^ Dynamic key/value map -> (k -> Dynamic t v -> Dynamic t Bool -> m (Event t a)) -- ^ Function to create a widget for a given key from Dynamic value and Dynamic Bool indicating if this widget is currently selected -> m (Event t (k, a)) -- ^ Event that fires when any child's return Event fires. Contains key of an arbitrary firing widget. selectViewListWithKey selection vals mkChild = do let selectionDemux = demux selection -- For good performance, this value must be shared across all children selectChild <- listWithKey vals $ \k v -> do let selected = demuxed selectionDemux k selectSelf <- mkChild k v selected return $ fmap ((,) k) selectSelf return $ switchPromptlyDyn $ leftmost . Map.elems <$> selectChild selectViewListWithKey_ :: forall t m k v a. (DomBuilder t m, Ord k, PostBuild t m, MonadHold t m, MonadFix m) => Dynamic t k -- ^ Current selection key -> Dynamic t (Map k v) -- ^ Dynamic key/value map -> (k -> Dynamic t v -> Dynamic t Bool -> m (Event t a)) -- ^ Function to create a widget for a given key from Dynamic value and Dynamic Bool indicating if this widget is currently selected -> m (Event t k) -- ^ Event that fires when any child's return Event fires. Contains key of an arbitrary firing widget. selectViewListWithKey_ selection vals mkChild = fmap fst <$> selectViewListWithKey selection vals mkChild -- | Create a DOM element -- > el "div" (text "Hello World") --