@@ -128,6 +128,7 @@ import Data.Monoid hiding (Product)
128import Data.Some (Some)
128import Data.Some (Some)
129import qualified Data.Some as Some
129import qualified Data.Some as Some
130import Data.Text (Text)
130import Data.Text (Text)
131+import qualified Data.Text as T
131import qualified GHCJS.DOM as DOM
132import qualified GHCJS.DOM as DOM
132import GHCJS.DOM.Document (Document, createDocumentFragment, createElement, createElementNS, createTextNode, createComment)
133import GHCJS.DOM.Document (Document, createDocumentFragment, createElement, createElementNS, createTextNode, createComment)
133import GHCJS.DOM.Element (getScrollTop, removeAttribute, removeAttributeNS, setAttribute, setAttributeNS)
134import GHCJS.DOM.Element (getScrollTop, removeAttribute, removeAttributeNS, setAttribute, setAttributeNS)
@@ -145,8 +146,10 @@ import GHCJS.DOM.MouseEvent
145import qualified GHCJS.DOM.Touch as Touch
146import qualified GHCJS.DOM.Touch as Touch
146import qualified GHCJS.DOM.TouchEvent as TouchEvent
147import qualified GHCJS.DOM.TouchEvent as TouchEvent
147import qualified GHCJS.DOM.TouchList as TouchList
148import qualified GHCJS.DOM.TouchList as TouchList
148−import GHCJS.DOM.Node (appendChild_, getOwnerDocumentUnchecked, getParentNodeUnchecked, setNodeValue, toNode)
149+import GHCJS.DOM.Node (appendChild_, getChildNodes, getOwnerDocumentUnchecked, getParentNodeUnchecked, setNodeValue, toNode)
149import qualified GHCJS.DOM.Node as DOM (insertBefore_)
150import qualified GHCJS.DOM.Node as DOM (insertBefore_)
151+import qualified GHCJS.DOM.Node as Node
152+import qualified GHCJS.DOM.NodeList as NodeList
150import GHCJS.DOM.Types
153import GHCJS.DOM.Types
151 (liftJSM, askJSM, runJSM, JSM, MonadJSM,
154 (liftJSM, askJSM, runJSM, JSM, MonadJSM,
152 FocusEvent, IsElement, IsEvent, IsNode, KeyboardEvent, Node,
155 FocusEvent, IsElement, IsEvent, IsNode, KeyboardEvent, Node,
@@ -155,7 +158,7 @@ import qualified GHCJS.DOM.Types as DOM
155import GHCJS.DOM.UIEvent
158import GHCJS.DOM.UIEvent
156import GHCJS.DOM.KeyboardEvent as KeyboardEvent
159import GHCJS.DOM.KeyboardEvent as KeyboardEvent
157import qualified GHCJS.DOM.Window as Window
160import qualified GHCJS.DOM.Window as Window
158−import Language.Javascript.JSaddle (call, eval)
161+import Language.Javascript.JSaddle (call, eval, jsg, js1)
159import Data.IntMap.Strict (IntMap)
162import Data.IntMap.Strict (IntMap)
160import qualified Data.IntMap.Strict as IntMap
163import qualified Data.IntMap.Strict as IntMap
161import Data.FastMutableIntMap (PatchIntMap (..))
164import Data.FastMutableIntMap (PatchIntMap (..))
@@ -172,11 +175,19 @@ instance MonadJSM m => MonadJSM (ImmediateDomBuilderT t m) where
172 liftJSM' = ImmediateDomBuilderT . liftJSM'
175 liftJSM' = ImmediateDomBuilderT . liftJSM'
179+ - Fence off `prerender` and totally replace immediately
180+ - Fence off `runWithReplace` and hydrate at postBuild
181+ - Otherwise hydrate at build time, skipping elements without SSR attribute, stripping the attrs as we go
175data ImmediateDomBuilderEnv t
184data ImmediateDomBuilderEnv t
176 = ImmediateDomBuilderEnv { _immediateDomBuilderEnv_document :: {-# UNPACK #-} !Document
185 = ImmediateDomBuilderEnv { _immediateDomBuilderEnv_document :: {-# UNPACK #-} !Document
177 , _immediateDomBuilderEnv_parent :: {-# UNPACK #-} !Node
186 , _immediateDomBuilderEnv_parent :: {-# UNPACK #-} !Node
178 , _immediateDomBuilderEnv_unreadyChildren :: {-# UNPACK #-} !(IORef Word) -- Number of children who still aren't fully rendered
187 , _immediateDomBuilderEnv_unreadyChildren :: {-# UNPACK #-} !(IORef Word) -- Number of children who still aren't fully rendered
179 , _immediateDomBuilderEnv_commitAction :: !(JSM ()) -- Action to take when all children are ready --TODO: we should probably get rid of this once we invoke it
188 , _immediateDomBuilderEnv_commitAction :: !(JSM ()) -- Action to take when all children are ready --TODO: we should probably get rid of this once we invoke it
189+ , _immediateDomBuilderEnv_hydrating :: {-# UNPACK #-} !(IORef Bool) -- In hydration mode?
190+ , _immediateDomBuilderEnv_hydrationNode :: {-# UNPACK #-} !(IORef (Maybe Node)) -- Hydration node index
182newtype ImmediateDomBuilderT t m a = ImmediateDomBuilderT { unImmediateDomBuilderT :: ReaderT (ImmediateDomBuilderEnv t) (RequesterT t JSM Identity (TriggerEventT t m)) a }
193newtype ImmediateDomBuilderT t m a = ImmediateDomBuilderT { unImmediateDomBuilderT :: ReaderT (ImmediateDomBuilderEnv t) (RequesterT t JSM Identity (TriggerEventT t m)) a }
@@ -249,21 +260,76 @@ append n = do
249 liftJSM $ appendChild_ p n
260 liftJSM $ appendChild_ p n
250 return ()
261 return ()
263+getHydrationMode :: MonadIO m => ImmediateDomBuilderT t m Bool
264+getHydrationMode = do
265+ ref <- ImmediateDomBuilderT $ asks _immediateDomBuilderEnv_hydrating
266+ liftIO $ readIORef ref
252{-# INLINABLE textNodeInternal #-}
268{-# INLINABLE textNodeInternal #-}
253textNodeInternal :: (MonadJSM m, ToDOMString contents) => contents -> ImmediateDomBuilderT t m DOM.Text
269textNodeInternal :: (MonadJSM m, ToDOMString contents) => contents -> ImmediateDomBuilderT t m DOM.Text
254textNodeInternal !t = do
270textNodeInternal !t = do
255 doc <- askDocument
271 doc <- askDocument
256− n <- liftJSM $ createTextNode doc t
272+-- n <- liftJSM $ createTextNode doc t
257− append $ toNode n
273+-- append $ toNode n
258− return n
274+-- return n
276+ getHydrationMode >>= \case
278+ liftIO $ print $ "Not in hydration mode, creating text node: " <> DOM.toJSString t
279+ n <- liftJSM $ createTextNode doc t -- not in hydration mode, create as usual
283+ lastHydrationNodeRef <- ImmediateDomBuilderT $ asks _immediateDomBuilderEnv_hydrationNode
284+ lastHydrationNode <- liftIO $ readIORef lastHydrationNodeRef
285+ let go mLastNode = maybe (Node.getFirstChild parent) Node.getNextSibling mLastNode >>= \case
287+ liftIO $ print $ "No node found, building text node: " <> DOM.toJSString t
288+ tn <- liftJSM $ createTextNode doc t
289+ append $ toNode tn -- we can append because there's no node at this index (we have exhausted all previous nodes)
291+ Just node -> DOM.castTo DOM.Text node >>= \case
293+ liftIO $ putStrLn $ "Not a text node, skipping"
296+ liftIO $ print $ "Using existing text node: instead of building " <> DOM.toJSString t
298+ n <- go lastHydrationNode
299+ liftIO $ modifyIORef' lastHydrationNodeRef (\_ -> Just $ toNode n)
260{-# INLINABLE commentNodeInternal #-}
302{-# INLINABLE commentNodeInternal #-}
261commentNodeInternal :: (MonadJSM m, ToDOMString contents) => contents -> ImmediateDomBuilderT t m DOM.Comment
303commentNodeInternal :: (MonadJSM m, ToDOMString contents) => contents -> ImmediateDomBuilderT t m DOM.Comment
262commentNodeInternal !t = do
304commentNodeInternal !t = do
263 doc <- askDocument
305 doc <- askDocument
308+ getHydrationMode >>= \case
310+ liftIO $ print $ "Not in hydration mode, creating comment node: " <> DOM.toJSString t
264− n <- liftJSM $ createComment doc t
311+ n <- liftJSM $ createComment doc t -- not in hydration mode, create as usual
265− append $ toNode n
312+ append $ toNode n
315+ lastHydrationNodeRef <- ImmediateDomBuilderT $ asks _immediateDomBuilderEnv_hydrationNode
316+ lastHydrationNode <- liftIO $ readIORef lastHydrationNodeRef
317+ let go mLastNode = maybe (Node.getFirstChild parent) Node.getNextSibling mLastNode >>= \case
319+ liftIO $ print $ "No node found, building comment node: " <> DOM.toJSString t
320+ tn <- liftJSM $ createComment doc t
321+ append $ toNode tn -- we can append because there's no node at this index (we have exhausted all previous nodes)
323+ Just node -> DOM.castTo DOM.Comment node >>= \case
325+ liftIO $ putStrLn $ "Not a comment node, skipping"
328+ liftIO $ print $ "Using existing comment node: instead of building " <> DOM.toJSString t
330+ n <- go lastHydrationNode
331+ liftIO $ modifyIORef' lastHydrationNodeRef (\_ -> Just $ toNode n)
268-- | s and e must both be children of the same node and s must precede e;
334-- | s and e must both be children of the same node and s must precede e;
269-- all nodes between s and e will be removed, but s and e will not be removed
335-- all nodes between s and e will be removed, but s and e will not be removed
@@ -367,16 +433,45 @@ wrap e cfg = do
367makeElement :: forall er t m a. (MonadJSM m, MonadFix m, MonadReflexCreateTrigger t m, Adjustable t m) => Text -> ElementConfig er t GhcjsDomSpace -> ImmediateDomBuilderT t m a -> ImmediateDomBuilderT t m ((Element er GhcjsDomSpace t, a), DOM.Element)
433makeElement :: forall er t m a. (MonadJSM m, MonadFix m, MonadReflexCreateTrigger t m, Adjustable t m) => Text -> ElementConfig er t GhcjsDomSpace -> ImmediateDomBuilderT t m a -> ImmediateDomBuilderT t m ((Element er GhcjsDomSpace t, a), DOM.Element)
368makeElement elementTag cfg child = do
434makeElement elementTag cfg child = do
369 doc <- askDocument
435 doc <- askDocument
437+ let buildElement = do
370− e <- liftJSM $ uncheckedCastTo DOM.Element <$> case cfg ^. namespace of
438+ e <- liftJSM $ uncheckedCastTo DOM.Element <$> case cfg ^. namespace of
371− Nothing -> createElement doc elementTag
439+ Nothing -> createElement doc elementTag
372− Just ens -> createElementNS doc (Just ens) elementTag
440+ Just ens -> createElementNS doc (Just ens) elementTag
373− ImmediateDomBuilderT $ iforM_ (cfg ^. initialAttributes) $ \(AttributeName mAttrNamespace n) v -> case mAttrNamespace of
441+ ImmediateDomBuilderT $ iforM_ (cfg ^. initialAttributes) $ \(AttributeName mAttrNamespace n) v -> case mAttrNamespace of
374− Nothing -> lift $ setAttribute e n v
442+ Nothing -> lift $ setAttribute e n v
375− Just ans -> lift $ setAttributeNS e (Just ans) n v
443+ Just ans -> lift $ setAttributeNS e (Just ans) n v
376− result <- flip localEnv child $ \env -> env
444+ pure e
445+ e <- getHydrationMode >>= \case
447+ liftIO $ print $ "Not in hydration mode, creating " <> elementTag
448+ e <- buildElement -- not in hydration mode, create as usual
452+ lastHydrationNodeRef <- ImmediateDomBuilderT $ asks _immediateDomBuilderEnv_hydrationNode
453+ lastHydrationNode <- liftIO $ readIORef lastHydrationNodeRef
454+ let go mLastNode = maybe (Node.getFirstChild parent) Node.getNextSibling mLastNode >>= \case
456+ liftIO $ print $ "No node found, building element: " <> elementTag
458+ append $ toNode e -- we can append because there's no node at this index (we have exhausted all previous nodes)
460+ Just node -> DOM.castTo DOM.Element node >>= \case
462+ liftIO $ putStrLn $ "Not an element, skipping"
463+ go (Just node) -- TODO maybe check for SSR attribute?
465+ tn <- Element.getTagName e
466+ liftIO $ print $ "Using existing element: " <> tn <> " instead of building " <> elementTag
468+ e <- go lastHydrationNode
469+ liftIO $ modifyIORef' lastHydrationNodeRef (\_ -> Just $ toNode e)
472+ -- TODO: prerender needs completely replacing
473+ childNode <- liftIO $ newIORef Nothing
377− { _immediateDomBuilderEnv_parent = toNode e
474+ result <- localEnv (\env -> env { _immediateDomBuilderEnv_parent = toNode e, _immediateDomBuilderEnv_hydrationNode = childNode }) child
380 wrapped <- wrap e $ extractRawElementConfig cfg
475 wrapped <- wrap e $ extractRawElementConfig cfg
381 return ((wrapped, result), e)
476 return ((wrapped, result), e)
@@ -625,11 +720,62 @@ instance SupportsImmediateDomBuilder t m => MountableDomBuilder t (ImmediateDomB
625 liftIO $ writeIORef (_immediateDomFragment_state childFragment) $ FragmentState_Mounted (before, after)
720 liftIO $ writeIORef (_immediateDomFragment_state childFragment) $ FragmentState_Mounted (before, after)
626 liftIO $ writeIORef (_immediateDomFragment_state fragment) $ FragmentState_Mounted (before, after)
721 liftIO $ writeIORef (_immediateDomFragment_state fragment) $ FragmentState_Mounted (before, after)
723+localNoHydration :: MonadIO m => ImmediateDomBuilderT t m a -> ImmediateDomBuilderT t m a
724+localNoHydration m = do
725+ h <- liftIO $ newIORef False
726+ localEnv (\env -> env { _immediateDomBuilderEnv_hydrating = h }) m
728+skipToCommentId :: (MonadJSM m, IsNode a) => Text -> ImmediateDomBuilderT t m a -> (a -> Maybe DOM.Node -> ImmediateDomBuilderT t m ()) -> ImmediateDomBuilderT t m (a, Text)
729+skipToCommentId prefix buildElement replaceOrCreate = do
732+ lastHydrationNodeRef <- ImmediateDomBuilderT $ asks _immediateDomBuilderEnv_hydrationNode
733+ lastHydrationNode <- liftIO $ readIORef lastHydrationNodeRef
734+ let go mLastNode = maybe (Node.getFirstChild parent) Node.getNextSibling mLastNode >>= \case
736+ liftIO $ print $ "No node found, building comment"
738+ replaceOrCreate a Nothing
740+ Just node -> DOM.castTo DOM.Comment node >>= \case
742+ commentText <- Node.getTextContentUnchecked comment
743+ case T.stripPrefix prefix commentText of
746+ replaceOrCreate new (Just node)
749+ liftIO $ putStrLn $ "Not a comment, skipping"
752+ liftIO $ putStrLn $ "Not a comment, skipping"
754+ (a, key) <- go lastHydrationNode
755+ liftIO $ modifyIORef' lastHydrationNodeRef (\_ -> Just $ toNode a)
758+skipToReplaceStart :: MonadJSM m => ImmediateDomBuilderT t m (DOM.Text, Text)
759+skipToReplaceStart = do
762+ skipToCommentId "replace-start" (liftJSM $ createTextNode doc ("" :: Text)) $ \new -> \case
763+ Nothing -> void $ append $ toNode new
764+ Just node -> void $ liftJSM $ Node.replaceChild parent new node
766+skipToReplaceEnd :: MonadJSM m => Text -> ImmediateDomBuilderT t m DOM.Text
767+skipToReplaceEnd key = do
770+ fmap fst $ skipToCommentId "replace-end" (liftJSM $ createTextNode doc ("" :: Text)) $ \new -> \case
771+ Nothing -> void $ append $ toNode new
772+ Just node -> void $ liftJSM $ Node.replaceChild parent new node
628instance (Reflex t, Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, PrimMonad m) => Adjustable t (ImmediateDomBuilderT t m) where
774instance (Reflex t, Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, PrimMonad m) => Adjustable t (ImmediateDomBuilderT t m) where
629 {-# INLINABLE runWithReplace #-}
775 {-# INLINABLE runWithReplace #-}
630 runWithReplace a0 a' = do
776 runWithReplace a0 a' = do
631 initialEnv <- ImmediateDomBuilderT ask
777 initialEnv <- ImmediateDomBuilderT ask
632− before <- textNodeInternal ("" :: Text)
778+ (before, beforeKey) <- skipToReplaceStart
633 let parentUnreadyChildren = _immediateDomBuilderEnv_unreadyChildren initialEnv
779 let parentUnreadyChildren = _immediateDomBuilderEnv_unreadyChildren initialEnv
634 haveEverBeenReady <- liftIO $ newIORef False
780 haveEverBeenReady <- liftIO $ newIORef False
635 currentCohort <- liftIO $ newIORef (-1 :: Int) -- Equal to the cohort currently in the DOM
781 currentCohort <- liftIO $ newIORef (-1 :: Int) -- Equal to the cohort currently in the DOM
@@ -644,14 +790,17 @@ instance (Reflex t, Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, PrimM
644 when (new == 0) $ _immediateDomBuilderEnv_commitAction initialEnv
790 when (new == 0) $ _immediateDomBuilderEnv_commitAction initialEnv
645 -- We draw 'after' in this roundabout way to avoid using MonadFix
791 -- We draw 'after' in this roundabout way to avoid using MonadFix
646 doc <- askDocument
792 doc <- askDocument
647− after <- createTextNode doc ("" :: Text)
793+ after <- skipToReplaceEnd beforeKey
648 let drawInitialChild = do
794 let drawInitialChild = do
649 unreadyChildren <- liftIO $ newIORef 0
795 unreadyChildren <- liftIO $ newIORef 0
650− let f = do
796+-- let f = do
651− result <- a0
797+-- result <- a0
652− append $ toNode after
798+-- append $ toNode after
799+-- Node.getNextSibling (toNode result) >>= \case
800+-- Nothing -> append (toNode after)
801+-- Just next -> insertBefore (toNode after) next
653− return result
802+-- return result
654− result <- runReaderT (unImmediateDomBuilderT f) $ initialEnv
803+ result <- runReaderT (unImmediateDomBuilderT a0) initialEnv
655 { _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
804 { _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
656 , _immediateDomBuilderEnv_commitAction = myCommitAction
805 , _immediateDomBuilderEnv_commitAction = myCommitAction
@@ -670,11 +819,12 @@ instance (Reflex t, Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, PrimM
670 insertBefore df after
819 insertBefore df after
671 liftIO $ writeIORef currentCohort cohortId
820 liftIO $ writeIORef currentCohort cohortId
672 myCommitAction
821 myCommitAction
673− result <- runReaderT (unImmediateDomBuilderT child) $ initialEnv
822+ let env = initialEnv
674− { _immediateDomBuilderEnv_parent = toNode df
823+ { _immediateDomBuilderEnv_parent = toNode df
675− , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
824+ , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
676− , _immediateDomBuilderEnv_commitAction = commitAction
825+ , _immediateDomBuilderEnv_commitAction = commitAction
827+ result <- runReaderT (unImmediateDomBuilderT child) env
678 uc <- liftIO $ readIORef unreadyChildren
828 uc <- liftIO $ readIORef unreadyChildren
679 let commitActionToRunNow = if uc == 0
829 let commitActionToRunNow = if uc == 0
680 then Just commitAction
830 then Just commitAction
@@ -1027,6 +1177,11 @@ mkHasFocus e = do
1027 , True <$ Reflex.select (_element_events e) (WrapArg Focus)
1177 , True <$ Reflex.select (_element_events e) (WrapArg Focus)
1180+insertAfter :: (MonadJSM m, IsNode new, IsNode existing) => new -> existing -> m ()
1181+insertAfter new existing = do
1182+ p <- getParentNodeUnchecked existing
1183+ Node.getNextSibling existing >>= DOM.insertBefore_ p new
1030insertBefore :: (MonadJSM m, IsNode new, IsNode existing) => new -> existing -> m ()
1185insertBefore :: (MonadJSM m, IsNode new, IsNode existing) => new -> existing -> m ()
1031insertBefore new existing = do
1186insertBefore new existing = do
1032 p <- getParentNodeUnchecked existing
1187 p <- getParentNodeUnchecked existing