on develop help sign in
WIP: Create Hydration builder module
63a56d6Tom Smalley
reflex-dom-core/reflex-dom-core.cabal+1
@@ -90,6 +90,7 @@ library
90 Foreign.JavaScript.Utils
90 Foreign.JavaScript.Utils
91 Reflex.Dom.Builder.Class
91 Reflex.Dom.Builder.Class
92 Reflex.Dom.Builder.Class.Events
92 Reflex.Dom.Builder.Class.Events
93+ Reflex.Dom.Builder.Hydration
93 Reflex.Dom.Builder.Immediate
94 Reflex.Dom.Builder.Immediate
94 Reflex.Dom.Builder.InputDisabled
95 Reflex.Dom.Builder.InputDisabled
95 Reflex.Dom.Builder.Static
96 Reflex.Dom.Builder.Static
reflex-dom-core/src/Reflex/Dom/Builder/Hydration.hsnew file+1190
@@ -0,0 +1,1190 @@
1+{-# LANGUAGE BangPatterns #-}
2+{-# LANGUAGE CPP #-}
3+{-# LANGUAGE ConstraintKinds #-}
4+{-# LANGUAGE DataKinds #-}
5+{-# LANGUAGE DefaultSignatures #-}
6+{-# LANGUAGE DeriveGeneric #-}
7+{-# LANGUAGE EmptyDataDecls #-}
8+{-# LANGUAGE FlexibleContexts #-}
9+{-# LANGUAGE FlexibleInstances #-}
10+{-# LANGUAGE GADTs #-}
11+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
12+{-# LANGUAGE LambdaCase #-}
13+{-# LANGUAGE MultiParamTypeClasses #-}
14+{-# LANGUAGE OverloadedStrings #-}
15+{-# LANGUAGE PolyKinds #-}
16+{-# LANGUAGE RankNTypes #-}
17+{-# LANGUAGE RecursiveDo #-}
18+{-# LANGUAGE ScopedTypeVariables #-}
19+{-# LANGUAGE StandaloneDeriving #-}
20+#ifdef USE_TEMPLATE_HASKELL
21+{-# LANGUAGE TemplateHaskell #-}
22+#endif
23+{-# LANGUAGE TypeFamilies #-}
24+{-# LANGUAGE TypeOperators #-}
25+{-# LANGUAGE UndecidableInstances #-}
26+#ifdef ghcjs_HOST_OS
27+{-# LANGUAGE ForeignFunctionInterface #-}
28+{-# LANGUAGE JavaScriptFFI #-}
29+#endif
30+module Reflex.Dom.Builder.Hydration
31+ ( EventTriggerRef (..)
32+ , HydrationDomBuilderEnv (..)
33+ , HydrationDomBuilderT (..)
34+ , HydrationMode (..)
35+ , runHydrationDomBuilderT
36+ , askParent
37+ , askEvents
38+ , append
39+ , textNodeInternal
40+ , deleteBetweenExclusive
41+ , extractBetweenExclusive
42+ , deleteUpTo
43+ , extractUpTo
44+ , SupportsHydrationDomBuilder
45+ , collectUpTo
46+ , collectUpToGivenParent
47+ , EventFilterTriggerRef (..)
48+ , wrap
49+ , makeElement
50+ , GhcjsDomHandler (..)
51+ , GhcjsDomHandler1 (..)
52+ , GhcjsDomEvent (..)
53+ , GhcjsEventFilter (..)
54+ , Pair1 (..)
55+ , Maybe1 (..)
56+ , GhcjsEventSpec (..)
57+ , HasDocument (..)
58+ , ghcjsEventSpec_filters
59+ , ghcjsEventSpec_handler
60+ , GhcjsEventHandler (..)
61+#ifndef USE_TEMPLATE_HASKELL
62+ , phantom2
63+#endif
64+ , drawChildUpdate
65+ , ChildReadyState (..)
66+ , ChildReadyStateInt (..)
67+ , mkHasFocus
68+ , insertBefore
69+ , EventType
70+ , defaultDomEventHandler
71+ , defaultDomWindowEventHandler
72+ , withIsEvent
73+ , showEventName
74+ , elementOnEventName
75+ , windowOnEventName
76+ , wrapDomEvent
77+ , subscribeDomEvent
78+ -- * Internal
79+ , traverseDMapWithKeyWithAdjust'
80+ , hoistTraverseWithKeyWithAdjust
81+ , traverseIntMapWithKeyWithAdjust'
82+ , hoistTraverseIntMapWithKeyWithAdjust
83+ ) where
84+
85+import Foreign.JavaScript.TH
86+import Reflex.Adjustable.Class
87+import Reflex.Class as Reflex
88+import qualified Reflex.Spider.Internal as Spider
89+import Reflex.Dom.Builder.Class
90+import Reflex.Dom.Builder.Immediate hiding (askParent, append, extractBetweenExclusive, extractUpTo, collectUpToGivenParent, askEvents, wrap, makeElement, textNodeInternal, commentNodeInternal, wrapDomEvent, mkHasFocus, insertBefore, deleteBetweenExclusive, traverseIntMapWithKeyWithAdjust', traverseDMapWithKeyWithAdjust', hoistTraverseWithKeyWithAdjust, deleteUpTo, collectUpTo, hoistTraverseIntMapWithKeyWithAdjust, drawChildUpdate, subscribeDomEvent)
91+import Reflex.Dynamic
92+import Reflex.Host.Class
93+import qualified Reflex.Patch.DMap as PatchDMap
94+import qualified Reflex.Patch.DMapWithMove as PatchDMapWithMove
95+import Reflex.PerformEvent.Class
96+import Reflex.PostBuild.Class
97+import Reflex.TriggerEvent.Base hiding (askEvents)
98+import qualified Reflex.TriggerEvent.Base as TriggerEventT (askEvents)
99+import Reflex.TriggerEvent.Class
100+
101+import Control.Concurrent
102+import Control.Lens hiding (element, ix)
103+import Control.Monad.Exception
104+import Control.Monad.Primitive
105+import Control.Monad.Reader
106+import Control.Monad.Ref
107+#ifndef USE_TEMPLATE_HASKELL
108+import Data.Functor.Contravariant (phantom)
109+#endif
110+import Data.Bitraversable
111+import Data.Default
112+import Data.Dependent.Map (DMap)
113+import qualified Data.Dependent.Map as DMap
114+import Data.Dependent.Sum
115+import Data.Functor.Compose
116+import Data.Functor.Constant
117+import Data.Functor.Misc
118+import Data.Functor.Product
119+import Data.IORef
120+import qualified Data.Map as Map
121+import Data.Maybe
122+import Data.Monoid hiding (Product)
123+import Data.Some (Some)
124+import qualified Data.Some as Some
125+import Data.Text (Text)
126+import qualified Data.Text as T
127+import GHC.Generics (Generic)
128+import qualified GHCJS.DOM as DOM
129+import GHCJS.DOM.Document (Document, createDocumentFragment, createElement, createElementNS, createTextNode, createComment)
130+import GHCJS.DOM.Element (getScrollTop, removeAttribute, removeAttributeNS, setAttribute, setAttributeNS)
131+import qualified GHCJS.DOM.Element as Element
132+import qualified GHCJS.DOM.Event as Event
133+import qualified GHCJS.DOM.GlobalEventHandlers as Events
134+import qualified GHCJS.DOM.DocumentAndElementEventHandlers as Events
135+import GHCJS.DOM.EventM (EventM, event, on)
136+import qualified GHCJS.DOM.EventM as DOM
137+import qualified GHCJS.DOM.FileList as FileList
138+import qualified GHCJS.DOM.HTMLInputElement as Input
139+import qualified GHCJS.DOM.HTMLSelectElement as Select
140+import qualified GHCJS.DOM.HTMLTextAreaElement as TextArea
141+import GHCJS.DOM.MouseEvent
142+import qualified GHCJS.DOM.Touch as Touch
143+import qualified GHCJS.DOM.TouchEvent as TouchEvent
144+import qualified GHCJS.DOM.TouchList as TouchList
145+import GHCJS.DOM.Node (appendChild_, getChildNodes, getOwnerDocumentUnchecked, getParentNodeUnchecked, setNodeValue, toNode)
146+import qualified GHCJS.DOM.Node as DOM (insertBefore_)
147+import qualified GHCJS.DOM.Node as Node
148+import qualified GHCJS.DOM.NodeList as NodeList
149+import GHCJS.DOM.Types
150+ (liftJSM, askJSM, runJSM, JSM, MonadJSM,
151+ FocusEvent, IsElement, IsEvent, IsNode, KeyboardEvent, Node,
152+ ToDOMString, TouchEvent, WheelEvent, uncheckedCastTo, ClipboardEvent)
153+import qualified GHCJS.DOM.Types as DOM
154+import GHCJS.DOM.UIEvent
155+import GHCJS.DOM.KeyboardEvent as KeyboardEvent
156+import qualified GHCJS.DOM.Window as Window
157+import Language.Javascript.JSaddle (call, eval, jsg, js1)
158+import Data.IntMap.Strict (IntMap)
159+import qualified Data.IntMap.Strict as IntMap
160+import Data.FastMutableIntMap (PatchIntMap (..))
161+import qualified Data.FastMutableIntMap as FastMutableIntMap
162+import System.IO.Unsafe (unsafePerformIO)
163+
164+import Reflex.Requester.Base
165+import Reflex.Requester.Class
166+import Foreign.JavaScript.Internal.Utils
167+
168+#ifndef ghcjs_HOST_OS
169+import GHCJS.DOM.Types (MonadJSM (..))
170+
171+instance MonadJSM m => MonadJSM (HydrationDomBuilderT t m) where
172+ liftJSM' = HydrationDomBuilderT . liftJSM'
173+#endif
174+
175+data HydrationDomSpace
176+
177+data HydrationEventSpec (er :: EventTag -> *) = HydrationEventSpec deriving (Generic)
178+
179+instance Default (HydrationEventSpec er)
180+
181+instance DomSpace HydrationDomSpace where
182+ type EventSpec HydrationDomSpace = GhcjsEventSpec
183+ type RawDocument HydrationDomSpace = DOM.Document
184+ type RawTextNode HydrationDomSpace = ()
185+ type RawCommentNode HydrationDomSpace = ()
186+ type RawElement HydrationDomSpace = ()
187+ type RawFile HydrationDomSpace = DOM.File
188+ type RawInputElement HydrationDomSpace = ()
189+ type RawTextAreaElement HydrationDomSpace = ()
190+ type RawSelectElement HydrationDomSpace = ()
191+ addEventSpecFlags _ en f es = es
192+ { _ghcjsEventSpec_filters =
193+ let f' = Just . GhcjsEventFilter . \case
194+ Nothing -> \evt -> do
195+ mEventResult <- unGhcjsEventHandler (_ghcjsEventSpec_handler es) (en, evt)
196+ return (f mEventResult, return mEventResult)
197+ Just (GhcjsEventFilter oldFilter) -> \evt -> do
198+ (oldFlags, oldContinuation) <- oldFilter evt
199+ mEventResult <- oldContinuation
200+ let newFlags = oldFlags <> f mEventResult
201+ return (newFlags, return mEventResult)
202+ in DMap.alter f' en $ _ghcjsEventSpec_filters es
203+ }
204+-- addEventSpecFlags _ _ _ _ = HydrationEventSpec
205+
206+{- Hydration:
207+ - Fence off `prerender` and totally replace immediately
208+ - Fence off `runWithReplace` and hydrate at postBuild
209+ - Otherwise hydrate at build time, skipping elements without SSR attribute, stripping the attrs as we go
210+-}
211+
212+data HydrationDomBuilderEnv t m
213+ = HydrationDomBuilderEnv { _hydrationDomBuilderEnv_document :: {-# UNPACK #-} !Document
214+ , _hydrationDomBuilderEnv_parent :: {-# UNPACK #-} !Node
215+ , _hydrationDomBuilderEnv_unreadyChildren :: {-# UNPACK #-} !(IORef Word) -- Number of children who still aren't fully rendered
216+ , _hydrationDomBuilderEnv_commitAction :: !(JSM ()) -- Action to take when all children are ready --TODO: we should probably get rid of this once we invoke it
217+ , _hydrationDomBuilderEnv_hydrationMode :: {-# UNPACK #-} !(IORef HydrationMode) -- In hydration mode?
218+ , _hydrationDomBuilderEnv_hydrationNode :: {-# UNPACK #-} !(IORef (Maybe Node)) -- Hydration node index
219+ , _hydrationDomBuilderEnv_hydrationResult :: {-# UNPACK #-} !(IORef [Behavior t (m ())])
220+ , _hydrationDomBuilderEnv_prerenderDepth :: {-# UNPACK #-} !(IORef Word)
221+ , _hydrationDomBuilderEnv_eventChan :: {-# UNPACK #-} !(Chan [DSum (EventTriggerRef t) TriggerInvocation])
222+ }
223+
224+newtype HydrationDomBuilderT t m a = HydrationDomBuilderT { unHydrationDomBuilderT :: ReaderT (HydrationDomBuilderEnv t m) (RequesterT t JSM Identity (TriggerEventT t m)) a }
225+ deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadException
226+#if MIN_VERSION_base(4,9,1)
227+ , MonadAsyncException
228+#endif
229+ )
230+
231+instance PrimMonad m => PrimMonad (HydrationDomBuilderT x m) where
232+ type PrimState (HydrationDomBuilderT x m) = PrimState m
233+ primitive = lift . primitive
234+
235+instance MonadTrans (HydrationDomBuilderT t) where
236+ lift = HydrationDomBuilderT . lift . lift . lift
237+
238+instance (Reflex t, MonadFix m) => DomRenderHook t (HydrationDomBuilderT t m) where
239+ withRenderHook hook (HydrationDomBuilderT a) = do
240+ e <- HydrationDomBuilderT ask
241+ HydrationDomBuilderT $ lift $ withRequesting $ \rsp -> do
242+ (x, req) <- lift $ runRequesterT (runReaderT a e) $ runIdentity <$> rsp
243+ return (ffor req $ \rm -> hook $ traverseRequesterData (\r -> Identity <$> r) rm, x)
244+ requestDomAction = HydrationDomBuilderT . lift . requestingIdentity
245+ requestDomAction_ = HydrationDomBuilderT . lift . requesting_
246+
247+{-# INLINABLE runHydrationDomBuilderT #-}
248+runHydrationDomBuilderT
249+ :: ( MonadFix m
250+ , PerformEvent t m
251+ , MonadReflexCreateTrigger t m
252+ , MonadJSM m
253+ , MonadJSM (Performable m)
254+ , MonadRef m
255+ , Ref m ~ IORef
256+ )
257+ => HydrationDomBuilderT t m a
258+ -> HydrationDomBuilderEnv t m
259+-- -> Chan [DSum (EventTriggerRef t) TriggerInvocation]
260+ -> m a
261+runHydrationDomBuilderT (HydrationDomBuilderT a) env =
262+ flip runTriggerEventT (_hydrationDomBuilderEnv_eventChan env) $ do
263+ rec (x, req) <- runRequesterT (runReaderT a env) rsp
264+ rsp <- performEventAsync $ ffor req $ \rm f -> liftJSM $ runInAnimationFrame f $
265+ traverseRequesterData (\r -> Identity <$> r) rm
266+ return x
267+ where
268+ runInAnimationFrame f x = void . DOM.inAnimationFrame' $ \_ -> do
269+ v <- synchronously x
270+ void . liftIO $ f v
271+
272+instance Monad m => HasDocument (HydrationDomBuilderT t m) where
273+ {-# INLINABLE askDocument #-}
274+ askDocument = HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_document
275+
276+{-# INLINABLE askParent #-}
277+askParent :: Monad m => HydrationDomBuilderT t m Node
278+askParent = HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_parent
279+
280+{-# INLINABLE askEvents #-}
281+askEvents :: Monad m => HydrationDomBuilderT t m (Chan [DSum (EventTriggerRef t) TriggerInvocation])
282+askEvents = HydrationDomBuilderT . lift . lift $ TriggerEventT.askEvents
283+
284+localEnv :: Monad m => (HydrationDomBuilderEnv t m -> HydrationDomBuilderEnv t m) -> HydrationDomBuilderT t m a -> HydrationDomBuilderT t m a
285+localEnv f = HydrationDomBuilderT . local f . unHydrationDomBuilderT
286+
287+{-# INLINABLE append #-}
288+append :: MonadJSM m => DOM.Node -> HydrationDomBuilderT t m ()
289+append n = do
290+ p <- askParent
291+ liftJSM $ appendChild_ p n
292+ return ()
293+
294+data HydrationMode
295+ = HydrationMode_Hydrating
296+ | HydrationMode_Immediate
297+ deriving (Eq, Ord, Show)
298+
299+getHydrationMode :: MonadIO m => HydrationDomBuilderT t m HydrationMode
300+getHydrationMode = do
301+ ref <- HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_hydrationMode
302+ liftIO $ readIORef ref
303+
304+{-# INLINABLE textNodeInternal #-}
305+textNodeInternal :: (MonadJSM m, ToDOMString contents) => contents -> HydrationDomBuilderT t m DOM.Text
306+textNodeInternal !t = makeNodeInternal (askDocument >>= \doc -> liftJSM $ createTextNode doc t) DOM.Text
307+
308+{-# INLINABLE commentNodeInternal #-}
309+commentNodeInternal :: (MonadJSM m, ToDOMString contents) => contents -> HydrationDomBuilderT t m DOM.Comment
310+commentNodeInternal !t = makeNodeInternal (askDocument >>= \doc -> liftJSM $ createComment doc t) DOM.Comment
311+
312+{-# INLINABLE makeNodeInternal #-}
313+makeNodeInternal :: (MonadJSM m, IsNode node) => HydrationDomBuilderT t m node -> (DOM.JSVal -> node) -> HydrationDomBuilderT t m node
314+makeNodeInternal mkNode constructor = do
315+ doc <- askDocument
316+ parent <- askParent
317+ getHydrationMode >>= \case
318+ HydrationMode_Immediate -> do
319+ liftIO $ print $ "Not in hydration mode, creating node"
320+ n <- mkNode
321+ append $ toNode n
322+ return n
323+ HydrationMode_Hydrating -> do
324+ lastHydrationNodeRef <- HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_hydrationNode
325+ lastHydrationNode <- liftIO $ readIORef lastHydrationNodeRef
326+ let go mLastNode = do
327+ node <- maybe (Node.getFirstChildUnchecked parent) Node.getNextSiblingUnchecked mLastNode
328+ DOM.castTo constructor node >>= \case
329+ Nothing -> do
330+ liftIO $ putStrLn $ "Wrong node type, skipping"
331+ go (Just node)
332+ Just tn -> do
333+ liftIO $ print $ "Using existing node"
334+ return tn
335+ n <- go lastHydrationNode
336+ liftIO $ modifyIORef' lastHydrationNodeRef (\_ -> Just $ toNode n)
337+ return n
338+
339+-- skipToCommentId "prerender-start" (liftJSM $ createComment doc "rerender-start") $ \new -> \case
340+-- Nothing -> void $ append $ toNode new
341+-- Just node -> void $ liftJSM $ Node.replaceChild parent new node
342+
343+-- | s and e must both be children of the same node and s must precede e;
344+-- all nodes between s and e will be removed, but s and e will not be removed
345+deleteBetweenExclusive :: (MonadJSM m, IsNode start, IsNode end) => start -> end -> m ()
346+deleteBetweenExclusive s e = liftJSM $ do
347+ df <- createDocumentFragment =<< getOwnerDocumentUnchecked s
348+ extractBetweenExclusive df s e -- In many places in HydrationDomBuilderT, we assume that things always have a parent; by adding them to this DocumentFragment, we maintain that invariant
349+
350+-- | s and e must both be children of the same node and s must precede e; all
351+-- nodes between s and e will be moved into the given DocumentFragment, but s
352+-- and e will not be moved
353+extractBetweenExclusive :: (MonadJSM m, IsNode start, IsNode end) => DOM.DocumentFragment -> start -> end -> m ()
354+extractBetweenExclusive df s e = liftJSM $ do
355+ f <- eval ("(function(df,s,e) { var x; for(;;) { x = s['nextSibling']; if(e===x) { break; }; df['appendChild'](x); } })" :: Text)
356+ void $ call f f (df, s, e)
357+
358+-- | s and e must both be children of the same node and s must precede e;
359+-- s and all nodes between s and e will be removed, but e will not be removed
360+{-# INLINABLE deleteUpTo #-}
361+deleteUpTo :: (MonadJSM m, IsNode start, IsNode end) => start -> end -> m ()
362+deleteUpTo s e = do
363+ df <- createDocumentFragment =<< getOwnerDocumentUnchecked s
364+ extractUpTo df s e -- In many places in HydrationDomBuilderT, we assume that things always have a parent; by adding them to this DocumentFragment, we maintain that invariant
365+
366+extractUpTo :: (MonadJSM m, IsNode start, IsNode end) => DOM.DocumentFragment -> start -> end -> m ()
367+#ifdef ghcjs_HOST_OS
368+--NOTE: Although wrapping this javascript in a function seems unnecessary, GHCJS's optimizer will break it if it is entered without that wrapping (as of 2017-09-04)
369+foreign import javascript unsafe
370+ "(function() { var x = $2; while(x !== $3) { var y = x['nextSibling']; $1['appendChild'](x); x = y; } })()"
371+ extractUpTo_ :: DOM.DocumentFragment -> DOM.Node -> DOM.Node -> IO ()
372+extractUpTo df s e = liftJSM $ extractUpTo_ df (toNode s) (toNode e)
373+#else
374+extractUpTo df s e = liftJSM $ do
375+ f <- eval ("(function(df,s,e){ var x = s; var y; for(;;) { y = x['nextSibling']; df['appendChild'](x); if(e===y) { break; } x = y; } })" :: Text)
376+ void $ call f f (df, s, e)
377+#endif
378+
379+type SupportsHydrationDomBuilder t m = (Reflex t, MonadJSM m, MonadHold t m, MonadFix m, MonadReflexCreateTrigger t m, MonadRef m, Ref m ~ Ref JSM, Adjustable t m, PrimMonad m, PerformEvent t m, MonadJSM (Performable m))
380+
381+{-# INLINABLE collectUpTo #-}
382+collectUpTo :: (MonadJSM m, IsNode start, IsNode end) => start -> end -> m DOM.DocumentFragment
383+collectUpTo s e = do
384+ currentParent <- getParentNodeUnchecked e -- May be different than it was at initial construction, e.g., because the parent may have dumped us in from a DocumentFragment
385+ collectUpToGivenParent currentParent s e
386+
387+{-# INLINABLE collectUpToGivenParent #-}
388+collectUpToGivenParent :: (MonadJSM m, IsNode parent, IsNode start, IsNode end) => parent -> start -> end -> m DOM.DocumentFragment
389+collectUpToGivenParent currentParent s e = do
390+ doc <- getOwnerDocumentUnchecked currentParent
391+ df <- createDocumentFragment doc
392+ extractUpTo df s e
393+ return df
394+
395+{-# INLINABLE wrap #-}
396+wrap
397+ :: forall m er t. (Reflex t, MonadJSM m, MonadReflexCreateTrigger t m, DomRenderHook t m)
398+ => Chan [DSum (EventTriggerRef t) TriggerInvocation]
399+ -> RawElement GhcjsDomSpace
400+ -> RawElementConfig er t HydrationDomSpace
401+-- -> m (Event t (DMap (WrapArg er EventName) Identity))
402+ -> m (EventSelector t (WrapArg er EventName))
403+wrap events e cfg = do
404+ liftIO $ putStrLn $ "called wrap"
405+ forM_ (_rawElementConfig_modifyAttributes cfg) $ \modifyAttrs -> requestDomAction_ $ ffor modifyAttrs $ imapM_ $ \(AttributeName mAttrNamespace n) mv -> case mAttrNamespace of
406+ Nothing -> maybe (removeAttribute e n) (setAttribute e n) mv
407+ Just ns -> maybe (removeAttributeNS e (Just ns) n) (setAttributeNS e (Just ns) n) mv
408+ eventTriggerRefs :: DMap EventName (EventFilterTriggerRef t er) <- liftJSM $ fmap DMap.fromList $ forM (DMap.toList $ _ghcjsEventSpec_filters $ _rawElementConfig_eventSpec cfg) $ \(en :=> GhcjsEventFilter f) -> do
409+ triggerRef <- liftIO $ newIORef Nothing
410+ _ <- elementOnEventName en e $ do --TODO: Something safer than this cast
411+ evt <- DOM.event
412+ (flags, k) <- liftJSM $ f $ GhcjsDomEvent evt
413+ when (_eventFlags_preventDefault flags) $ withIsEvent en DOM.preventDefault
414+ case _eventFlags_propagation flags of
415+ Propagation_Continue -> return ()
416+ Propagation_Stop -> withIsEvent en DOM.stopPropagation
417+ Propagation_StopImmediate -> withIsEvent en DOM.stopImmediatePropagation
418+ mv <- liftJSM k --TODO: Only do this when the event is subscribed
419+ liftIO $ forM_ mv $ \v -> writeChan events [EventTriggerRef triggerRef :=> TriggerInvocation v (return ())]
420+ return $ en :=> EventFilterTriggerRef triggerRef
421+ es <- do
422+ let h :: GhcjsEventHandler er
423+ !h = _ghcjsEventSpec_handler $ _rawElementConfig_eventSpec cfg -- Note: this needs to be done strictly and outside of the newFanEventWithTrigger, so that the newFanEventWithTrigger doesn't retain the entire cfg, which can cause a cyclic dependency that the GC won't be able to clean up
424+ ctx <- askJSM
425+
426+ newFanEventWithTrigger $ \(WrapArg en) t ->
427+ case DMap.lookup en eventTriggerRefs of
428+ Just (EventFilterTriggerRef r) -> do
429+ writeIORef r $ Just t
430+ return $ do
431+ writeIORef r Nothing
432+ Nothing -> (`runJSM` ctx) <$> (`runJSM` ctx) (elementOnEventName en e $ do
433+ evt <- DOM.event
434+ mv <- lift $ unGhcjsEventHandler h (en, GhcjsDomEvent evt)
435+ case mv of
436+ Nothing -> return ()
437+ Just v -> liftIO $ do
438+ --TODO: I don't think this is quite right: if a new trigger is created between when this is enqueued and when it fires, this may not work quite right
439+ ref <- newIORef $ Just t
440+ writeChan events [EventTriggerRef ref :=> TriggerInvocation v (return ())])
441+
442+ return es
443+
444+{-# INLINABLE makeElement #-}
445+makeElement :: forall er t m a. (MonadJSM m, MonadHold t m, MonadFix m, MonadReflexCreateTrigger t m, Adjustable t m, Ref m ~ IORef, PerformEvent t m, MonadJSM (Performable m), MonadRef m) => Text -> ElementConfig er t HydrationDomSpace -> HydrationDomBuilderT t m a -> HydrationDomBuilderT t m ((Element er HydrationDomSpace t, a), DOM.Element)
446+makeElement elementTag cfg child = do
447+ doc <- askDocument
448+ parent <- askParent
449+ let buildElement = do
450+ e <- uncheckedCastTo DOM.Element <$> case cfg ^. namespace of
451+ Nothing -> createElement doc elementTag
452+ Just ens -> createElementNS doc (Just ens) elementTag
453+ iforM_ (cfg ^. initialAttributes) $ \(AttributeName mAttrNamespace n) v -> case mAttrNamespace of
454+ Nothing -> setAttribute e n v
455+ Just ans -> setAttributeNS e (Just ans) n v
456+ pure e
457+ -- TODO: prerender needs completely replacing
458+ e <- makeNodeInternal buildElement DOM.Element
459+ -- Run the child builder with updated parent and previous sibling references
460+ childNode <- liftIO $ newIORef Nothing
461+ result <- localEnv (\env -> env { _hydrationDomBuilderEnv_parent = toNode e, _hydrationDomBuilderEnv_hydrationNode = childNode }) child
462+ events <- askEvents
463+ env <- HydrationDomBuilderT ask
464+ (ese, fire) <- newTriggerEvent
465+ let beh = pure $ void . flip runHydrationDomBuilderT env $ do
466+ es <- wrap events e $ extractRawElementConfig cfg
467+ liftIO $ fire es
468+ hydrationResultRef <- HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_hydrationResult
469+ liftIO $ modifyIORef' hydrationResultRef (beh :)
470+ collapsed <- switchHold never $ ffor ese $ \es -> DMap.singleton (WrapArg Click) . Identity <$> (select es (WrapArg Click))
471+ let evts = fan collapsed
472+ return ((Element evts (), result), e)
473+
474+instance SupportsHydrationDomBuilder t m => NotReady t (HydrationDomBuilderT t m) where
475+ notReadyUntil e = do
476+ eOnce <- headE e
477+ env <- HydrationDomBuilderT ask
478+ let unreadyChildren = _hydrationDomBuilderEnv_unreadyChildren env
479+ liftIO $ modifyIORef' unreadyChildren succ
480+ let ready = do
481+ old <- liftIO $ readIORef unreadyChildren
482+ let new = pred old
483+ liftIO $ writeIORef unreadyChildren $! new
484+ when (new == 0) $ _hydrationDomBuilderEnv_commitAction env
485+ requestDomAction_ $ ready <$ eOnce
486+ notReady = do
487+ env <- HydrationDomBuilderT ask
488+ let unreadyChildren = _hydrationDomBuilderEnv_unreadyChildren env
489+ liftIO $ modifyIORef' unreadyChildren succ
490+
491+instance SupportsHydrationDomBuilder t m => DomBuilder t (HydrationDomBuilderT t m) where
492+ type DomBuilderSpace (HydrationDomBuilderT t m) = HydrationDomSpace
493+ {-# INLINABLE textNode #-}
494+ textNode (TextNodeConfig initialContents mSetContents) = do
495+ n <- textNodeInternal initialContents
496+ mapM_ (requestDomAction_ . fmap (setNodeValue n . Just)) mSetContents
497+ return $ TextNode ()
498+ {-# INLINABLE commentNode #-}
499+ commentNode (CommentNodeConfig initialContents mSetContents) = do
500+ n <- commentNodeInternal initialContents
501+ mapM_ (requestDomAction_ . fmap (setNodeValue n . Just)) mSetContents
502+ return $ CommentNode ()
503+ {-# INLINABLE element #-}
504+ element elementTag cfg child = fst <$> makeElement elementTag cfg child
505+ {-# INLINABLE inputElement #-}
506+ inputElement cfg = do
507+ ((e, _), domElement) <- makeElement "input" (cfg ^. inputElementConfig_elementConfig) $ return ()
508+ let domInputElement = uncheckedCastTo DOM.HTMLInputElement domElement
509+ Input.setValue domInputElement $ cfg ^. inputElementConfig_initialValue
510+ v0 <- Input.getValue domInputElement
511+ let getMyValue = Input.getValue domInputElement
512+ valueChangedByUI <- requestDomAction $ liftJSM getMyValue <$ Reflex.select (_element_events e) (WrapArg Input)
513+ valueChangedBySetValue <- case _inputElementConfig_setValue cfg of
514+ Nothing -> return never
515+ Just eSetValue -> requestDomAction $ ffor eSetValue $ \v' -> do
516+ Input.setValue domInputElement v'
517+ getMyValue -- We get the value after setting it in case the browser has mucked with it somehow
518+ v <- holdDyn v0 $ leftmost
519+ [ valueChangedBySetValue
520+ , valueChangedByUI
521+ ]
522+ Input.setChecked domInputElement $ _inputElementConfig_initialChecked cfg
523+ checkedChangedByUI <- wrapDomEvent domInputElement (`on` Events.click) $ do
524+ Input.getChecked domInputElement
525+ checkedChangedBySetChecked <- case _inputElementConfig_setChecked cfg of
526+ Nothing -> return never
527+ Just eNewchecked -> requestDomAction $ ffor eNewchecked $ \newChecked -> do
528+ oldChecked <- Input.getChecked domInputElement
529+ Input.setChecked domInputElement newChecked
530+ return $ if newChecked /= oldChecked
531+ then Just newChecked
532+ else Nothing
533+ c <- holdDyn (_inputElementConfig_initialChecked cfg) $ leftmost
534+ [ fmapMaybe id checkedChangedBySetChecked
535+ , checkedChangedByUI
536+ ]
537+ let initialFocus = False --TODO: Is this correct?
538+ hasFocus <- holdDyn initialFocus $ leftmost
539+ [ False <$ Reflex.select (_element_events e) (WrapArg Blur)
540+ , True <$ Reflex.select (_element_events e) (WrapArg Focus)
541+ ]
542+ files <- holdDyn mempty <=< wrapDomEvent domInputElement (`on` Events.change) $ do
543+ mfiles <- Input.getFiles domInputElement
544+ let getMyFiles xs = fmap catMaybes . mapM (FileList.item xs) . flip take [0..] . fromIntegral =<< FileList.getLength xs
545+ maybe (return []) getMyFiles mfiles
546+ checked <- holdUniqDyn c
547+ return $ InputElement
548+ { _inputElement_value = v
549+ , _inputElement_checked = checked
550+ , _inputElement_checkedChange = checkedChangedByUI
551+ , _inputElement_input = valueChangedByUI
552+ , _inputElement_hasFocus = hasFocus
553+ , _inputElement_element = e
554+ , _inputElement_raw = ()--domInputElement
555+ , _inputElement_files = files
556+ }
557+ {-# INLINABLE textAreaElement #-}
558+ textAreaElement cfg = do --TODO
559+ ((e, _), domElement) <- makeElement "textarea" (cfg ^. textAreaElementConfig_elementConfig) $ return ()
560+ let domTextAreaElement = uncheckedCastTo DOM.HTMLTextAreaElement domElement
561+ TextArea.setValue domTextAreaElement $ cfg ^. textAreaElementConfig_initialValue
562+ v0 <- TextArea.getValue domTextAreaElement
563+ let getMyValue = TextArea.getValue domTextAreaElement
564+ valueChangedByUI <- requestDomAction $ liftJSM getMyValue <$ Reflex.select (_element_events e) (WrapArg Input)
565+ valueChangedBySetValue <- case _textAreaElementConfig_setValue cfg of
566+ Nothing -> return never
567+ Just eSetValue -> requestDomAction $ ffor eSetValue $ \v' -> do
568+ TextArea.setValue domTextAreaElement v'
569+ getMyValue -- We get the value after setting it in case the browser has mucked with it somehow
570+ v <- holdDyn v0 $ leftmost
571+ [ valueChangedBySetValue
572+ , valueChangedByUI
573+ ]
574+ hasFocus <- mkHasFocus e
575+ return $ TextAreaElement
576+ { _textAreaElement_value = v
577+ , _textAreaElement_input = valueChangedByUI
578+ , _textAreaElement_hasFocus = hasFocus
579+ , _textAreaElement_element = e
580+ , _textAreaElement_raw = ()--domTextAreaElement
581+ }
582+ {-# INLINABLE selectElement #-}
583+ selectElement cfg child = do
584+ ((e, result), domElement) <- makeElement "select" (cfg ^. selectElementConfig_elementConfig) child
585+ let domSelectElement = uncheckedCastTo DOM.HTMLSelectElement domElement
586+ Select.setValue domSelectElement $ cfg ^. selectElementConfig_initialValue
587+ v0 <- Select.getValue domSelectElement
588+ let getMyValue = Select.getValue domSelectElement
589+ valueChangedByUI <- requestDomAction $ liftJSM getMyValue <$ Reflex.select (_element_events e) (WrapArg Change)
590+ valueChangedBySetValue <- case _selectElementConfig_setValue cfg of
591+ Nothing -> return never
592+ Just eSetValue -> requestDomAction $ ffor eSetValue $ \v' -> do
593+ Select.setValue domSelectElement v'
594+ getMyValue -- We get the value after setting it in case the browser has mucked with it somehow
595+ v <- holdDyn v0 $ leftmost
596+ [ valueChangedBySetValue
597+ , valueChangedByUI
598+ ]
599+ hasFocus <- mkHasFocus e
600+ let wrapped = SelectElement
601+ { _selectElement_value = v
602+ , _selectElement_change = valueChangedByUI
603+ , _selectElement_hasFocus = hasFocus
604+ , _selectElement_element = e
605+ , _selectElement_raw = ()--domSelectElement
606+ }
607+ return (wrapped, result)
608+ placeRawElement _ = return () -- append . toNode
609+ wrapRawElement () _ = return $ Element (EventSelector $ const never) ()
610+
611+data FragmentState
612+ = FragmentState_Unmounted
613+ | FragmentState_Mounted (DOM.Text, DOM.Text)
614+
615+data HydrationDomFragment = HydrationDomFragment
616+ { _hydrationDomFragment_document :: DOM.DocumentFragment
617+ , _hydrationDomFragment_state :: IORef FragmentState
618+ }
619+
620+extractFragment :: MonadJSM m => HydrationDomFragment -> m ()
621+extractFragment fragment = do
622+ state <- liftIO $ readIORef $ _hydrationDomFragment_state fragment
623+ case state of
624+ FragmentState_Unmounted -> return ()
625+ FragmentState_Mounted (before, after) -> do
626+ extractBetweenExclusive (_hydrationDomFragment_document fragment) before after
627+ liftIO $ writeIORef (_hydrationDomFragment_state fragment) FragmentState_Unmounted
628+
629+instance SupportsHydrationDomBuilder t m => MountableDomBuilder t (HydrationDomBuilderT t m) where
630+ type DomFragment (HydrationDomBuilderT t m) = HydrationDomFragment
631+ buildDomFragment w = do
632+ df <- createDocumentFragment =<< askDocument
633+ result <- flip localEnv w $ \env -> env
634+ { _hydrationDomBuilderEnv_parent = toNode df
635+ }
636+ state <- liftIO $ newIORef FragmentState_Unmounted
637+ return (HydrationDomFragment df state, result)
638+ mountDomFragment fragment setFragment = do
639+ parent <- askParent
640+ extractFragment fragment
641+ before <- textNodeInternal ("" :: Text)
642+ appendChild_ parent $ _hydrationDomFragment_document fragment
643+ after <- textNodeInternal ("" :: Text)
644+ xs <- foldDyn (\new (previous, _) -> (new, Just previous)) (fragment, Nothing) setFragment
645+ requestDomAction_ $ ffor (updated xs) $ \(childFragment, Just previousFragment) -> do
646+ extractFragment previousFragment
647+ extractFragment childFragment
648+ insertBefore (_hydrationDomFragment_document childFragment) after
649+ liftIO $ writeIORef (_hydrationDomFragment_state childFragment) $ FragmentState_Mounted (before, after)
650+ liftIO $ writeIORef (_hydrationDomFragment_state fragment) $ FragmentState_Mounted (before, after)
651+
652+skipToCommentId :: MonadJSM m => Text -> HydrationDomBuilderT t m (DOM.Comment, Text)
653+skipToCommentId prefix = do
654+ doc <- askDocument
655+ parent <- askParent
656+ lastHydrationNodeRef <- HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_hydrationNode
657+ lastHydrationNode <- liftIO $ readIORef lastHydrationNodeRef
658+ let go mLastNode = do
659+ node <- maybe (Node.getFirstChildUnchecked parent) Node.getNextSiblingUnchecked mLastNode
660+ DOM.castTo DOM.Comment node >>= \case
661+ Just comment -> do
662+ commentText <- Node.getTextContentUnchecked comment
663+ case T.stripPrefix prefix commentText of
664+ Just key -> pure (comment, key)
665+ Nothing -> do
666+ liftIO $ putStrLn $ "Bad key in comment, skipping"
667+ go (Just node)
668+ Nothing -> do
669+ commentText <- Node.getTextContentUnchecked node
670+ liftIO $ putStrLn $ "Not a comment, skipping"
671+ liftIO $ putStrLn commentText
672+ go (Just node)
673+ (a, key) <- go lastHydrationNode
674+ liftIO $ modifyIORef' lastHydrationNodeRef (\_ -> Just $ toNode a)
675+ pure (a, key)
676+
677+skipToReplaceStart :: MonadJSM m => HydrationDomBuilderT t m (DOM.Comment, Text)
678+skipToReplaceStart = skipToCommentId "replace-start"
679+
680+skipToReplaceEnd :: MonadJSM m => Text -> HydrationDomBuilderT t m DOM.Comment
681+skipToReplaceEnd key = fmap fst $ skipToCommentId $ "replace-end" <> key
682+
683+instance (Reflex t, Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, PrimMonad m, Ref m ~ IORef, PerformEvent t m, MonadReflexCreateTrigger t m, MonadJSM (Performable m), MonadRef m) => Adjustable t (HydrationDomBuilderT t m) where
684+ {-# INLINABLE runWithReplace #-}
685+ runWithReplace a0 a' = do
686+ initialEnv <- HydrationDomBuilderT ask
687+ (before, beforeKey) <- skipToReplaceStart
688+ let hydrating = _hydrationDomBuilderEnv_hydrationMode initialEnv
689+ let parentUnreadyChildren = _hydrationDomBuilderEnv_unreadyChildren initialEnv
690+ haveEverBeenReady <- liftIO $ newIORef False
691+ currentCohort <- liftIO $ newIORef (-1 :: Int) -- Equal to the cohort currently in the DOM
692+ let myCommitAction = do
693+ liftIO (readIORef haveEverBeenReady) >>= \case
694+ True -> return ()
695+ False -> do
696+ liftIO $ writeIORef haveEverBeenReady True
697+ old <- liftIO $ readIORef parentUnreadyChildren
698+ let new = pred old
699+ liftIO $ writeIORef parentUnreadyChildren $! new
700+ when (new == 0) $ _hydrationDomBuilderEnv_commitAction initialEnv
701+ -- We draw 'after' in this roundabout way to avoid using MonadFix
702+ doc <- askDocument
703+ parent <- askParent
704+ after <- skipToReplaceEnd beforeKey
705+ let drawInitialChild = do
706+ liftIO $ putStrLn "drawInitialChild"
707+ df <- createDocumentFragment doc
708+ unreadyChildren <- liftIO $ newIORef 0
709+ hydrationNode <- liftIO $ newIORef Nothing
710+ hydrationResult <- liftIO $ newIORef []
711+ localHydrationMode <- liftIO $ newIORef HydrationMode_Immediate
712+ result <- runReaderT (unHydrationDomBuilderT a0) initialEnv
713+ { _hydrationDomBuilderEnv_unreadyChildren = unreadyChildren
714+ , _hydrationDomBuilderEnv_commitAction = myCommitAction
715+ , _hydrationDomBuilderEnv_hydrationNode = hydrationNode
716+ , _hydrationDomBuilderEnv_parent = toNode df
717+ , _hydrationDomBuilderEnv_hydrationMode = localHydrationMode
718+ }
719+ liftIO $ readIORef unreadyChildren >>= \case
720+ 0 -> writeIORef haveEverBeenReady True
721+ _ -> modifyIORef' parentUnreadyChildren succ
722+ liftIO $ putStrLn "drawInitialChild: end"
723+ return result
724+ a'' <- numberOccurrences a'
725+ (result0, child') <- HydrationDomBuilderT $ lift $ runWithReplace drawInitialChild $ ffor a'' $ \(cohortId, child) -> do
726+ df <- createDocumentFragment doc
727+ unreadyChildren <- liftIO $ newIORef 0
728+ let commitAction = do
729+ c <- liftIO $ readIORef currentCohort
730+ when (c <= cohortId) $ do -- If a newer cohort has already been committed, just ignore this
731+ deleteBetweenExclusive before after
732+ insertBefore df after
733+ liftIO $ writeIORef currentCohort cohortId
734+ myCommitAction
735+ hydrationNode <- liftIO $ newIORef (Just $ toNode before)
736+ h <- liftIO $ readIORef hydrating
737+ let child' = runReaderT (unHydrationDomBuilderT child) $ initialEnv
738+ { _hydrationDomBuilderEnv_unreadyChildren = unreadyChildren
739+ , _hydrationDomBuilderEnv_commitAction = commitAction
740+ , _hydrationDomBuilderEnv_hydrationNode = hydrationNode
741+ , _hydrationDomBuilderEnv_parent = toNode df
742+ }
743+ uc <- liftIO $ readIORef unreadyChildren
744+ result <- case h of
745+ HydrationMode_Hydrating -> pure Nothing
746+ HydrationMode_Immediate -> Just <$> child'
747+ let commitActionToRunNow = if uc == 0
748+ then Just $ commitAction
749+ else Nothing -- A child will run it when unreadyChildren is decremented to 0
750+ commitActionToRunNow' = if h == HydrationMode_Hydrating then Nothing else commitActionToRunNow
751+ hydrationAction = if h == HydrationMode_Hydrating then Just child' else Nothing
752+ return (hydrationAction, commitActionToRunNow', result)
753+ let drawInitialChild' = do
754+ unreadyChildren <- liftIO $ newIORef 0
755+ hydrationNode <- liftIO $ newIORef (Just $ toNode before)
756+ result <- runReaderT (unHydrationDomBuilderT a0) initialEnv
757+ { _hydrationDomBuilderEnv_unreadyChildren = unreadyChildren
758+ , _hydrationDomBuilderEnv_commitAction = myCommitAction
759+ , _hydrationDomBuilderEnv_hydrationNode = hydrationNode
760+ }
761+ return result
762+ env <- HydrationDomBuilderT ask
763+ let f :: RequesterT t JSM Identity (TriggerEventT t m) x -> m ()
764+ f = void . flip runHydrationDomBuilderT env . HydrationDomBuilderT . lift
765+ beh <- hold (f drawInitialChild') (fmapMaybe (\(h,_,_) -> f <$> h) child')
766+ hydrationResultRef <- HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_hydrationResult
767+ liftIO $ modifyIORef' hydrationResultRef (beh :)
768+ requestDomAction_ $ fmapMaybe (\(_,c,_) -> c) child'
769+ return (result0, fmapMaybe (\(_,_,r) -> r) child')
770+
771+ {-# INLINABLE traverseIntMapWithKeyWithAdjust #-}
772+ traverseIntMapWithKeyWithAdjust = traverseIntMapWithKeyWithAdjust'
773+ {-# INLINABLE traverseDMapWithKeyWithAdjust #-}
774+ traverseDMapWithKeyWithAdjust = traverseDMapWithKeyWithAdjust'
775+ {-# INLINABLE traverseDMapWithKeyWithAdjustWithMove #-}
776+ traverseDMapWithKeyWithAdjustWithMove = do
777+ let updateChildUnreadiness (p :: PatchDMapWithMove k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v')) old = do
778+ let new :: forall a. k a -> PatchDMapWithMove.NodeInfo k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v') a -> IO (PatchDMapWithMove.NodeInfo k (Constant (IORef (ChildReadyState k))) a)
779+ new k = PatchDMapWithMove.nodeInfoMapFromM $ \case
780+ PatchDMapWithMove.From_Insert (Compose (_, _, sRef, _)) -> do
781+ readIORef sRef >>= \case
782+ ChildReadyState_Ready -> return PatchDMapWithMove.From_Delete
783+ ChildReadyState_Unready _ -> do
784+ writeIORef sRef $ ChildReadyState_Unready $ Just $ Some.This k
785+ return $ PatchDMapWithMove.From_Insert $ Constant sRef
786+ PatchDMapWithMove.From_Delete -> return PatchDMapWithMove.From_Delete
787+ PatchDMapWithMove.From_Move fromKey -> return $ PatchDMapWithMove.From_Move fromKey
788+ deleteOrMove :: forall a. k a -> Product (Constant (IORef (ChildReadyState k))) (ComposeMaybe k) a -> IO (Constant () a)
789+ deleteOrMove _ (Pair (Constant sRef) (ComposeMaybe mToKey)) = do
790+ writeIORef sRef $ ChildReadyState_Unready $ Some.This <$> mToKey -- This will be Nothing if deleting, and Just if moving, so it works out in both cases
791+ return $ Constant ()
792+ p' <- fmap unsafePatchDMapWithMove $ DMap.traverseWithKey new $ unPatchDMapWithMove p
793+ _ <- DMap.traverseWithKey deleteOrMove $ PatchDMapWithMove.getDeletionsAndMoves p old
794+ return $ applyAlways p' old
795+ hoistTraverseWithKeyWithAdjust traverseDMapWithKeyWithAdjustWithMove mapPatchDMapWithMove updateChildUnreadiness $ \placeholders lastPlaceholderRef (p_ :: PatchDMapWithMove k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v')) -> do
796+ let p = unPatchDMapWithMove p_
797+ phsBefore <- liftIO $ readIORef placeholders
798+ lastPlaceholder <- liftIO $ readIORef lastPlaceholderRef
799+ let collectIfMoved :: forall a. k a -> PatchDMapWithMove.NodeInfo k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v') a -> JSM (Constant (Maybe DOM.DocumentFragment) a)
800+ collectIfMoved k e = do
801+ let mThisPlaceholder = Map.lookup (Some.This k) phsBefore -- Will be Nothing if this element wasn't present before
802+ nextPlaceholder = maybe lastPlaceholder snd $ Map.lookupGT (Some.This k) phsBefore
803+ case isJust $ getComposeMaybe $ PatchDMapWithMove._nodeInfo_to e of
804+ False -> do
805+ mapM_ (`deleteUpTo` nextPlaceholder) mThisPlaceholder
806+ return $ Constant Nothing
807+ True -> do
808+ Constant <$> mapM (`collectUpTo` nextPlaceholder) mThisPlaceholder
809+ collected <- DMap.traverseWithKey collectIfMoved p
810+ let !phsAfter = fromMaybe phsBefore $ apply (weakenPatchDMapWithMoveWith (\(Compose (_, ph, _, _)) -> ph) p_) phsBefore --TODO: Don't recompute this
811+ let placeFragment :: forall a. k a -> PatchDMapWithMove.NodeInfo k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v') a -> JSM (Constant () a)
812+ placeFragment k e = do
813+ let nextPlaceholder = maybe lastPlaceholder snd $ Map.lookupGT (Some.This k) phsAfter
814+ case PatchDMapWithMove._nodeInfo_from e of
815+ PatchDMapWithMove.From_Insert (Compose (df, _, _, _)) -> do
816+ df `insertBefore` nextPlaceholder
817+ PatchDMapWithMove.From_Delete -> do
818+ return ()
819+ PatchDMapWithMove.From_Move fromKey -> do
820+ Just (Constant mdf) <- return $ DMap.lookup fromKey collected
821+ mapM_ (`insertBefore` nextPlaceholder) mdf
822+ return $ Constant ()
823+ mapM_ (\(k :=> v) -> void $ placeFragment k v) $ DMap.toDescList p -- We need to go in reverse order here, to make sure the placeholders are in the right spot at the right time
824+ liftIO $ writeIORef placeholders $! phsAfter
825+
826+{-# INLINABLE traverseDMapWithKeyWithAdjust' #-}
827+traverseDMapWithKeyWithAdjust' :: forall t m (k :: * -> *) v v'. (Adjustable t m, MonadHold t m, MonadFix m, MonadIO m, MonadJSM m, PrimMonad m, DMap.GCompare k) => (forall a. k a -> v a -> HydrationDomBuilderT t m (v' a)) -> DMap k v -> Event t (PatchDMap k v) -> HydrationDomBuilderT t m (DMap k v', Event t (PatchDMap k v'))
828+traverseDMapWithKeyWithAdjust' = do
829+ let updateChildUnreadiness (p :: PatchDMap k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v')) old = do
830+ let new :: forall a. k a -> ComposeMaybe (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v') a -> IO (ComposeMaybe (Constant (IORef (ChildReadyState k))) a)
831+ new k (ComposeMaybe m) = ComposeMaybe <$> case m of
832+ Nothing -> return Nothing
833+ Just (Compose (_, _, sRef, _)) -> do
834+ readIORef sRef >>= \case
835+ ChildReadyState_Ready -> return Nothing -- Delete this child, since it's ready
836+ ChildReadyState_Unready _ -> do
837+ writeIORef sRef $ ChildReadyState_Unready $ Just $ Some.This k
838+ return $ Just $ Constant sRef
839+ delete _ (Constant sRef) = do
840+ writeIORef sRef $ ChildReadyState_Unready Nothing
841+ return $ Constant ()
842+ p' <- fmap PatchDMap $ DMap.traverseWithKey new $ unPatchDMap p
843+ _ <- DMap.traverseWithKey delete $ PatchDMap.getDeletions p old
844+ return $ applyAlways p' old
845+ hoistTraverseWithKeyWithAdjust traverseDMapWithKeyWithAdjust mapPatchDMap updateChildUnreadiness $ \placeholders lastPlaceholderRef (PatchDMap p) -> do
846+ phs <- liftIO $ readIORef placeholders
847+ forM_ (DMap.toList p) $ \(k :=> ComposeMaybe mv) -> do
848+ lastPlaceholder <- liftIO $ readIORef lastPlaceholderRef
849+ let nextPlaceholder = maybe lastPlaceholder snd $ Map.lookupGT (Some.This k) phs
850+ forM_ (Map.lookup (Some.This k) phs) $ \thisPlaceholder -> thisPlaceholder `deleteUpTo` nextPlaceholder
851+ forM_ mv $ \(Compose (df, _, _, _)) -> df `insertBefore` nextPlaceholder
852+ liftIO $ writeIORef placeholders $! fromMaybe phs $ apply (weakenPatchDMapWith (\(Compose (_, ph, _, _)) -> ph) $ PatchDMap p) phs
853+
854+{-# INLINABLE traverseIntMapWithKeyWithAdjust' #-}
855+traverseIntMapWithKeyWithAdjust' :: forall t m v v'. (Adjustable t m, MonadHold t m, MonadFix m, MonadIO m, MonadJSM m, PrimMonad m) => (IntMap.Key -> v -> HydrationDomBuilderT t m v') -> IntMap v -> Event t (PatchIntMap v) -> HydrationDomBuilderT t m (IntMap v', Event t (PatchIntMap v'))
856+traverseIntMapWithKeyWithAdjust' = do
857+ let updateChildUnreadiness (p@(PatchIntMap pInner) :: PatchIntMap (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v')) old = do
858+ let new :: IntMap.Key -> Maybe (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v') -> IO (Maybe (IORef ChildReadyStateInt))
859+ new k m = case m of
860+ Nothing -> return Nothing
861+ Just (_, _, sRef, _) -> do
862+ readIORef sRef >>= \case
863+ ChildReadyStateInt_Ready -> return Nothing -- Delete this child, since it's ready
864+ ChildReadyStateInt_Unready _ -> do
865+ writeIORef sRef $ ChildReadyStateInt_Unready $ Just k
866+ return $ Just sRef
867+ delete _ sRef = do
868+ writeIORef sRef $ ChildReadyStateInt_Unready Nothing
869+ return ()
870+ p' <- PatchIntMap <$> IntMap.traverseWithKey new pInner
871+ _ <- IntMap.traverseWithKey delete $ FastMutableIntMap.getDeletions p old
872+ return $ applyAlways p' old
873+ hoistTraverseIntMapWithKeyWithAdjust traverseIntMapWithKeyWithAdjust updateChildUnreadiness $ \placeholders lastPlaceholderRef (PatchIntMap p) -> do
874+ phs <- liftIO $ readIORef placeholders
875+ forM_ (IntMap.toList p) $ \(k, mv) -> do
876+ lastPlaceholder <- liftIO $ readIORef lastPlaceholderRef
877+ let nextPlaceholder = maybe lastPlaceholder snd $ IntMap.lookupGT k phs
878+ forM_ (IntMap.lookup k phs) $ \thisPlaceholder -> thisPlaceholder `deleteUpTo` nextPlaceholder
879+ forM_ mv $ \(df, _, _, _) -> df `insertBefore` nextPlaceholder
880+ liftIO $ writeIORef placeholders $! fromMaybe phs $ apply ((\(_, ph, _, _) -> ph) <$> PatchIntMap p) phs
881+
882+{-# INLINE hoistTraverseIntMapWithKeyWithAdjust #-}
883+hoistTraverseIntMapWithKeyWithAdjust :: forall v v' t m p.
884+ ( Adjustable t m
885+ , MonadIO m
886+ , MonadJSM m
887+ , MonadFix m
888+ , PrimMonad m
889+ , Monoid (p (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v'))
890+ , Functor p
891+ )
892+ => ( (IntMap.Key -> v -> RequesterT t JSM Identity (TriggerEventT t m) (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v'))
893+ -> IntMap v
894+ -> Event t (p v)
895+ -> RequesterT t JSM Identity (TriggerEventT t m) (IntMap (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v'), Event t (p (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v')))
896+ ) -- ^ The base monad's traversal
897+ -> (p (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v') -> IntMap (IORef ChildReadyStateInt) -> IO (IntMap (IORef ChildReadyStateInt))) -- ^ Given a patch for the children DOM elements, produce a patch for the childrens' unreadiness state
898+ -> (IORef (IntMap DOM.Text) -> IORef DOM.Text -> p (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v') -> JSM ()) -- ^ Apply a patch to the DOM
899+ -> (IntMap.Key -> v -> HydrationDomBuilderT t m v')
900+ -> IntMap v
901+ -> Event t (p v)
902+ -> HydrationDomBuilderT t m (IntMap v', Event t (p v'))
903+hoistTraverseIntMapWithKeyWithAdjust base updateChildUnreadiness applyDomUpdate_ f dm0 dm' = do
904+ initialEnv <- HydrationDomBuilderT ask
905+ let parentUnreadyChildren = _hydrationDomBuilderEnv_unreadyChildren initialEnv
906+ pendingChange :: IORef (IntMap (IORef ChildReadyStateInt), p (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v')) <- liftIO $ newIORef mempty
907+ haveEverBeenReady <- liftIO $ newIORef False
908+ placeholders <- liftIO $ newIORef $ error "placeholders not yet initialized"
909+ lastPlaceholderRef <- liftIO $ newIORef $ error "lastPlaceholderRef not yet initialized"
910+ let applyDomUpdate p = do
911+ applyDomUpdate_ placeholders lastPlaceholderRef p
912+ markSelfReady
913+ liftIO $ writeIORef pendingChange $! mempty
914+ markSelfReady = do
915+ liftIO (readIORef haveEverBeenReady) >>= \case
916+ True -> return ()
917+ False -> do
918+ liftIO $ writeIORef haveEverBeenReady True
919+ old <- liftIO $ readIORef parentUnreadyChildren
920+ let new = pred old
921+ liftIO $ writeIORef parentUnreadyChildren $! new
922+ when (new == 0) $ _hydrationDomBuilderEnv_commitAction initialEnv
923+ markChildReady :: IORef ChildReadyStateInt -> JSM ()
924+ markChildReady childReadyState = do
925+ liftIO (readIORef childReadyState) >>= \case
926+ ChildReadyStateInt_Ready -> return ()
927+ ChildReadyStateInt_Unready countedAt -> do
928+ liftIO $ writeIORef childReadyState ChildReadyStateInt_Ready
929+ case countedAt of
930+ Nothing -> return ()
931+ Just k -> do -- This child has been counted as unready, so we need to remove it from the unready set
932+ (oldUnready, p) <- liftIO $ readIORef pendingChange
933+ when (not $ IntMap.null oldUnready) $ do -- This shouldn't actually ever be null
934+ let newUnready = IntMap.delete k oldUnready
935+ liftIO $ writeIORef pendingChange (newUnready, p)
936+ when (IntMap.null newUnready) $ do
937+ applyDomUpdate p
938+ (children0, children') <- HydrationDomBuilderT $ lift $ base (\k v -> drawChildUpdateInt initialEnv markChildReady $ f k v) dm0 dm'
939+ let processChild k (_, _, sRef, _) = do
940+ readIORef sRef >>= \case
941+ ChildReadyStateInt_Ready -> return Nothing
942+ ChildReadyStateInt_Unready _ -> do
943+ writeIORef sRef $ ChildReadyStateInt_Unready $ Just k
944+ return $ Just sRef
945+ initialUnready <- liftIO $ IntMap.mapMaybe id <$> IntMap.traverseWithKey processChild children0
946+ liftIO $ if IntMap.null initialUnready
947+ then writeIORef haveEverBeenReady True
948+ else do
949+ modifyIORef' parentUnreadyChildren succ
950+ writeIORef pendingChange (initialUnready, mempty) -- The patch is always empty because it got applied implicitly when we ran the children the first time
951+ let result0 = IntMap.map (\(_, _, _, v) -> v) children0
952+ placeholders0 = fmap (\(_, ph, _, _) -> ph) children0
953+ result' = ffor children' $ fmap $ \(_, _, _, r) -> r
954+ liftIO $ writeIORef placeholders $! placeholders0
955+ _ <- IntMap.traverseWithKey (\_ (df, _, _, _) -> void $ append $ toNode df) children0
956+ liftIO . writeIORef lastPlaceholderRef =<< textNodeInternal ("" :: Text)
957+ requestDomAction_ $ ffor children' $ \p -> do
958+ (oldUnready, oldP) <- liftIO $ readIORef pendingChange
959+ newUnready <- liftIO $ updateChildUnreadiness p oldUnready
960+ let !newP = p <> oldP
961+ liftIO $ writeIORef pendingChange (newUnready, newP)
962+ when (IntMap.null newUnready) $ do
963+ applyDomUpdate newP
964+ return (result0, result')
965+
966+{-# INLINABLE hoistTraverseWithKeyWithAdjust #-}
967+hoistTraverseWithKeyWithAdjust :: forall (k :: * -> *) v v' t m p.
968+ ( Adjustable t m
969+ , MonadHold t m
970+ , DMap.GCompare k
971+ , MonadIO m
972+ , MonadJSM m
973+ , PrimMonad m
974+ , MonadFix m
975+ , Patch (p k v)
976+ , PatchTarget (p k (Constant Int)) ~ DMap k (Constant Int)
977+ , Monoid (p k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v'))
978+ , Patch (p k (Constant Int))
979+ )
980+ => (forall vv vv'.
981+ (forall a. k a -> vv a -> RequesterT t JSM Identity (TriggerEventT t m) (vv' a))
982+ -> DMap k vv
983+ -> Event t (p k vv)
984+ -> RequesterT t JSM Identity (TriggerEventT t m) (DMap k vv', Event t (p k vv'))
985+ ) -- ^ The base monad's traversal
986+ -> (forall vv vv'. (forall a. vv a -> vv' a) -> p k vv -> p k vv') -- ^ A way of mapping over the patch type
987+ -> (p k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v') -> DMap k (Constant (IORef (ChildReadyState k))) -> IO (DMap k (Constant (IORef (ChildReadyState k))))) -- ^ Given a patch for the children DOM elements, produce a patch for the childrens' unreadiness state
988+ -> (IORef (Map.Map (Some.Some k) DOM.Text) -> IORef DOM.Text -> p k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v') -> JSM ()) -- ^ Apply a patch to the DOM
989+ -> (forall a. k a -> v a -> HydrationDomBuilderT t m (v' a))
990+ -> DMap k v
991+ -> Event t (p k v)
992+ -> HydrationDomBuilderT t m (DMap k v', Event t (p k v'))
993+hoistTraverseWithKeyWithAdjust base mapPatch updateChildUnreadiness applyDomUpdate_ (f :: forall a. k a -> v a -> HydrationDomBuilderT t m (v' a)) (dm0 :: DMap k v) dm' = do
994+ initialEnv <- HydrationDomBuilderT ask
995+ let parentUnreadyChildren = _hydrationDomBuilderEnv_unreadyChildren initialEnv
996+ pendingChange :: IORef (DMap k (Constant (IORef (ChildReadyState k))), p k (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v')) <- liftIO $ newIORef mempty
997+ haveEverBeenReady <- liftIO $ newIORef False
998+ placeholders <- liftIO $ newIORef $ error "placeholders not yet initialized"
999+ lastPlaceholderRef <- liftIO $ newIORef $ error "lastPlaceholderRef not yet initialized"
1000+ let applyDomUpdate p = do
1001+ applyDomUpdate_ placeholders lastPlaceholderRef p
1002+ markSelfReady
1003+ liftIO $ writeIORef pendingChange $! mempty
1004+ markSelfReady = do
1005+ liftIO (readIORef haveEverBeenReady) >>= \case
1006+ True -> return ()
1007+ False -> do
1008+ liftIO $ writeIORef haveEverBeenReady True
1009+ old <- liftIO $ readIORef parentUnreadyChildren
1010+ let new = pred old
1011+ liftIO $ writeIORef parentUnreadyChildren $! new
1012+ when (new == 0) $ _hydrationDomBuilderEnv_commitAction initialEnv
1013+ markChildReady :: IORef (ChildReadyState k) -> JSM ()
1014+ markChildReady childReadyState = do
1015+ liftIO (readIORef childReadyState) >>= \case
1016+ ChildReadyState_Ready -> return ()
1017+ ChildReadyState_Unready countedAt -> do
1018+ liftIO $ writeIORef childReadyState ChildReadyState_Ready
1019+ case countedAt of
1020+ Nothing -> return ()
1021+ Just (Some.This k) -> do -- This child has been counted as unready, so we need to remove it from the unready set
1022+ (oldUnready, p) <- liftIO $ readIORef pendingChange
1023+ when (not $ DMap.null oldUnready) $ do -- This shouldn't actually ever be null
1024+ let newUnready = DMap.delete k oldUnready
1025+ liftIO $ writeIORef pendingChange (newUnready, p)
1026+ when (DMap.null newUnready) $ do
1027+ applyDomUpdate p
1028+ (children0, children') <- HydrationDomBuilderT $ lift $ base (\k v -> drawChildUpdate initialEnv markChildReady $ f k v) dm0 dm'
1029+ let processChild k (Compose (_, _, sRef, _)) = ComposeMaybe <$> do
1030+ readIORef sRef >>= \case
1031+ ChildReadyState_Ready -> return Nothing
1032+ ChildReadyState_Unready _ -> do
1033+ writeIORef sRef $ ChildReadyState_Unready $ Just $ Some.This k
1034+ return $ Just $ Constant sRef
1035+ initialUnready <- liftIO $ DMap.mapMaybeWithKey (\_ -> getComposeMaybe) <$> DMap.traverseWithKey processChild children0
1036+ liftIO $ if DMap.null initialUnready
1037+ then writeIORef haveEverBeenReady True
1038+ else do
1039+ modifyIORef' parentUnreadyChildren succ
1040+ writeIORef pendingChange (initialUnready, mempty) -- The patch is always empty because it got applied implicitly when we ran the children the first time
1041+ let result0 = DMap.map (\(Compose (_, _, _, v)) -> v) children0
1042+ placeholders0 = weakenDMapWith (\(Compose (_, ph, _, _)) -> ph) children0
1043+ result' = ffor children' $ mapPatch $ \(Compose (_, _, _, r)) -> r
1044+ liftIO $ writeIORef placeholders $! placeholders0
1045+ _ <- DMap.traverseWithKey (\_ (Compose (df, _, _, _)) -> Constant () <$ append (toNode df)) children0
1046+ liftIO . writeIORef lastPlaceholderRef =<< textNodeInternal ("" :: Text)
1047+ requestDomAction_ $ ffor children' $ \p -> do
1048+ (oldUnready, oldP) <- liftIO $ readIORef pendingChange
1049+ newUnready <- liftIO $ updateChildUnreadiness p oldUnready
1050+ let !newP = p <> oldP
1051+ liftIO $ writeIORef pendingChange (newUnready, newP)
1052+ when (DMap.null newUnready) $ do
1053+ applyDomUpdate newP
1054+ return (result0, result')
1055+
1056+{-# INLINABLE drawChildUpdate #-}
1057+drawChildUpdate :: (MonadIO m, MonadJSM m)
1058+ => HydrationDomBuilderEnv t m
1059+ -> (IORef (ChildReadyState k) -> JSM ()) -- This will NOT be called if the child is ready at initialization time; instead, the ChildReadyState return value will be ChildReadyState_Ready
1060+ -> HydrationDomBuilderT t m (v' a)
1061+ -> RequesterT t JSM Identity (TriggerEventT t m) (Compose ((,,,) DOM.DocumentFragment DOM.Text (IORef (ChildReadyState k))) v' a)
1062+drawChildUpdate initialEnv markReady child = do
1063+ childReadyState <- liftIO $ newIORef $ ChildReadyState_Unready Nothing
1064+ unreadyChildren <- liftIO $ newIORef 0
1065+ df <- createDocumentFragment $ _hydrationDomBuilderEnv_document initialEnv
1066+ (placeholder, result) <- runReaderT (unHydrationDomBuilderT $ (,) <$> textNodeInternal ("" :: Text) <*> child) $ initialEnv
1067+ { _hydrationDomBuilderEnv_parent = toNode df
1068+ , _hydrationDomBuilderEnv_unreadyChildren = unreadyChildren
1069+ , _hydrationDomBuilderEnv_commitAction = markReady childReadyState
1070+ }
1071+ u <- liftIO $ readIORef unreadyChildren
1072+ when (u == 0) $ liftIO $ writeIORef childReadyState ChildReadyState_Ready
1073+ return $ Compose (df, placeholder, childReadyState, result)
1074+
1075+{-# INLINABLE drawChildUpdateInt #-}
1076+drawChildUpdateInt :: (MonadIO m, MonadJSM m)
1077+ => HydrationDomBuilderEnv t m
1078+ -> (IORef ChildReadyStateInt -> JSM ()) -- This will NOT be called if the child is ready at initialization time; instead, the ChildReadyState return value will be ChildReadyState_Ready
1079+ -> HydrationDomBuilderT t m v'
1080+ -> RequesterT t JSM Identity (TriggerEventT t m) (DOM.DocumentFragment, DOM.Text, IORef ChildReadyStateInt, v')
1081+drawChildUpdateInt initialEnv markReady child = do
1082+ childReadyState <- liftIO $ newIORef $ ChildReadyStateInt_Unready Nothing
1083+ unreadyChildren <- liftIO $ newIORef 0
1084+ df <- createDocumentFragment $ _hydrationDomBuilderEnv_document initialEnv
1085+ (placeholder, result) <- runReaderT (unHydrationDomBuilderT $ (,) <$> textNodeInternal ("" :: Text) <*> child) $ initialEnv
1086+ { _hydrationDomBuilderEnv_parent = toNode df
1087+ , _hydrationDomBuilderEnv_unreadyChildren = unreadyChildren
1088+ , _hydrationDomBuilderEnv_commitAction = markReady childReadyState
1089+ }
1090+ u <- liftIO $ readIORef unreadyChildren
1091+ when (u == 0) $ liftIO $ writeIORef childReadyState ChildReadyStateInt_Ready
1092+ return (df, placeholder, childReadyState, result)
1093+
1094+mkHasFocus :: (MonadHold t m, Reflex t) => Element er d t -> m (Dynamic t Bool)
1095+mkHasFocus e = do
1096+ let initialFocus = False --TODO: Actually get the initial focus of the element
1097+ holdDyn initialFocus $ leftmost
1098+ [ False <$ Reflex.select (_element_events e) (WrapArg Blur)
1099+ , True <$ Reflex.select (_element_events e) (WrapArg Focus)
1100+ ]
1101+
1102+insertAfter :: (MonadJSM m, IsNode new, IsNode existing) => new -> existing -> m ()
1103+insertAfter new existing = do
1104+ p <- getParentNodeUnchecked existing
1105+ Node.getNextSibling existing >>= DOM.insertBefore_ p new
1106+
1107+insertBefore :: (MonadJSM m, IsNode new, IsNode existing) => new -> existing -> m ()
1108+insertBefore new existing = do
1109+ p <- getParentNodeUnchecked existing
1110+ DOM.insertBefore_ p new (Just existing) -- If there's no parent, that means we've been removed from the DOM; this should not happen if the we're removing ourselves from the performEvent properly
1111+
1112+instance PerformEvent t m => PerformEvent t (HydrationDomBuilderT t m) where
1113+ type Performable (HydrationDomBuilderT t m) = Performable m
1114+ {-# INLINABLE performEvent_ #-}
1115+ performEvent_ e = lift $ performEvent_ e
1116+ {-# INLINABLE performEvent #-}
1117+ performEvent e = lift $ performEvent e
1118+
1119+instance PostBuild t m => PostBuild t (HydrationDomBuilderT t m) where
1120+ {-# INLINABLE getPostBuild #-}
1121+ getPostBuild = lift getPostBuild
1122+
1123+instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (HydrationDomBuilderT t m) where
1124+ {-# INLINABLE newEventWithTrigger #-}
1125+ newEventWithTrigger = lift . newEventWithTrigger
1126+ {-# INLINABLE newFanEventWithTrigger #-}
1127+ newFanEventWithTrigger f = lift $ newFanEventWithTrigger f
1128+
1129+instance (Monad m, MonadRef m, Ref m ~ Ref IO, MonadReflexCreateTrigger t m) => TriggerEvent t (HydrationDomBuilderT t m) where
1130+ {-# INLINABLE newTriggerEvent #-}
1131+ newTriggerEvent = HydrationDomBuilderT . lift . lift $ newTriggerEvent
1132+ {-# INLINABLE newTriggerEventWithOnComplete #-}
1133+ newTriggerEventWithOnComplete = HydrationDomBuilderT . lift . lift $ newTriggerEventWithOnComplete
1134+ {-# INLINABLE newEventWithLazyTriggerWithOnComplete #-}
1135+ newEventWithLazyTriggerWithOnComplete f = HydrationDomBuilderT . lift . lift $ newEventWithLazyTriggerWithOnComplete f
1136+
1137+instance HasJSContext m => HasJSContext (HydrationDomBuilderT t m) where
1138+ type JSContextPhantom (HydrationDomBuilderT t m) = JSContextPhantom m
1139+ askJSContext = lift askJSContext
1140+
1141+instance MonadRef m => MonadRef (HydrationDomBuilderT t m) where
1142+ type Ref (HydrationDomBuilderT t m) = Ref m
1143+ {-# INLINABLE newRef #-}
1144+ newRef = lift . newRef
1145+ {-# INLINABLE readRef #-}
1146+ readRef = lift . readRef
1147+ {-# INLINABLE writeRef #-}
1148+ writeRef r = lift . writeRef r
1149+
1150+instance MonadAtomicRef m => MonadAtomicRef (HydrationDomBuilderT t m) where
1151+ {-# INLINABLE atomicModifyRef #-}
1152+ atomicModifyRef r = lift . atomicModifyRef r
1153+
1154+instance (HasJS x m, ReflexHost t) => HasJS x (HydrationDomBuilderT t m) where
1155+ type JSX (HydrationDomBuilderT t m) = JSX m
1156+ liftJS = lift . liftJS
1157+
1158+{-# INLINABLE wrapDomEvent #-}
1159+wrapDomEvent :: (TriggerEvent t m, MonadJSM m) => e -> (e -> EventM e event () -> JSM (JSM ())) -> EventM e event a -> m (Event t a)
1160+wrapDomEvent el elementOnevent getValue = wrapDomEventMaybe el elementOnevent $ fmap Just getValue
1161+
1162+{-# INLINABLE subscribeDomEvent #-}
1163+subscribeDomEvent :: (EventM e event () -> JSM (JSM ()))
1164+ -> EventM e event (Maybe a)
1165+ -> Chan [DSum (EventTriggerRef t) TriggerInvocation]
1166+ -> EventTrigger t a
1167+ -> JSM (JSM ())
1168+subscribeDomEvent elementOnevent getValue eventChan et = elementOnevent $ do
1169+ mv <- getValue
1170+ forM_ mv $ \v -> liftIO $ do
1171+ --TODO: I don't think this is quite right: if a new trigger is created between when this is enqueued and when it fires, this may not work quite right
1172+ etr <- newIORef $ Just et
1173+ writeChan eventChan [EventTriggerRef etr :=> TriggerInvocation v (return ())]
1174+
1175+instance MonadSample t m => MonadSample t (HydrationDomBuilderT t m) where
1176+ {-# INLINABLE sample #-}
1177+ sample = lift . sample
1178+
1179+instance MonadHold t m => MonadHold t (HydrationDomBuilderT t m) where
1180+ {-# INLINABLE hold #-}
1181+ hold v0 v' = lift $ hold v0 v'
1182+ {-# INLINABLE holdDyn #-}
1183+ holdDyn v0 v' = lift $ holdDyn v0 v'
1184+ {-# INLINABLE holdIncremental #-}
1185+ holdIncremental v0 v' = lift $ holdIncremental v0 v'
1186+ {-# INLINABLE buildDynamic #-}
1187+ buildDynamic a0 = lift . buildDynamic a0
1188+ {-# INLINABLE headE #-}
1189+ headE = lift . headE
1190+
reflex-dom-core/src/Reflex/Dom/Builder/Immediate.hs+30−185
@@ -128,7 +128,6 @@ 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
132import qualified GHCJS.DOM as DOM
131import qualified GHCJS.DOM as DOM
133import GHCJS.DOM.Document (Document, createDocumentFragment, createElement, createElementNS, createTextNode, createComment)
132import GHCJS.DOM.Document (Document, createDocumentFragment, createElement, createElementNS, createTextNode, createComment)
134import GHCJS.DOM.Element (getScrollTop, removeAttribute, removeAttributeNS, setAttribute, setAttributeNS)
133import GHCJS.DOM.Element (getScrollTop, removeAttribute, removeAttributeNS, setAttribute, setAttributeNS)
@@ -146,10 +145,8 @@ import GHCJS.DOM.MouseEvent
146import qualified GHCJS.DOM.Touch as Touch
145import qualified GHCJS.DOM.Touch as Touch
147import qualified GHCJS.DOM.TouchEvent as TouchEvent
146import qualified GHCJS.DOM.TouchEvent as TouchEvent
148import qualified GHCJS.DOM.TouchList as TouchList
147import qualified GHCJS.DOM.TouchList as TouchList
149−import GHCJS.DOM.Node (appendChild_, getChildNodes, getOwnerDocumentUnchecked, getParentNodeUnchecked, setNodeValue, toNode)
148+import GHCJS.DOM.Node (appendChild_, getOwnerDocumentUnchecked, getParentNodeUnchecked, setNodeValue, toNode)
150import qualified GHCJS.DOM.Node as DOM (insertBefore_)
149import qualified GHCJS.DOM.Node as DOM (insertBefore_)
151−import qualified GHCJS.DOM.Node as Node
152−import qualified GHCJS.DOM.NodeList as NodeList
153import GHCJS.DOM.Types
150import GHCJS.DOM.Types
154 (liftJSM, askJSM, runJSM, JSM, MonadJSM,
151 (liftJSM, askJSM, runJSM, JSM, MonadJSM,
155 FocusEvent, IsElement, IsEvent, IsNode, KeyboardEvent, Node,
152 FocusEvent, IsElement, IsEvent, IsNode, KeyboardEvent, Node,
@@ -158,7 +155,7 @@ import qualified GHCJS.DOM.Types as DOM
158import GHCJS.DOM.UIEvent
155import GHCJS.DOM.UIEvent
159import GHCJS.DOM.KeyboardEvent as KeyboardEvent
156import GHCJS.DOM.KeyboardEvent as KeyboardEvent
160import qualified GHCJS.DOM.Window as Window
157import qualified GHCJS.DOM.Window as Window
161−import Language.Javascript.JSaddle (call, eval, jsg, js1)
158+import Language.Javascript.JSaddle (call, eval)
162import Data.IntMap.Strict (IntMap)
159import Data.IntMap.Strict (IntMap)
163import qualified Data.IntMap.Strict as IntMap
160import qualified Data.IntMap.Strict as IntMap
164import Data.FastMutableIntMap (PatchIntMap (..))
161import Data.FastMutableIntMap (PatchIntMap (..))
@@ -175,19 +172,11 @@ instance MonadJSM m => MonadJSM (ImmediateDomBuilderT t m) where
175 liftJSM' = ImmediateDomBuilderT . liftJSM'
172 liftJSM' = ImmediateDomBuilderT . liftJSM'
176#endif
173#endif
177
174
178−{- Hydration:
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
182−-}
183−
184data ImmediateDomBuilderEnv t
175data ImmediateDomBuilderEnv t
185 = ImmediateDomBuilderEnv { _immediateDomBuilderEnv_document :: {-# UNPACK #-} !Document
176 = ImmediateDomBuilderEnv { _immediateDomBuilderEnv_document :: {-# UNPACK #-} !Document
186 , _immediateDomBuilderEnv_parent :: {-# UNPACK #-} !Node
177 , _immediateDomBuilderEnv_parent :: {-# UNPACK #-} !Node
187 , _immediateDomBuilderEnv_unreadyChildren :: {-# UNPACK #-} !(IORef Word) -- Number of children who still aren't fully rendered
178 , _immediateDomBuilderEnv_unreadyChildren :: {-# UNPACK #-} !(IORef Word) -- Number of children who still aren't fully rendered
188 , _immediateDomBuilderEnv_commitAction :: !(JSM ()) -- Action to take when all children are ready --TODO: we should probably get rid of this once we invoke it
179 , _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
191 }
180 }
192
181
193newtype ImmediateDomBuilderT t m a = ImmediateDomBuilderT { unImmediateDomBuilderT :: ReaderT (ImmediateDomBuilderEnv t) (RequesterT t JSM Identity (TriggerEventT t m)) a }
182newtype ImmediateDomBuilderT t m a = ImmediateDomBuilderT { unImmediateDomBuilderT :: ReaderT (ImmediateDomBuilderEnv t) (RequesterT t JSM Identity (TriggerEventT t m)) a }
@@ -260,76 +249,21 @@ append n = do
260 liftJSM $ appendChild_ p n
249 liftJSM $ appendChild_ p n
261 return ()
250 return ()
262
251
263−getHydrationMode :: MonadIO m => ImmediateDomBuilderT t m Bool
264−getHydrationMode = do
265− ref <- ImmediateDomBuilderT $ asks _immediateDomBuilderEnv_hydrating
266− liftIO $ readIORef ref
267−
268{-# INLINABLE textNodeInternal #-}
252{-# INLINABLE textNodeInternal #-}
269textNodeInternal :: (MonadJSM m, ToDOMString contents) => contents -> ImmediateDomBuilderT t m DOM.Text
253textNodeInternal :: (MonadJSM m, ToDOMString contents) => contents -> ImmediateDomBuilderT t m DOM.Text
270textNodeInternal !t = do
254textNodeInternal !t = do
271 doc <- askDocument
255 doc <- askDocument
272−-- n <- liftJSM $ createTextNode doc t
256+ n <- liftJSM $ createTextNode doc t
273−-- append $ toNode n
257+ append $ toNode n
274−-- return n
258+ return n
275− parent <- askParent
276− getHydrationMode >>= \case
277− False -> do
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
280− append $ toNode n
281− pure n
282− True -> do
283− lastHydrationNodeRef <- ImmediateDomBuilderT $ asks _immediateDomBuilderEnv_hydrationNode
284− lastHydrationNode <- liftIO $ readIORef lastHydrationNodeRef
285− let go mLastNode = maybe (Node.getFirstChild parent) Node.getNextSibling mLastNode >>= \case
286− Nothing -> do
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)
290− pure tn
291− Just node -> DOM.castTo DOM.Text node >>= \case
292− Nothing -> do
293− liftIO $ putStrLn $ "Not a text node, skipping"
294− go (Just node)
295− Just tn -> do
296− liftIO $ print $ "Using existing text node: instead of building " <> DOM.toJSString t
297− pure tn
298− n <- go lastHydrationNode
299− liftIO $ modifyIORef' lastHydrationNodeRef (\_ -> Just $ toNode n)
300− pure n
301
259
302{-# INLINABLE commentNodeInternal #-}
260{-# INLINABLE commentNodeInternal #-}
303commentNodeInternal :: (MonadJSM m, ToDOMString contents) => contents -> ImmediateDomBuilderT t m DOM.Comment
261commentNodeInternal :: (MonadJSM m, ToDOMString contents) => contents -> ImmediateDomBuilderT t m DOM.Comment
304commentNodeInternal !t = do
262commentNodeInternal !t = do
305 doc <- askDocument
263 doc <- askDocument
306− parent <- askParent
307−
308− getHydrationMode >>= \case
309− False -> do
310− liftIO $ print $ "Not in hydration mode, creating comment node: " <> DOM.toJSString t
311− n <- liftJSM $ createComment doc t -- not in hydration mode, create as usual
264+ n <- liftJSM $ createComment doc t
312− append $ toNode n
265+ append $ toNode n
313− pure n
266+ return n
314− True -> do
315− lastHydrationNodeRef <- ImmediateDomBuilderT $ asks _immediateDomBuilderEnv_hydrationNode
316− lastHydrationNode <- liftIO $ readIORef lastHydrationNodeRef
317− let go mLastNode = maybe (Node.getFirstChild parent) Node.getNextSibling mLastNode >>= \case
318− Nothing -> do
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)
322− pure tn
323− Just node -> DOM.castTo DOM.Comment node >>= \case
324− Nothing -> do
325− liftIO $ putStrLn $ "Not a comment node, skipping"
326− go (Just node)
327− Just tn -> do
328− liftIO $ print $ "Using existing comment node: instead of building " <> DOM.toJSString t
329− pure tn
330− n <- go lastHydrationNode
331− liftIO $ modifyIORef' lastHydrationNodeRef (\_ -> Just $ toNode n)
332− pure n
333
267
334-- | s and e must both be children of the same node and s must precede e;
268-- | s and e must both be children of the same node and s must precede e;
335-- all nodes between s and e will be removed, but s and e will not be removed
269-- all nodes between s and e will be removed, but s and e will not be removed
@@ -433,45 +367,16 @@ wrap e cfg = do
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)
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)
434makeElement elementTag cfg child = do
368makeElement elementTag cfg child = do
435 doc <- askDocument
369 doc <- askDocument
436− parent <- askParent
437− let buildElement = do
438− e <- liftJSM $ uncheckedCastTo DOM.Element <$> case cfg ^. namespace of
370+ e <- liftJSM $ uncheckedCastTo DOM.Element <$> case cfg ^. namespace of
439− Nothing -> createElement doc elementTag
371+ Nothing -> createElement doc elementTag
440− Just ens -> createElementNS doc (Just ens) elementTag
372+ Just ens -> createElementNS doc (Just ens) elementTag
441− ImmediateDomBuilderT $ iforM_ (cfg ^. initialAttributes) $ \(AttributeName mAttrNamespace n) v -> case mAttrNamespace of
373+ ImmediateDomBuilderT $ iforM_ (cfg ^. initialAttributes) $ \(AttributeName mAttrNamespace n) v -> case mAttrNamespace of
442− Nothing -> lift $ setAttribute e n v
374+ Nothing -> lift $ setAttribute e n v
443− Just ans -> lift $ setAttributeNS e (Just ans) n v
375+ Just ans -> lift $ setAttributeNS e (Just ans) n v
444− pure e
376+ result <- flip localEnv child $ \env -> env
445− e <- getHydrationMode >>= \case
446− False -> do
447− liftIO $ print $ "Not in hydration mode, creating " <> elementTag
448− e <- buildElement -- not in hydration mode, create as usual
449− append $ toNode e
450− pure e
451− True -> do
452− lastHydrationNodeRef <- ImmediateDomBuilderT $ asks _immediateDomBuilderEnv_hydrationNode
453− lastHydrationNode <- liftIO $ readIORef lastHydrationNodeRef
454− let go mLastNode = maybe (Node.getFirstChild parent) Node.getNextSibling mLastNode >>= \case
455− Nothing -> do
456− liftIO $ print $ "No node found, building element: " <> elementTag
457− e <- buildElement
458− append $ toNode e -- we can append because there's no node at this index (we have exhausted all previous nodes)
459− pure e
460− Just node -> DOM.castTo DOM.Element node >>= \case
461− Nothing -> do
462− liftIO $ putStrLn $ "Not an element, skipping"
463− go (Just node) -- TODO maybe check for SSR attribute?
464− Just e -> do
465− tn <- Element.getTagName e
466− liftIO $ print $ "Using existing element: " <> tn <> " instead of building " <> elementTag
467− pure e
468− e <- go lastHydrationNode
469− liftIO $ modifyIORef' lastHydrationNodeRef (\_ -> Just $ toNode e)
470− pure e
471−
472− -- TODO: prerender needs completely replacing
473− childNode <- liftIO $ newIORef Nothing
474− result <- localEnv (\env -> env { _immediateDomBuilderEnv_parent = toNode e, _immediateDomBuilderEnv_hydrationNode = childNode }) child
377+ { _immediateDomBuilderEnv_parent = toNode e
378+ }
379+ append $ toNode e
475 wrapped <- wrap e $ extractRawElementConfig cfg
380 wrapped <- wrap e $ extractRawElementConfig cfg
476 return ((wrapped, result), e)
381 return ((wrapped, result), e)
477
382
@@ -720,62 +625,11 @@ instance SupportsImmediateDomBuilder t m => MountableDomBuilder t (ImmediateDomB
720 liftIO $ writeIORef (_immediateDomFragment_state childFragment) $ FragmentState_Mounted (before, after)
625 liftIO $ writeIORef (_immediateDomFragment_state childFragment) $ FragmentState_Mounted (before, after)
721 liftIO $ writeIORef (_immediateDomFragment_state fragment) $ FragmentState_Mounted (before, after)
626 liftIO $ writeIORef (_immediateDomFragment_state fragment) $ FragmentState_Mounted (before, after)
722
627
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
727−
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
730− doc <- askDocument
731− parent <- askParent
732− lastHydrationNodeRef <- ImmediateDomBuilderT $ asks _immediateDomBuilderEnv_hydrationNode
733− lastHydrationNode <- liftIO $ readIORef lastHydrationNodeRef
734− let go mLastNode = maybe (Node.getFirstChild parent) Node.getNextSibling mLastNode >>= \case
735− Nothing -> do
736− liftIO $ print $ "No node found, building comment"
737− a <- buildElement
738− replaceOrCreate a Nothing
739− pure (a, "")
740− Just node -> DOM.castTo DOM.Comment node >>= \case
741− Just comment -> do
742− commentText <- Node.getTextContentUnchecked comment
743− case T.stripPrefix prefix commentText of
744− Just key -> do
745− new <- buildElement
746− replaceOrCreate new (Just node)
747− pure (new, key)
748− Nothing -> do
749− liftIO $ putStrLn $ "Not a comment, skipping"
750− go (Just node)
751− Nothing -> do
752− liftIO $ putStrLn $ "Not a comment, skipping"
753− go (Just node)
754− (a, key) <- go lastHydrationNode
755− liftIO $ modifyIORef' lastHydrationNodeRef (\_ -> Just $ toNode a)
756− pure (a, key)
757−
758−skipToReplaceStart :: MonadJSM m => ImmediateDomBuilderT t m (DOM.Text, Text)
759−skipToReplaceStart = do
760− doc <- askDocument
761− parent <- askParent
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
765−
766−skipToReplaceEnd :: MonadJSM m => Text -> ImmediateDomBuilderT t m DOM.Text
767−skipToReplaceEnd key = do
768− doc <- askDocument
769− parent <- askParent
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
773−
774instance (Reflex t, Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, PrimMonad m) => Adjustable t (ImmediateDomBuilderT t m) where
628instance (Reflex t, Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, PrimMonad m) => Adjustable t (ImmediateDomBuilderT t m) where
775 {-# INLINABLE runWithReplace #-}
629 {-# INLINABLE runWithReplace #-}
776 runWithReplace a0 a' = do
630 runWithReplace a0 a' = do
777 initialEnv <- ImmediateDomBuilderT ask
631 initialEnv <- ImmediateDomBuilderT ask
778− (before, beforeKey) <- skipToReplaceStart
632+ before <- textNodeInternal ("" :: Text)
779 let parentUnreadyChildren = _immediateDomBuilderEnv_unreadyChildren initialEnv
633 let parentUnreadyChildren = _immediateDomBuilderEnv_unreadyChildren initialEnv
780 haveEverBeenReady <- liftIO $ newIORef False
634 haveEverBeenReady <- liftIO $ newIORef False
781 currentCohort <- liftIO $ newIORef (-1 :: Int) -- Equal to the cohort currently in the DOM
635 currentCohort <- liftIO $ newIORef (-1 :: Int) -- Equal to the cohort currently in the DOM
@@ -790,17 +644,14 @@ instance (Reflex t, Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, PrimM
790 when (new == 0) $ _immediateDomBuilderEnv_commitAction initialEnv
644 when (new == 0) $ _immediateDomBuilderEnv_commitAction initialEnv
791 -- We draw 'after' in this roundabout way to avoid using MonadFix
645 -- We draw 'after' in this roundabout way to avoid using MonadFix
792 doc <- askDocument
646 doc <- askDocument
793− after <- skipToReplaceEnd beforeKey
647+ after <- createTextNode doc ("" :: Text)
794 let drawInitialChild = do
648 let drawInitialChild = do
795 unreadyChildren <- liftIO $ newIORef 0
649 unreadyChildren <- liftIO $ newIORef 0
796−-- let f = do
650+ let f = do
797−-- result <- a0
651+ result <- a0
798−-- append $ toNode after
652+ append $ toNode after
799−-- Node.getNextSibling (toNode result) >>= \case
800−-- Nothing -> append (toNode after)
801−-- Just next -> insertBefore (toNode after) next
802−-- return result
653+ return result
803− result <- runReaderT (unImmediateDomBuilderT a0) initialEnv
654+ result <- runReaderT (unImmediateDomBuilderT f) $ initialEnv
804 { _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
655 { _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
805 , _immediateDomBuilderEnv_commitAction = myCommitAction
656 , _immediateDomBuilderEnv_commitAction = myCommitAction
806 }
657 }
@@ -819,12 +670,11 @@ instance (Reflex t, Adjustable t m, MonadJSM m, MonadHold t m, MonadFix m, PrimM
819 insertBefore df after
670 insertBefore df after
820 liftIO $ writeIORef currentCohort cohortId
671 liftIO $ writeIORef currentCohort cohortId
821 myCommitAction
672 myCommitAction
822− let env = initialEnv
673+ result <- runReaderT (unImmediateDomBuilderT child) $ initialEnv
823− { _immediateDomBuilderEnv_parent = toNode df
674+ { _immediateDomBuilderEnv_parent = toNode df
824− , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
675+ , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
825− , _immediateDomBuilderEnv_commitAction = commitAction
676+ , _immediateDomBuilderEnv_commitAction = commitAction
826− }
677+ }
827− result <- runReaderT (unImmediateDomBuilderT child) env
828 uc <- liftIO $ readIORef unreadyChildren
678 uc <- liftIO $ readIORef unreadyChildren
829 let commitActionToRunNow = if uc == 0
679 let commitActionToRunNow = if uc == 0
830 then Just commitAction
680 then Just commitAction
@@ -1177,11 +1027,6 @@ mkHasFocus e = do
1177 , True <$ Reflex.select (_element_events e) (WrapArg Focus)
1027 , True <$ Reflex.select (_element_events e) (WrapArg Focus)
1178 ]
1028 ]
1179
1029
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
1184−
1185insertBefore :: (MonadJSM m, IsNode new, IsNode existing) => new -> existing -> m ()
1030insertBefore :: (MonadJSM m, IsNode new, IsNode existing) => new -> existing -> m ()
1186insertBefore new existing = do
1031insertBefore new existing = do
1187 p <- getParentNodeUnchecked existing
1032 p <- getParentNodeUnchecked existing
reflex-dom-core/src/Reflex/Dom/Main.hs+1−8
@@ -18,6 +18,7 @@ module Reflex.Dom.Main where
18import Prelude hiding (concat, mapM, mapM_, sequence, sequence_)
18import Prelude hiding (concat, mapM, mapM_, sequence, sequence_)
19
19
20import Reflex.Dom.Builder.Immediate
20import Reflex.Dom.Builder.Immediate
21+import Reflex.Dom.Builder.Hydration
21import Reflex.Dom.Class
22import Reflex.Dom.Class
22import Reflex.Host.Class
23import Reflex.Host.Class
23import Reflex.PerformEvent.Base
24import Reflex.PerformEvent.Base
@@ -123,15 +124,11 @@ mainWidgetWithHead' widgets = withJSContextSingletonMono $ \jsSing -> do
123 let go :: forall c. Widget () c -> DOM.DocumentFragment -> PerformEventT DomTimeline DomHost c
124 let go :: forall c. Widget () c -> DOM.DocumentFragment -> PerformEventT DomTimeline DomHost c
124 go w df = do
125 go w df = do
125 unreadyChildren <- liftIO $ newIORef 0
126 unreadyChildren <- liftIO $ newIORef 0
126− hydrating <- liftIO $ newIORef True
127− hydrationNode <- liftIO $ newIORef Nothing
128 let builderEnv = ImmediateDomBuilderEnv
127 let builderEnv = ImmediateDomBuilderEnv
129 { _immediateDomBuilderEnv_document = toDocument doc
128 { _immediateDomBuilderEnv_document = toDocument doc
130 , _immediateDomBuilderEnv_parent = toNode df
129 , _immediateDomBuilderEnv_parent = toNode df
131 , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
130 , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
132 , _immediateDomBuilderEnv_commitAction = return () --TODO
131 , _immediateDomBuilderEnv_commitAction = return () --TODO
133− , _immediateDomBuilderEnv_hydrating = hydrating
134− , _immediateDomBuilderEnv_hydrationNode = hydrationNode
135 }
132 }
136 runWithJSContextSingleton (runImmediateDomBuilderT (runPostBuildT w postBuild) builderEnv events) jsSing
133 runWithJSContextSingleton (runImmediateDomBuilderT (runPostBuildT w postBuild) builderEnv events) jsSing
137 rec b <- go (headWidget a) headFragment
134 rec b <- go (headWidget a) headFragment
@@ -155,15 +152,11 @@ attachWidget' rootElement jsSing w = do
155 ((a, events), fc) <- liftIO . attachWidget'' $ \events -> do
152 ((a, events), fc) <- liftIO . attachWidget'' $ \events -> do
156 (postBuild, postBuildTriggerRef) <- newEventWithTriggerRef
153 (postBuild, postBuildTriggerRef) <- newEventWithTriggerRef
157 unreadyChildren <- liftIO $ newIORef 0
154 unreadyChildren <- liftIO $ newIORef 0
158− hydrating <- liftIO $ newIORef True
159− hydrationNode <- liftIO $ newIORef Nothing
160 let builderEnv = ImmediateDomBuilderEnv
155 let builderEnv = ImmediateDomBuilderEnv
161 { _immediateDomBuilderEnv_document = toDocument doc
156 { _immediateDomBuilderEnv_document = toDocument doc
162 , _immediateDomBuilderEnv_parent = toNode df
157 , _immediateDomBuilderEnv_parent = toNode df
163 , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
158 , _immediateDomBuilderEnv_unreadyChildren = unreadyChildren
164 , _immediateDomBuilderEnv_commitAction = return () --TODO
159 , _immediateDomBuilderEnv_commitAction = return () --TODO
165− , _immediateDomBuilderEnv_hydrating = hydrating
166− , _immediateDomBuilderEnv_hydrationNode = hydrationNode
167 }
160 }
168 a <- runWithJSContextSingleton (runImmediateDomBuilderT (runPostBuildT w postBuild) builderEnv events) jsSing
161 a <- runWithJSContextSingleton (runImmediateDomBuilderT (runPostBuildT w postBuild) builderEnv events) jsSing
169 return ((a, events), postBuildTriggerRef)
162 return ((a, events), postBuildTriggerRef)
reflex-dom-core/src/Reflex/Dom/Prerender.hs+44−20
@@ -1,4 +1,5 @@
1{-# LANGUAGE ConstraintKinds #-}
1{-# LANGUAGE ConstraintKinds #-}
2+{-# LANGUAGE DefaultSignatures #-}
2{-# LANGUAGE EmptyDataDecls #-}
3{-# LANGUAGE EmptyDataDecls #-}
3{-# LANGUAGE FlexibleContexts #-}
4{-# LANGUAGE FlexibleContexts #-}
4{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE FlexibleInstances #-}
@@ -17,14 +18,18 @@ module Reflex.Dom.Prerender
17 ) where
18 ) where
18
19
19import Control.Monad.Reader
20import Control.Monad.Reader
21+import Control.Monad.Ref (Ref, MonadRef)
20import Data.Constraint
22import Data.Constraint
21import Data.Default
23import Data.Default
24+import Data.IORef (modifyIORef')
22import Foreign.JavaScript.TH
25import Foreign.JavaScript.TH
26+import GHC.IORef (IORef)
23import GHCJS.DOM.Types (MonadJSM)
27import GHCJS.DOM.Types (MonadJSM)
24import Reflex
28import Reflex
25import Reflex.Dom.Builder.Class
29import Reflex.Dom.Builder.Class
26import Reflex.Dom.Builder.InputDisabled
30import Reflex.Dom.Builder.InputDisabled
27import Reflex.Dom.Builder.Immediate
31import Reflex.Dom.Builder.Immediate
32+import Reflex.Dom.Builder.Hydration
28import Reflex.Dom.Builder.Static
33import Reflex.Dom.Builder.Static
29import Reflex.Host.Class
34import Reflex.Host.Class
30
35
@@ -37,33 +42,29 @@ type PrerenderClientConstraint js m =
37 , HasJSContext (Performable m)
42 , HasJSContext (Performable m)
38 , MonadFix m
43 , MonadFix m
39 , MonadFix (Performable m)
44 , MonadFix (Performable m)
40− , DomBuilderSpace m ~ GhcjsDomSpace
45+-- , DomBuilderSpace m ~ GhcjsDomSpace
41 )
46 )
42
47
43−class Prerender js m | m -> js where
48+class Monad m => Prerender js m | m -> js where
44 prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m))
49 prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m))
50+ startPrerenderBlock :: m ()
51+ default startPrerenderBlock :: (Prerender js n, m ~ t n, MonadTrans t, Monad n) => m ()
52+ startPrerenderBlock = lift $ startPrerenderBlock
53+ endPrerenderBlock :: m ()
54+ default endPrerenderBlock :: (Prerender js n, m ~ t n, MonadTrans t, Monad n) => m ()
55+ endPrerenderBlock = lift $ endPrerenderBlock
45
56
46-- | Draw one widget when prerendering (e.g. server-side) and another when the
57-- | Draw one widget when prerendering (e.g. server-side) and another when the
47-- widget is fully instantiated. In a given execution of this function, there
58-- widget is fully instantiated. In a given execution of this function, there
48-- will be exactly one invocation of exactly one of the arguments.
59-- will be exactly one invocation of exactly one of the arguments.
49prerender :: forall js t m a. (Prerender js m, DomBuilder t m) => m a -> (PrerenderClientConstraint js m => m a) -> m a
60prerender :: forall js t m a. (Prerender js m, DomBuilder t m) => m a -> (PrerenderClientConstraint js m => m a) -> m a
61+prerender server client = do
62+ startPrerenderBlock
50−prerender server client = case prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m)) of
63+ a <- case prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m)) of
51− Nothing -> prerenderStart *> server <* prerenderEnd
64+ Nothing -> server
52− Just Dict -> rerenderStart *> client <* rerenderEnd
65+ Just Dict -> client
53−
66+ endPrerenderBlock
54−-- TODO: add a unique ID
67+ pure a
55−prerenderStart :: DomBuilder t m => m ()
56−prerenderStart = void $ commentNode $ def { _commentNodeConfig_initialContents = "prerender-start" }
57−
58−prerenderEnd :: DomBuilder t m => m ()
59−prerenderEnd = void $ commentNode $ def { _commentNodeConfig_initialContents = "prerender-end" }
60−
61−-- TODO: add a unique ID
62−rerenderStart :: DomBuilder t m => m ()
63−rerenderStart = void $ commentNode $ def { _commentNodeConfig_initialContents = "rerender-start" }
64−
65−rerenderEnd :: DomBuilder t m => m ()
66−rerenderEnd = void $ commentNode $ def { _commentNodeConfig_initialContents = "rerender-end" }
67
68
68instance ( HasJS js m
69instance ( HasJS js m
69 , HasJS js (Performable m)
70 , HasJS js (Performable m)
@@ -76,11 +77,34 @@ instance ( HasJS js m
76 , ReflexHost t
77 , ReflexHost t
77 ) => Prerender js (ImmediateDomBuilderT t m) where
78 ) => Prerender js (ImmediateDomBuilderT t m) where
78 prerenderClientDict = Just Dict
79 prerenderClientDict = Just Dict
80+ startPrerenderBlock = return ()
81+ endPrerenderBlock = return ()
82+
83+instance ( HasJS js m
84+ , HasJS js (Performable m)
85+ , HasJSContext m
86+ , HasJSContext (Performable m)
87+ , MonadJSM m
88+ , MonadJSM (Performable m)
89+ , MonadFix m
90+ , MonadFix (Performable m)
91+ , ReflexHost t
92+ ) => Prerender js (HydrationDomBuilderT t m) where
93+ prerenderClientDict = Just Dict
94+ startPrerenderBlock = do
95+ depth <- HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_prerenderDepth
96+ liftIO $ modifyIORef' depth succ
97+ endPrerenderBlock = do
98+ depth <- HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_prerenderDepth
99+ liftIO $ modifyIORef' depth pred
79
100
80data NoJavaScript -- This type should never have a HasJS instance
101data NoJavaScript -- This type should never have a HasJS instance
81
102
82−instance js ~ NoJavaScript => Prerender js (StaticDomBuilderT t m) where
103+instance (Monad m, js ~ NoJavaScript, Adjustable t m, MonadIO m, MonadHold t m, MonadFix m, PerformEvent t m, MonadReflexCreateTrigger t m, MonadRef m, Ref m ~ IORef) => Prerender js (StaticDomBuilderT t m) where
83 prerenderClientDict = Nothing
104 prerenderClientDict = Nothing
105+ startPrerenderBlock = void $ commentNode $ def { _commentNodeConfig_initialContents = "prerender-start" }
106+ endPrerenderBlock = void $ commentNode $ def { _commentNodeConfig_initialContents = "prerender-end" }
107+
84
108
85instance (Prerender js m, ReflexHost t) => Prerender js (PostBuildT t m) where
109instance (Prerender js m, ReflexHost t) => Prerender js (PostBuildT t m) where
86 prerenderClientDict = fmap (\Dict -> Dict) (prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m)))
110 prerenderClientDict = fmap (\Dict -> Dict) (prerenderClientDict :: Maybe (Dict (PrerenderClientConstraint js m)))