{-# LANGUAGE ScopedTypeVariables, LambdaCase, ConstraintKinds, TypeFamilies, FlexibleContexts, MultiParamTypeClasses, FlexibleInstances, RecursiveDo, GADTs, DataKinds, RankNTypes, TemplateHaskell #-} module Reflex.Dom.Widget.Basic where import Reflex.Dom.Class import Reflex.Dom.Internal.Foreign () import Prelude hiding (mapM, mapM_, sequence, sequence_) import Reflex import Reflex.Host.Class import Data.Functor.Misc import Data.Map (Map) import qualified Data.Map as Map import Data.Set (Set) import qualified Data.Set as Set import Data.Dependent.Sum (DSum (..)) import Data.Foldable import Data.Traversable import Control.Monad.Trans import Control.Monad.Reader hiding (mapM, mapM_, forM, forM_, sequence, sequence_) import Control.Monad.State hiding (state, mapM, mapM_, forM, forM_, sequence, sequence_) import GHCJS.DOM.Node import GHCJS.DOM.UIEvent import GHCJS.DOM.EventM (event, EventM) import GHCJS.DOM.Document import GHCJS.DOM.Element import GHCJS.DOM.HTMLElement import GHCJS.DOM.Types hiding (Widget (..), unWidget, Event) import GHCJS.DOM.NamedNodeMap import Control.Lens hiding (element, children) import Data.These import Data.Align import Data.Maybe import Data.GADT.Compare.TH import Data.Bitraversable import GHCJS.DOM.MouseEvent type AttributeMap = Map String String data El t = El { _el_element :: HTMLElement , _el_events :: EventSelector t (WrapArg EventResult EventName) } class Attributes m a where addAttributes :: IsElement e => a -> e -> m () instance MonadIO m => Attributes m AttributeMap where addAttributes curAttrs e = liftIO $ imapM_ (elementSetAttribute e) curAttrs instance MonadWidget t m => Attributes m (Dynamic t AttributeMap) where addAttributes attrs e = do schedulePostBuild $ do curAttrs <- sample $ current attrs liftIO $ imapM_ (elementSetAttribute e) curAttrs addVoidAction $ flip fmap (updated attrs) $ \newAttrs -> liftIO $ do oldAttrs <- maybe (return Set.empty) namedNodeMapGetNames =<< elementGetAttributes e forM_ (Set.toList $ oldAttrs `Set.difference` Map.keysSet newAttrs) $ elementRemoveAttribute e imapM_ (elementSetAttribute e) newAttrs --TODO: avoid re-setting unchanged attributes; possibly do the compare using Align in haskell buildEmptyElement :: (MonadWidget t m, Attributes m attrs) => String -> attrs -> m HTMLElement buildEmptyElement elementTag attrs = do doc <- askDocument p <- askParent Just e <- liftIO $ documentCreateElement doc elementTag addAttributes attrs e _ <- liftIO $ nodeAppendChild p $ Just e return $ castToHTMLElement e -- We need to decide what type of attrs we've got statically, because it will often be a recursively defined value, in which case inspecting it will lead to a cycle buildElement :: (MonadWidget t m, Attributes m attrs) => String -> attrs -> m a -> m (HTMLElement, a) buildElement elementTag attrs child = do e <- buildEmptyElement elementTag attrs result <- subWidget (toNode e) child return (e, result) namedNodeMapGetNames :: IsNamedNodeMap self => self -> IO (Set String) namedNodeMapGetNames self = do l <- namedNodeMapGetLength self let locations = if l == 0 then [] else [0..l-1] -- Can't use 0..l-1 if l is 0 because l is unsigned and will wrap around liftM Set.fromList $ forM locations $ \i -> do Just n <- namedNodeMapItem self i nodeGetNodeName n text :: MonadWidget t m => String -> m () text = void . text' --TODO: Wrap the result text' :: MonadWidget t m => String -> m Text text' s = do doc <- askDocument p <- askParent Just n <- liftIO $ documentCreateTextNode doc s _ <- liftIO $ nodeAppendChild p $ Just n return n dynText :: MonadWidget t m => Dynamic t String -> m () dynText s = do n <- text' "" schedulePostBuild $ do curS <- sample $ current s liftIO $ nodeSetNodeValue n curS addVoidAction $ fmap (liftIO . nodeSetNodeValue n) $ updated s display :: (MonadWidget t m, Show a) => Dynamic t a -> m () display a = dynText =<< mapDyn show a --TODO: Should this be renamed to 'widgetView' for consistency with 'widgetHold'? dyn :: MonadWidget t m => Dynamic t (m a) -> m (Event t a) dyn child = do startPlaceholder <- text' "" endPlaceholder <- text' "" (newChildBuilt, newChildBuiltTriggerRef) <- newEventWithTriggerRef let e = fmap snd newChildBuilt --TODO: Get rid of this hack childVoidAction <- hold never e performEvent_ $ fmap (const $ return ()) e --TODO: Get rid of this hack addVoidAction $ switch childVoidAction doc <- askDocument runWidget <- getRunWidget let build c = do Just df <- liftIO $ documentCreateDocumentFragment doc (result, postBuild, voidActions) <- runWidget df c runFrameWithTriggerRef newChildBuiltTriggerRef (result, voidActions) postBuild Just p <- liftIO $ nodeGetParentNode endPlaceholder _ <- liftIO $ nodeInsertBefore p (Just df) (Just endPlaceholder) return () schedulePostBuild $ do c <- sample $ current child build c addVoidAction $ ffor (updated child) $ \newChild -> do liftIO $ deleteBetweenExclusive startPlaceholder endPlaceholder build newChild return $ fmap fst newChildBuilt widgetHold :: MonadWidget t m => m a -> Event t (m a) -> m (Dynamic t a) widgetHold child0 newChild = do startPlaceholder <- text' "" result0 <- child0 -- I'm pretty sure this is wrong; the void actions should get removed when the child is swapped out endPlaceholder <- text' "" (newChildBuilt, newChildBuiltTriggerRef) <- newEventWithTriggerRef performEvent_ $ fmap (const $ return ()) newChildBuilt --TODO: Get rid of this hack childVoidAction <- hold never $ fmap snd newChildBuilt addVoidAction $ switch childVoidAction --TODO: Should this be a switchPromptly? doc <- askDocument runWidget <- getRunWidget let build c = do Just df <- liftIO $ documentCreateDocumentFragment doc (result, postBuild, voidActions) <- runWidget df c runFrameWithTriggerRef newChildBuiltTriggerRef (result, voidActions) postBuild mp <- liftIO $ nodeGetParentNode endPlaceholder case mp of Nothing -> return () --TODO: Is this right? Just p -> do _ <- liftIO $ nodeInsertBefore p (Just df) (Just endPlaceholder) return () return () addVoidAction $ ffor newChild $ \c -> do liftIO $ deleteBetweenExclusive startPlaceholder endPlaceholder build c holdDyn result0 $ fmap fst newChildBuilt --TODO: Something better than Dynamic t (Map k v) - we want something where the Events carry diffs, not the whole value listWithKey :: (Ord k, MonadWidget t m) => Dynamic t (Map k v) -> (k -> Dynamic t v -> m a) -> m (Dynamic t (Map k a)) listWithKey vals mkChild = do doc <- askDocument endPlaceholder <- text' "" (newChildren, newChildrenTriggerRef) <- newEventWithTriggerRef performEvent_ $ fmap (const $ return ()) newChildren --TODO: Get rid of this hack children <- hold Map.empty newChildren addVoidAction $ switch $ fmap (mergeWith (>>) . map snd . Map.elems) children runWidget <- getRunWidget let buildChild df k v = runWidget df $ do childStart <- text' "" result <- mkChild k =<< holdDyn v (fmapMaybe (Map.lookup k) (updated vals)) childEnd <- text' "" return (result, (childStart, childEnd)) schedulePostBuild $ do Just df <- liftIO $ documentCreateDocumentFragment doc curVals <- sample $ current vals initialState <- iforM curVals $ \k v -> do (result, postBuild, voidAction) <- buildChild df k v return ((result, voidAction), postBuild) runFrameWithTriggerRef newChildrenTriggerRef $ fmap fst initialState --TODO: Do all these in a single runFrame sequence_ $ fmap snd initialState Just p <- liftIO $ nodeGetParentNode endPlaceholder _ <- liftIO $ nodeInsertBefore p (Just df) (Just endPlaceholder) return () addVoidAction $ flip fmap (updated vals) $ \newVals -> do curState <- sample children --TODO: Should we remove the parent from the DOM first to avoid reflows? (newState, postBuild) <- flip runStateT (return ()) $ liftM (Map.mapMaybe id) $ iforM (align curState newVals) $ \k -> \case This ((_, (start, end)), _) -> do liftIO $ deleteBetweenInclusive start end return Nothing That v -> do Just df <- liftIO $ documentCreateDocumentFragment doc (childResult, childPostBuild, childVoidAction) <- lift $ buildChild df k v let s = (childResult, childVoidAction) modify (>>childPostBuild) let placeholder = case Map.lookupGT k curState of Nothing -> endPlaceholder Just (_, ((_, (start, _)), _)) -> start Just p <- liftIO $ nodeGetParentNode placeholder _ <- liftIO $ nodeInsertBefore p (Just df) (Just placeholder) return $ Just s These state _ -> do return $ Just state runFrameWithTriggerRef newChildrenTriggerRef newState postBuild holdDyn Map.empty $ fmap (fmap (fst . fst)) newChildren listWithKey' :: forall t m k v a. (Ord k, MonadWidget 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' initialVals valsChanged mkChild = do doc <- askDocument endPlaceholder <- text' "" (newChildren, newChildrenTriggerRef) <- newEventWithTriggerRef -- performEvent_ $ fmap (const $ return ()) newChildren --TODO: Get rid of this hack runWidget <- getRunWidget let childValChangedSelector :: EventSelector t (Const2 k v) childValChangedSelector = fanMap $ fmap (Map.mapMaybe id) valsChanged buildChild df k v = runWidget df $ wrapChild k v wrapChild k v = do childStart <- text' "" result <- mkChild k v $ select childValChangedSelector $ Const2 k childEnd <- text' "" return (result, (childStart, childEnd)) Just dfOrig <- liftIO $ documentCreateDocumentFragment doc initialState <- iforM initialVals $ \k v -> subWidgetWithVoidActions (toNode dfOrig) $ wrapChild k v --Note: we have to use subWidgetWithVoidActions rather than runWidget here, because running post-build actions during build can cause not-yet-constructed values to be read children <- holdDyn initialState newChildren addVoidAction $ switch $ fmap (mergeWith (>>) . map snd . Map.elems) $ current children Just pOrig <- liftIO $ nodeGetParentNode endPlaceholder _ <- liftIO $ nodeInsertBefore pOrig (Just dfOrig) (Just endPlaceholder) addVoidAction $ flip fmap valsChanged $ \newVals -> do curState <- sample $ current children --TODO: Should we remove the parent from the DOM first to avoid reflows? (newState, postBuild) <- flip runStateT (return ()) $ liftM (Map.mapMaybe id) $ iforM (align curState newVals) $ \k -> \case These ((_, (start, end)), _) Nothing -> do -- Deleting child liftIO $ deleteBetweenInclusive start end return Nothing These ((_, (start, end)), _) (Just v) -> do -- Replacing existing child liftIO $ deleteBetweenExclusive start end Just df <- liftIO $ documentCreateDocumentFragment doc (childResult, childPostBuild, childVoidAction) <- lift $ buildChild df k v let s = (childResult, childVoidAction) modify (>>childPostBuild) Just p <- liftIO $ nodeGetParentNode end _ <- liftIO $ nodeInsertBefore p (Just df) (Just end) return $ Just s That Nothing -> return Nothing -- Deleting non-existent child That (Just v) -> do -- Creating new child Just df <- liftIO $ documentCreateDocumentFragment doc (childResult, childPostBuild, childVoidAction) <- lift $ buildChild df k v let s = (childResult, childVoidAction) modify (>>childPostBuild) let placeholder = case Map.lookupGT k curState of Nothing -> endPlaceholder Just (_, ((_, (start, _)), _)) -> start Just p <- liftIO $ nodeGetParentNode placeholder _ <- liftIO $ nodeInsertBefore p (Just df) (Just placeholder) return $ Just s This state -> do -- No change return $ Just state runFrameWithTriggerRef newChildrenTriggerRef newState postBuild mapDyn (fmap (fst . fst)) children --TODO: Something better than Dynamic t (Map k v) - we want something where the Events carry diffs, not the whole value listViewWithKey :: (Ord k, MonadWidget t m) => Dynamic t (Map k v) -> (k -> Dynamic t v -> m (Event t a)) -> m (Event t (Map k a)) listViewWithKey vals mkChild = liftM (switch . fmap mergeMap) $ listViewWithKey' vals mkChild listViewWithKey' :: (Ord k, MonadWidget t m) => Dynamic t (Map k v) -> (k -> Dynamic t v -> m a) -> m (Behavior t (Map k a)) listViewWithKey' vals mkChild = do doc <- askDocument endPlaceholder <- text' "" (newChildren, newChildrenTriggerRef) <- newEventWithTriggerRef performEvent_ $ fmap (const $ return ()) newChildren --TODO: Get rid of this hack children <- hold Map.empty newChildren addVoidAction $ switch $ fmap (mergeWith (>>) . map snd . Map.elems) children runWidget <- getRunWidget let buildChild df k v = runWidget df $ do childStart <- text' "" result <- mkChild k =<< holdDyn v (fmapMaybe (Map.lookup k) (updated vals)) childEnd <- text' "" return (result, (childStart, childEnd)) schedulePostBuild $ do Just df <- liftIO $ documentCreateDocumentFragment doc curVals <- sample $ current vals initialState <- iforM curVals $ \k v -> do (result, postBuild, voidAction) <- buildChild df k v return ((result, voidAction), postBuild) runFrameWithTriggerRef newChildrenTriggerRef $ fmap fst initialState --TODO: Do all these in a single runFrame sequence_ $ fmap snd initialState Just p <- liftIO $ nodeGetParentNode endPlaceholder _ <- liftIO $ nodeInsertBefore p (Just df) (Just endPlaceholder) return () addVoidAction $ flip fmap (updated vals) $ \newVals -> do curState <- sample children --TODO: Should we remove the parent from the DOM first to avoid reflows? (newState, postBuild) <- flip runStateT (return ()) $ liftM (Map.mapMaybe id) $ iforM (align curState newVals) $ \k -> \case This ((_, (start, end)), _) -> do liftIO $ deleteBetweenInclusive start end return Nothing That v -> do Just df <- liftIO $ documentCreateDocumentFragment doc (childResult, childPostBuild, childVoidAction) <- lift $ buildChild df k v let s = (childResult, childVoidAction) modify (>>childPostBuild) let placeholder = case Map.lookupGT k curState of Nothing -> endPlaceholder Just (_, ((_, (start, _)), _)) -> start Just p <- liftIO $ nodeGetParentNode placeholder _ <- liftIO $ nodeInsertBefore p (Just df) (Just placeholder) return $ Just s These state _ -> do return $ Just state runFrameWithTriggerRef newChildrenTriggerRef newState postBuild return $ fmap (fmap (fst . fst)) children --TODO: Deduplicate the various list*WithKey* implementations selectViewListWithKey_ :: forall t m k v a. (MonadWidget t m, Ord k) => Dynamic t k -> Dynamic t (Map k v) -> (k -> Dynamic t v -> Dynamic t Bool -> m (Event t a)) -> m (Event t k) 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 selected <- getDemuxed selectionDemux k selectSelf <- mkChild k v selected return $ fmap (const k) selectSelf liftM switchPromptlyDyn $ mapDyn (leftmost . Map.elems) selectChild -------------------------------------------------------------------------------- -- Basic DOM manipulation helpers -------------------------------------------------------------------------------- -- | s and e must both be children of the same node and s must precede e deleteBetweenExclusive :: (IsNode start, IsNode end) => start -> end -> IO () deleteBetweenExclusive s e = do mCurrentParent <- nodeGetParentNode e -- May be different than it was at initial construction, e.g., because the parent may have dumped us in from a DocumentFragment case mCurrentParent of Nothing -> return () --TODO: Is this the right behavior? Just currentParent -> do let go = do Just x <- nodeGetPreviousSibling e -- This can't be Nothing because we should hit 's' first when (toNode s /= toNode x) $ do _ <- nodeRemoveChild currentParent $ Just x go go -- | s and e must both be children of the same node and s must precede e deleteBetweenInclusive :: (IsNode start, IsNode end) => start -> end -> IO () deleteBetweenInclusive s e = do mCurrentParent <- nodeGetParentNode e -- May be different than it was at initial construction, e.g., because the parent may have dumped us in from a DocumentFragment case mCurrentParent of Nothing -> return () --TODO: Is this the right behavior? Just currentParent -> do let go = do Just x <- nodeGetPreviousSibling e -- This can't be Nothing because we should hit 's' first _ <- nodeRemoveChild currentParent $ Just x when (toNode s /= toNode x) go go _ <- nodeRemoveChild currentParent $ Just e return () -------------------------------------------------------------------------------- -- Adapters -------------------------------------------------------------------------------- wrapDomEvent :: (Functor (Event t), MonadIO m, MonadSample t m, MonadReflexCreateTrigger t m, Reflex t, HasPostGui t h m) => e -> (e -> EventM event e () -> IO (IO ())) -> EventM event e a -> m (Event t a) wrapDomEvent element elementOnevent getValue = wrapDomEventMaybe element elementOnevent $ liftM Just getValue wrapDomEventMaybe :: (Functor (Event t), MonadIO m, MonadSample t m, MonadReflexCreateTrigger t m, Reflex t, HasPostGui t h m) => e -> (e -> EventM event e () -> IO (IO ())) -> EventM event e (Maybe a) -> m (Event t a) wrapDomEventMaybe element elementOnevent getValue = do postGui <- askPostGui runWithActions <- askRunWithActions e <- newEventWithTrigger $ \et -> do unsubscribe <- {-# SCC "a" #-} liftIO $ {-# SCC "b" #-} elementOnevent element $ {-# SCC "c" #-} do mv <- {-# SCC "d" #-} getValue forM_ mv $ \v -> liftIO $ postGui $ runWithActions [et :=> v] return $ liftIO $ do {-# SCC "e" #-} unsubscribe return $! {-# SCC "f" #-} e data EventTag = AbortTag | BlurTag | ChangeTag | ClickTag | ContextmenuTag | DblclickTag | DragTag | DragendTag | DragenterTag | DragleaveTag | DragoverTag | DragstartTag | DropTag | ErrorTag | FocusTag | InputTag | InvalidTag | KeydownTag | KeypressTag | KeyupTag | LoadTag | MousedownTag | MouseenterTag | MouseleaveTag | MousemoveTag | MouseoutTag | MouseoverTag | MouseupTag -- | MousewheelTag -- webkitgtk only provides elementOnmousewheel (not elementOnwheel), but firefox does not support the mousewheel event; we should provide wheel (the equivalent, standard event), but we will need to make sure webkitgtk supports it first | ScrollTag | SelectTag | SubmitTag -- | WheelTag -- See MousewheelTag | BeforecutTag | CutTag | BeforecopyTag | CopyTag | BeforepasteTag | PasteTag | ResetTag | SearchTag | SelectstartTag | TouchstartTag | TouchmoveTag | TouchendTag | TouchcancelTag data EventName :: EventTag -> * where Abort :: EventName 'AbortTag Blur :: EventName 'BlurTag Change :: EventName 'ChangeTag Click :: EventName 'ClickTag Contextmenu :: EventName 'ContextmenuTag Dblclick :: EventName 'DblclickTag Drag :: EventName 'DragTag Dragend :: EventName 'DragendTag Dragenter :: EventName 'DragenterTag Dragleave :: EventName 'DragleaveTag Dragover :: EventName 'DragoverTag Dragstart :: EventName 'DragstartTag Drop :: EventName 'DropTag Error :: EventName 'ErrorTag Focus :: EventName 'FocusTag Input :: EventName 'InputTag Invalid :: EventName 'InvalidTag Keydown :: EventName 'KeydownTag Keypress :: EventName 'KeypressTag Keyup :: EventName 'KeyupTag Load :: EventName 'LoadTag Mousedown :: EventName 'MousedownTag Mouseenter :: EventName 'MouseenterTag Mouseleave :: EventName 'MouseleaveTag Mousemove :: EventName 'MousemoveTag Mouseout :: EventName 'MouseoutTag Mouseover :: EventName 'MouseoverTag Mouseup :: EventName 'MouseupTag --Mousewheel :: EventName 'MousewheelTag Scroll :: EventName 'ScrollTag Select :: EventName 'SelectTag Submit :: EventName 'SubmitTag --Wheel :: EventName 'WheelTag Beforecut :: EventName 'BeforecutTag Cut :: EventName 'CutTag Beforecopy :: EventName 'BeforecopyTag Copy :: EventName 'CopyTag Beforepaste :: EventName 'BeforepasteTag Paste :: EventName 'PasteTag Reset :: EventName 'ResetTag Search :: EventName 'SearchTag Selectstart :: EventName 'SelectstartTag Touchstart :: EventName 'TouchstartTag Touchmove :: EventName 'TouchmoveTag Touchend :: EventName 'TouchendTag Touchcancel :: EventName 'TouchcancelTag type family EventType en where EventType 'AbortTag = UIEvent EventType 'BlurTag = UIEvent EventType 'ChangeTag = UIEvent EventType 'ClickTag = MouseEvent EventType 'ContextmenuTag = MouseEvent EventType 'DblclickTag = MouseEvent EventType 'DragTag = MouseEvent EventType 'DragendTag = MouseEvent EventType 'DragenterTag = MouseEvent EventType 'DragleaveTag = MouseEvent EventType 'DragoverTag = MouseEvent EventType 'DragstartTag = MouseEvent EventType 'DropTag = MouseEvent EventType 'ErrorTag = UIEvent EventType 'FocusTag = UIEvent EventType 'InputTag = UIEvent EventType 'InvalidTag = UIEvent EventType 'KeydownTag = UIEvent EventType 'KeypressTag = UIEvent EventType 'KeyupTag = UIEvent EventType 'LoadTag = UIEvent EventType 'MousedownTag = MouseEvent EventType 'MouseenterTag = UIEvent EventType 'MouseleaveTag = UIEvent EventType 'MousemoveTag = MouseEvent EventType 'MouseoutTag = MouseEvent EventType 'MouseoverTag = MouseEvent EventType 'MouseupTag = MouseEvent --EventType 'MousewheelTag = MouseEvent EventType 'ScrollTag = UIEvent EventType 'SelectTag = UIEvent EventType 'SubmitTag = UIEvent --EventType 'WheelTag = UIEvent EventType 'BeforecutTag = UIEvent EventType 'CutTag = UIEvent EventType 'BeforecopyTag = UIEvent EventType 'CopyTag = UIEvent EventType 'BeforepasteTag = UIEvent EventType 'PasteTag = UIEvent EventType 'ResetTag = UIEvent EventType 'SearchTag = UIEvent EventType 'SelectstartTag = UIEvent EventType 'TouchstartTag = UIEvent EventType 'TouchmoveTag = UIEvent EventType 'TouchendTag = UIEvent EventType 'TouchcancelTag = UIEvent onEventName :: IsElement e => EventName en -> e -> EventM (EventType en) e () -> IO (IO ()) onEventName en = case en of Abort -> elementOnabort Blur -> elementOnblur Change -> elementOnchange Click -> elementOnclick Contextmenu -> elementOncontextmenu Dblclick -> elementOndblclick Drag -> elementOndrag Dragend -> elementOndragend Dragenter -> elementOndragenter Dragleave -> elementOndragleave Dragover -> elementOndragover Dragstart -> elementOndragstart Drop -> elementOndrop Error -> elementOnerror Focus -> elementOnfocus Input -> elementOninput Invalid -> elementOninvalid Keydown -> elementOnkeydown Keypress -> elementOnkeypress Keyup -> elementOnkeyup Load -> elementOnload Mousedown -> elementOnmousedown Mouseenter -> elementOnmouseenter Mouseleave -> elementOnmouseleave Mousemove -> elementOnmousemove Mouseout -> elementOnmouseout Mouseover -> elementOnmouseover Mouseup -> elementOnmouseup --Mousewheel -> elementOnmousewheel Scroll -> elementOnscroll Select -> elementOnselect Submit -> elementOnsubmit --Wheel -> elementOnwheel Beforecut -> elementOnbeforecut Cut -> elementOncut Beforecopy -> elementOnbeforecopy Copy -> elementOncopy Beforepaste -> elementOnbeforepaste Paste -> elementOnpaste Reset -> elementOnreset Search -> elementOnsearch Selectstart -> elementOnselectstart Touchstart -> elementOntouchstart Touchmove -> elementOntouchmove Touchend -> elementOntouchend Touchcancel -> elementOntouchcancel newtype EventResult en = EventResult { unEventResult :: EventResultType en } type family EventResultType (en :: EventTag) :: * where EventResultType 'ClickTag = () EventResultType 'DblclickTag = () EventResultType 'KeypressTag = Int EventResultType 'ScrollTag = Int EventResultType 'MousemoveTag = (Int, Int) EventResultType 'MousedownTag = (Int, Int) EventResultType 'MouseupTag = (Int, Int) EventResultType 'MouseenterTag = () EventResultType 'MouseleaveTag = () EventResultType 'FocusTag = () EventResultType 'BlurTag = () wrapDomEventsMaybe :: (Functor (Event t), IsElement e, MonadIO m, MonadSample t m, MonadReflexCreateTrigger t m, Reflex t, HasPostGui t h m) => e -> (forall en. EventName en -> EventM (EventType en) e (Maybe (f en))) -> m (EventSelector t (WrapArg f EventName)) wrapDomEventsMaybe element handlers = do postGui <- askPostGui runWithActions <- askRunWithActions e <- newFanEventWithTrigger $ \(WrapArg en) et -> do unsubscribe <- liftIO $ (onEventName en) element $ do mv <- handlers en forM_ mv $ \v -> liftIO $ postGui $ runWithActions [et :=> v] return $ liftIO $ do unsubscribe return $! e getKeyEvent :: EventM UIEvent e Int getKeyEvent = do e <- event liftIO $ do which <- uiEventGetWhich e if which /= 0 then return which else do charCode <- uiEventGetCharCode e if charCode /= 0 then return charCode else uiEventGetKeyCode e getMouseEventCoords :: EventM MouseEvent e (Int, Int) getMouseEventCoords = do e <- event liftIO $ bisequence (mouseEventGetX e, mouseEventGetY e) defaultDomEventHandler :: IsElement e => e -> EventName en -> EventM (EventType en) e (Maybe (EventResult en)) defaultDomEventHandler e evt = liftM (Just . EventResult) $ case evt of Click -> return () Dblclick -> return () Keypress -> getKeyEvent Scroll -> liftIO $ elementGetScrollTop e Mousemove -> getMouseEventCoords Mouseup -> getMouseEventCoords Mousedown -> getMouseEventCoords Mouseenter -> return () Mouseleave -> return () Focus -> return () Blur -> return () wrapElement :: forall t h m. (Functor (Event t), MonadIO m, MonadSample t m, MonadReflexCreateTrigger t m, Reflex t, HasPostGui t h m) => HTMLElement -> m (El t) wrapElement e = do es <- wrapDomEventsMaybe e $ defaultDomEventHandler e return $ El e es elDynAttr' :: forall t m a. MonadWidget t m => String -> Dynamic t (Map String String) -> m a -> m (El t, a) elDynAttr' elementTag attrs child = do (e, result) <- buildElement elementTag attrs child e' <- wrapElement e return (e', result) {-# INLINABLE elAttr #-} elAttr :: forall t m a. MonadWidget t m => String -> Map String String -> m a -> m a elAttr elementTag attrs child = do (_, result) <- buildElement elementTag attrs child return result {-# INLINABLE el' #-} el' :: forall t m a. MonadWidget t m => String -> m a -> m (El t, a) el' elementTag child = elAttr' elementTag (Map.empty :: AttributeMap) child {-# INLINABLE elAttr' #-} elAttr' :: forall t m a. MonadWidget t m => String -> Map String String -> m a -> m (El t, a) elAttr' elementTag attrs child = do (e, result) <- buildElement elementTag attrs child e' <- wrapElement e return (e', result) {-# INLINABLE elDynAttr #-} elDynAttr :: forall t m a. MonadWidget t m => String -> Dynamic t (Map String String) -> m a -> m a elDynAttr elementTag attrs child = do (_, result) <- buildElement elementTag attrs child return result {-# INLINABLE el #-} el :: forall t m a. MonadWidget t m => String -> m a -> m a el elementTag child = elAttr elementTag Map.empty child elClass :: forall t m a. MonadWidget t m => String -> String -> m a -> m a elClass elementTag c child = elAttr elementTag ("class" =: c) child -------------------------------------------------------------------------------- -- Copied and pasted from Reflex.Widget.Class -------------------------------------------------------------------------------- list :: (MonadWidget t m, Ord k) => Dynamic t (Map k v) -> (Dynamic t v -> m a) -> m (Dynamic t (Map k a)) list dm mkChild = listWithKey dm (\_ dv -> mkChild dv) simpleList :: MonadWidget t m => Dynamic t [v] -> (Dynamic t v -> m a) -> m (Dynamic t [a]) simpleList xs mkChild = mapDyn (map snd . Map.toList) =<< flip list mkChild =<< mapDyn (Map.fromList . zip [(1::Int)..]) xs elDynHtml' :: MonadWidget t m => String -> Dynamic t String -> m (El t) elDynHtml' elementTag html = do e <- buildEmptyElement elementTag (Map.empty :: Map String String) schedulePostBuild $ liftIO . htmlElementSetInnerHTML e =<< sample (current html) addVoidAction $ fmap (liftIO . htmlElementSetInnerHTML e) $ updated html wrapElement e elDynHtmlAttr' :: MonadWidget t m => String -> Map String String -> Dynamic t String -> m (El t) elDynHtmlAttr' elementTag attrs html = do e <- buildEmptyElement elementTag attrs schedulePostBuild $ liftIO . htmlElementSetInnerHTML e =<< sample (current html) addVoidAction $ fmap (liftIO . htmlElementSetInnerHTML e) $ updated html wrapElement e {- --TODO: Update dynamically {-# INLINABLE dynHtml #-} dynHtml :: MonadWidget t m => Dynamic t String -> m () dynHtml ds = do let mkSelf h = do doc <- askDocument Just e <- liftIO $ liftM (fmap castToHTMLElement) $ documentCreateElement doc "div" liftIO $ htmlElementSetInnerHTML e h return e eCreated <- performEvent . fmap mkSelf . tagDyn ds =<< getEInit putEChildren $ fmap ((:[]) . toNode) eCreated -} data Link t = Link { _link_clicked :: Event t () } class HasDomEvent t a where domEvent :: EventName en -> a -> Event t (EventResultType en) instance Reflex t => HasDomEvent t (El t) where domEvent en el = fmap unEventResult $ select (_el_events el) (WrapArg en) linkClass :: MonadWidget t m => String -> String -> m (Link t) linkClass s c = do (l,_) <- elAttr' "a" ("class" =: c) $ text s return $ Link $ domEvent Click l link :: MonadWidget t m => String -> m (Link t) link s = linkClass s "" button :: MonadWidget t m => String -> m (Event t ()) button s = do (e, _) <- el' "button" $ text s return $ domEvent Click e newtype Workflow t m a = Workflow { unWorkflow :: m (a, Event t (Workflow t m a)) } workflow :: forall t m a. MonadWidget t m => Workflow t m a -> m (Dynamic t a) workflow w0 = do rec eResult <- widgetHold (unWorkflow w0) $ fmap unWorkflow $ switch $ fmap snd $ current eResult mapDyn fst eResult workflowView :: forall t m a. MonadWidget t m => Workflow t m a -> m (Event t a) workflowView w0 = do rec eResult <- dyn =<< mapDyn unWorkflow =<< holdDyn w0 eReplace eReplace <- liftM switch $ hold never $ fmap snd eResult return $ fmap fst eResult divClass :: forall t m a. MonadWidget t m => String -> m a -> m a divClass = elClass "div" dtdd :: forall t m a. MonadWidget t m => String -> m a -> m a dtdd h w = do el "dt" $ text h el "dd" $ w blank :: forall t m. MonadWidget t m => m () blank = return () tableDynAttr :: forall t m r k v. (MonadWidget t m, Show k, Ord k) => String -> [(String, k -> Dynamic t r -> m v)] -> Dynamic t (Map k r) -> (k -> m (Dynamic t (Map String String))) -> m (Dynamic t (Map k (El t, [v]))) tableDynAttr klass cols dRows rowAttrs = elAttr "div" (Map.singleton "style" "zoom: 1; overflow: auto; background: white;") $ do elAttr "table" (Map.singleton "class" klass) $ do el "thead" $ el "tr" $ do mapM_ (\(h, _) -> el "th" $ text h) cols el "tbody" $ do listWithKey dRows (\k r -> do dAttrs <- rowAttrs k elDynAttr' "tr" dAttrs $ mapM (\x -> el "td" $ snd x k r) cols) --TODO preselect a tab on open tabDisplay :: forall t m k. (MonadFix m, MonadWidget t m, Show k, Ord k) => String -> String -> Map k (String, m ()) -> m () tabDisplay ulClass activeClass tabItems = do rec dCurrentTab <- holdDyn Nothing (updated dTabClicks) dTabClicks :: Dynamic t (Maybe k) <- elAttr "ul" (Map.singleton "class" ulClass) $ do tabClicksList :: [Event t k] <- (liftM Map.elems) $ imapM (\k (s,_) -> headerBarLink s k =<< mapDyn (== (Just k)) dCurrentTab) tabItems let eTabClicks :: Event t k = leftmost tabClicksList holdDyn Nothing $ fmap Just eTabClicks :: m (Dynamic t (Maybe k)) divClass "" $ do let dTabs :: Dynamic t (Map k (String, m ())) = constDyn tabItems _ <- listWithKey dTabs (\k dTab -> do dAttrs <- mapDyn (\sel -> do let t1 = listToMaybe $ Map.keys tabItems if sel == Just k || (sel == Nothing && t1 == Just k) then Map.empty else Map.singleton "style" "display:none;") dCurrentTab elDynAttr "div" dAttrs $ dyn =<< mapDyn snd dTab) return () where headerBarLink :: (MonadWidget t m, Ord k) => String -> k -> Dynamic t Bool -> m (Event t k) headerBarLink x k dBool = do dAttributes <- mapDyn (\b -> if b then Map.singleton "class" activeClass else Map.empty) dBool elDynAttr "li" dAttributes $ do a <- link x return $ fmap (const k) (_link_clicked a) -- | Place an element into the DOM and wrap it with Reflex event handlers. Note: undefined behavior may result if the element is placed multiple times, removed from the DOM after being placed, or in other situations. Don't use this unless you understand the internals of MonadWidget. unsafePlaceElement :: MonadWidget t m => HTMLElement -> m (El t) unsafePlaceElement e = do p <- askParent _ <- liftIO $ nodeAppendChild p $ Just e wrapElement e deriveGEq ''EventName deriveGCompare ''EventName -------------------------------------------------------------------------------- -- Deprecated functions {-# DEPRECATED _el_clicked "Use `domEvent Click` instead" #-} _el_clicked :: Reflex t => El t -> Event t () _el_clicked = domEvent Click {-# DEPRECATED _el_keypress "Use `domEvent Keypress` instead" #-} _el_keypress :: Reflex t => El t -> Event t Int _el_keypress = domEvent Keypress {-# DEPRECATED _el_scrolled "Use `domEvent Scroll` instead" #-} _el_scrolled :: Reflex t => El t -> Event t Int _el_scrolled = domEvent Scroll