@@ -84,7 +84,7 @@ import Data.Default
84import Data.Dependent.Map (DMap)
84import Data.Dependent.Map (DMap)
85import qualified Data.Dependent.Map as DMap
85import qualified Data.Dependent.Map as DMap
86import Data.Dependent.Sum
86import Data.Dependent.Sum
87−import Data.Foldable (foldl')
87+import Data.Foldable (foldl', for_)
88import Data.Functor.Compose
88import Data.Functor.Compose
89import Data.Functor.Constant
89import Data.Functor.Constant
90import Data.Functor.Misc
90import Data.Functor.Misc
@@ -204,8 +204,9 @@ addHydrationStepWithSetup :: (Adjustable t m, MonadIO m) => m a -> (a -> Hydrati
204addHydrationStepWithSetup setup f = getHydrationMode >>= \case
204addHydrationStepWithSetup setup f = getHydrationMode >>= \case
205 HydrationMode_Immediate -> pure ()
205 HydrationMode_Immediate -> pure ()
206 HydrationMode_Hydrating -> do
206 HydrationMode_Hydrating -> do
207− pb <- HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_switchover -- Use postBuild as a proxy for switchover to immediate mode TODO is this correct?
207+ switchover <- HydrationDomBuilderT $ asks _hydrationDomBuilderEnv_switchover
208− (s, _) <- lift $ runWithReplace setup $ return () <$ pb
209+ (s, _) <- lift $ runWithReplace setup $ return () <$ switchover -- 4% more memory than above
209 HydrationDomBuilderT $ modify' (>> f s)
210 HydrationDomBuilderT $ modify' (>> f s)
211-- | Add a hydration step
212-- | Add a hydration step
@@ -352,8 +353,6 @@ makeNodeInternal
352 :: forall node t m. (MonadJSM m, IsNode node, Typeable node)
353 :: forall node t m. (MonadJSM m, IsNode node, Typeable node)
353 => HydrationDomBuilderT t m node -> HydrationDomBuilderT t m node
354 => HydrationDomBuilderT t m node -> HydrationDomBuilderT t m node
354makeNodeInternal mkNode = do
355makeNodeInternal mkNode = do
355− liftIO $ putStr $ show (typeRep (Proxy :: Proxy node)) <> ": "
356− liftIO $ putStrLn $ "Not in hydration mode, creating node"
357 n <- mkNode
356 n <- mkNode
358 append $ toNode n
357 append $ toNode n
@@ -367,21 +366,14 @@ hydrateNode
367 => (node -> HydrationRunnerT t m Bool) -> (DOM.JSVal -> node) -> HydrationRunnerT t m node
366 => (node -> HydrationRunnerT t m Bool) -> (DOM.JSVal -> node) -> HydrationRunnerT t m node
368hydrateNode check constructor = do
367hydrateNode check constructor = do
369 parent <- askParent
368 parent <- askParent
370− liftIO $ putStr $ show (typeRep constructor) <> ": "
371 lastHydrationNode <- getPreviousNode
369 lastHydrationNode <- getPreviousNode
372 let go mLastNode = do
370 let go mLastNode = do
373 node <- maybe (Node.getFirstChildUnchecked parent) Node.getNextSiblingUnchecked mLastNode
371 node <- maybe (Node.getFirstChildUnchecked parent) Node.getNextSiblingUnchecked mLastNode
374 DOM.castTo constructor node >>= \case
372 DOM.castTo constructor node >>= \case
376− liftIO $ putStr $ "Wrong node type, skipping... "
377− go (Just node)
373+ Nothing -> go (Just node)
378 Just tn -> check tn >>= \case
374 Just tn -> check tn >>= \case
380− liftIO $ putStr $ "Using existing node... "
381− return tn
375+ True -> return tn
383− liftIO $ putStr $ "Node failed check, skipping... "
384− go (Just node)
376+ False -> go (Just node)
385 n <- go lastHydrationNode
377 n <- go lastHydrationNode
386 setPreviousNode $ Just $ toNode n
378 setPreviousNode $ Just $ toNode n
@@ -421,7 +413,7 @@ makeElement
422 -> ElementConfig er t GhcjsDomSpace
414 -> ElementConfig er t GhcjsDomSpace
423 -> HydrationDomBuilderT t m a
415 -> HydrationDomBuilderT t m a
424− -> HydrationDomBuilderT t m ((Element er GhcjsDomSpace t, a), DOM.Element)
416+ -> HydrationDomBuilderT t m ((Element er GhcjsDomSpace t, a), IORef DOM.Element)
425makeElement elementTag cfg child = do
417makeElement elementTag cfg child = do
426 doc <- askDocument
418 doc <- askDocument
427 ctx <- askJSM
419 ctx <- askJSM
@@ -469,18 +461,21 @@ makeElement elementTag cfg child = do
469 events <- askEvents
461 events <- askEvents
470 eventTriggerRefs <- wrap events e $ extractRawElementConfig cfg
462 eventTriggerRefs <- wrap events e $ extractRawElementConfig cfg
471 es <- newFanEventWithTrigger $ triggerBody eventTriggerRefs e
463 es <- newFanEventWithTrigger $ triggerBody eventTriggerRefs e
464+ e' <- liftIO $ newIORef e
472− return ((Element es e, result), e)
465+ return ((Element es e, result), e')
473 HydrationMode_Hydrating -> do
466 HydrationMode_Hydrating -> do
474 -- Schedule everything for after postBuild, except for getting the result itself
467 -- Schedule everything for after postBuild, except for getting the result itself
475 parent <- liftIO $ newIORef $ error "Parent not yet initialized"
468 parent <- liftIO $ newIORef $ error "Parent not yet initialized"
469+ e' <- liftIO $ newIORef $ error "makeElement: Element not yet initialized"
476 env <- HydrationDomBuilderT ask
470 env <- HydrationDomBuilderT ask
477− let env' = env { _hydrationDomBuilderEnv_parent = parent}
471+ let env' = env { _hydrationDomBuilderEnv_parent = parent }
478 (result, childDom) <- HydrationDomBuilderT $ lift $ lift $ runStateT (runReaderT (unHydrationDomBuilderT child) env') (pure ())
472 (result, childDom) <- HydrationDomBuilderT $ lift $ lift $ runStateT (runReaderT (unHydrationDomBuilderT child) env') (pure ())
479 wrapResult <- liftIO newEmptyMVar
473 wrapResult <- liftIO newEmptyMVar
480 let activateElement = do
474 let activateElement = do
481 e <- hydrateNode hasSSRAttribute DOM.Element
475 e <- hydrateNode hasSSRAttribute DOM.Element
482 -- Update the parent node used by the children
476 -- Update the parent node used by the children
483 liftIO $ writeIORef parent $ toNode e
477 liftIO $ writeIORef parent $ toNode e
478+ liftIO $ writeIORef e' e
484 -- Setup events, store the result so we can wait on it later
479 -- Setup events, store the result so we can wait on it later
485 refs <- wrap events e $ extractRawElementConfig cfg
480 refs <- wrap events e $ extractRawElementConfig cfg
486 liftIO $ putMVar wrapResult (e, refs)
481 liftIO $ putMVar wrapResult (e, refs)
@@ -508,7 +503,7 @@ makeElement elementTag cfg child = do
508 Just c -> c
503 Just c -> c
510 let dummyElement = error "makeElement: DOM.Element"
505 let dummyElement = error "makeElement: DOM.Element"
511− return ((Element es dummyElement, result), dummyElement)
506+ return ((Element es dummyElement, result), e')
513{-# INLINABLE textNodeInternal #-}
508{-# INLINABLE textNodeInternal #-}
514textNodeInternal :: (Adjustable t m, MonadHold t m, MonadJSM m, MonadFix m, Reflex t) => Text -> Maybe (Event t Text) -> HydrationDomBuilderT t m DOM.Text
509textNodeInternal :: (Adjustable t m, MonadHold t m, MonadJSM m, MonadFix m, Reflex t) => Text -> Maybe (Event t Text) -> HydrationDomBuilderT t m DOM.Text
@@ -566,7 +561,6 @@ commentNodeInternal !t mSetContents = getHydrationMode >>= \case
566 HydrationMode_Hydrating -> do
561 HydrationMode_Hydrating -> do
567 doc <- askDocument
562 doc <- askDocument
568 addHydrationStep $ do
563 addHydrationStep $ do
569− liftIO $ putStrLn $ "Action: Comment: " <> show (DOM.toJSString t)
570 n <- hydrateNode (const $ pure True) DOM.Comment
564 n <- hydrateNode (const $ pure True) DOM.Comment
571 mapM_ (requestDomAction_ . fmap (setNodeValue n . Just)) mSetContents
565 mapM_ (requestDomAction_ . fmap (setNodeValue n . Just)) mSetContents
572 pure $ error "commentNodeInternal: DOM.Comment"
566 pure $ error "commentNodeInternal: DOM.Comment"
@@ -601,14 +595,11 @@ skipToAndReplaceComment prefix key0Ref = getHydrationMode >>= \case
601 Just key -> do
595 Just key -> do
602 -- Replace the comment with an (invisible) text node
596 -- Replace the comment with an (invisible) text node
603 textNode <- createTextNode doc ("" :: Text)
597 textNode <- createTextNode doc ("" :: Text)
604− liftIO $ putStr "Replacing existing comment..."
605 Node.replaceChild_ parent textNode comment
598 Node.replaceChild_ parent textNode comment
606 pure (textNode, key)
599 pure (textNode, key)
607 Nothing -> do
600 Nothing -> do
608− liftIO $ putStr "Bad key in comment, skipping... "
609 go key0 (Just node)
601 go key0 (Just node)
610 Nothing -> do
602 Nothing -> do
611− liftIO $ putStr "Not a comment, skipping... "
612 go key0 (Just node)
603 go key0 (Just node)
613 switchComment = do
604 switchComment = do
614 key0 <- liftIO $ readIORef key0Ref
605 key0 <- liftIO $ readIORef key0Ref
@@ -646,73 +637,100 @@ instance (SupportsHydrationDomBuilder t m) => DomBuilder t (HydrationDomBuilderT
646 type DomBuilderSpace (HydrationDomBuilderT t m) = GhcjsDomSpace
637 type DomBuilderSpace (HydrationDomBuilderT t m) = GhcjsDomSpace
647 {-# INLINABLE textNode #-}
638 {-# INLINABLE textNode #-}
648 textNode (TextNodeConfig initialContents mSetContents) = do
639 textNode (TextNodeConfig initialContents mSetContents) = do
649− liftIO $ putStrLn $ "Text: " <> T.unpack initialContents
650 t <- textNodeInternal initialContents mSetContents
640 t <- textNodeInternal initialContents mSetContents
651 return $ TextNode t
641 return $ TextNode t
652 {-# INLINABLE commentNode #-}
642 {-# INLINABLE commentNode #-}
653 commentNode (CommentNodeConfig initialContents mSetContents) = do
643 commentNode (CommentNodeConfig initialContents mSetContents) = do
654− liftIO $ putStrLn $ "Comment: " <> T.unpack initialContents
655 c <- commentNodeInternal initialContents mSetContents
644 c <- commentNodeInternal initialContents mSetContents
656 return $ CommentNode c
645 return $ CommentNode c
657 {-# INLINABLE element #-}
646 {-# INLINABLE element #-}
658 element elementTag cfg child = do
647 element elementTag cfg child = do
659− liftIO $ putStrLn $ "Element: " <> T.unpack elementTag
660 fst <$> makeElement elementTag cfg child
648 fst <$> makeElement elementTag cfg child
661 {-# INLINABLE inputElement #-}
649 {-# INLINABLE inputElement #-}
662 inputElement cfg = do
650 inputElement cfg = do
663− ((e, _), domElement) <- makeElement "input" (cfg ^. inputElementConfig_elementConfig) $ return ()
651+ ((e, _), domElementRef) <- makeElement "input" (cfg ^. inputElementConfig_elementConfig) $ return ()
653+ (valueChangedByUI, triggerChangeByUI) <- newTriggerEvent
654+ (valueChangedBySetValue, triggerChangeBySetValue) <- newTriggerEvent
656+ (focusChange, triggerFocusChange) <- newTriggerEvent
657+ (checkedChangedByUI, triggerCheckedChangedByUI) <- newTriggerEvent
658+ (checkedChangedBySetChecked, triggerCheckedChangedBySetChecked) <- newTriggerEvent
660+ -- Expected initial value from config
661+ let v0 = _inputElementConfig_initialValue cfg
663+ addHydrationStep $ do
664+ domElement <- liftIO $ readIORef domElementRef
664− let domInputElement = uncheckedCastTo DOM.HTMLInputElement domElement
665+ let domInputElement = uncheckedCastTo DOM.HTMLInputElement domElement
665− Input.setValue domInputElement $ cfg ^. inputElementConfig_initialValue
666+ getValue = Input.getValue domInputElement
666− v0 <- Input.getValue domInputElement
667+
667− let getMyValue = Input.getValue domInputElement
668+ -- The browser might have messed with the value, or the user could have
669+ -- altered it before activation, so we set it if it isn't what we expect
670+ liftJSM getValue >>= \v0' -> do
671+ when (v0' /= v0) $ liftIO $ triggerChangeByUI v0'
673+ -- Watch for user interaction and trigger event accordingly
668− valueChangedByUI <- requestDomAction $ liftJSM getMyValue <$ Reflex.select (_element_events e) (WrapArg Input)
674+ requestDomAction_ $ (liftJSM getValue >>= liftIO . triggerChangeByUI) <$ Reflex.select (_element_events e) (WrapArg Input)
669− valueChangedBySetValue <- case _inputElementConfig_setValue cfg of
676+ case _inputElementConfig_setValue cfg of
670− Nothing -> return never
677+ Nothing -> pure ()
671− Just eSetValue -> requestDomAction $ ffor eSetValue $ \v' -> do
678+ Just eSetValue -> requestDomAction_ $ ffor eSetValue $ \v' -> do
672− Input.setValue domInputElement v'
679+ Input.setValue domInputElement v'
673− getMyValue -- We get the value after setting it in case the browser has mucked with it somehow
680+ v <- getValue -- We get the value after setting it in case the browser has mucked with it somehow
681+ liftIO $ triggerChangeBySetValue v
683+ -- TODO look at initial focus at hydration time?
674− v <- holdDyn v0 $ leftmost
684+ let focusChange' = leftmost
675− [ valueChangedBySetValue
685+ [ False <$ Reflex.select (_element_events e) (WrapArg Blur)
676− , valueChangedByUI
686+ , True <$ Reflex.select (_element_events e) (WrapArg Focus)
688+ requestDomAction_ $ liftIO . triggerFocusChange <$> focusChange'
678− Input.setChecked domInputElement $ _inputElementConfig_initialChecked cfg
690+ Input.setChecked domInputElement $ _inputElementConfig_initialChecked cfg
679− checkedChangedByUI <- wrapDomEvent domInputElement (`on` Events.click) $ do
691+ liftJSM $ domInputElement `on` Events.click $ do
680− Input.getChecked domInputElement
692+ liftIO . triggerCheckedChangedByUI =<< Input.getChecked domInputElement
681− checkedChangedBySetChecked <- case _inputElementConfig_setChecked cfg of
694+ for_ (_inputElementConfig_setChecked cfg) $ \eNewchecked ->
682− Nothing -> return never
683− Just eNewchecked -> requestDomAction $ ffor eNewchecked $ \newChecked -> do
695+ requestDomAction $ ffor eNewchecked $ \newChecked -> do
684− oldChecked <- Input.getChecked domInputElement
696+ oldChecked <- Input.getChecked domInputElement
685− Input.setChecked domInputElement newChecked
697+ Input.setChecked domInputElement newChecked
686− return $ if newChecked /= oldChecked
698+ when (newChecked /= oldChecked) $ liftIO $ triggerCheckedChangedBySetChecked newChecked
687− then Just newChecked
699+
688− else Nothing
700+-- files <- holdDyn mempty <=< wrapDomEvent domInputElement (`on` Events.change) $ do
701+-- mfiles <- Input.getFiles domInputElement
702+-- let getMyFiles xs = fmap catMaybes . mapM (FileList.item xs) . flip take [0..] . fromIntegral =<< FileList.getLength xs
703+-- maybe (return []) getMyFiles mfiles
689− c <- holdDyn (_inputElementConfig_initialChecked cfg) $ leftmost
705+ checked' <- holdDyn (_inputElementConfig_initialChecked cfg) $ leftmost
690− [ fmapMaybe id checkedChangedBySetChecked
706+ [ checkedChangedBySetChecked
691 , checkedChangedByUI
707 , checkedChangedByUI
709+ checked <- holdUniqDyn checked'
693 let initialFocus = False --TODO: Is this correct?
711 let initialFocus = False --TODO: Is this correct?
694− hasFocus <- holdDyn initialFocus $ leftmost
712+ hasFocus <- holdDyn initialFocus focusChange
695− [ False <$ Reflex.select (_element_events e) (WrapArg Blur)
713+
696− , True <$ Reflex.select (_element_events e) (WrapArg Focus)
714+ v <- holdDyn v0 $ leftmost
715+ [ valueChangedBySetValue
698− files <- holdDyn mempty <=< wrapDomEvent domInputElement (`on` Events.change) $ do
718+
699− mfiles <- Input.getFiles domInputElement
700− let getMyFiles xs = fmap catMaybes . mapM (FileList.item xs) . flip take [0..] . fromIntegral =<< FileList.getLength xs
701− maybe (return []) getMyFiles mfiles
702− checked <- holdUniqDyn c
703 return $ InputElement
719 return $ InputElement
704 { _inputElement_value = v
720 { _inputElement_value = v
705 , _inputElement_checked = checked
721 , _inputElement_checked = checked
706− , _inputElement_checkedChange = checkedChangedByUI
722+ , _inputElement_checkedChange = checkedChangedByUI
707 , _inputElement_input = valueChangedByUI
723 , _inputElement_input = valueChangedByUI
708 , _inputElement_hasFocus = hasFocus
724 , _inputElement_hasFocus = hasFocus
709 , _inputElement_element = e
725 , _inputElement_element = e
710− , _inputElement_raw = domInputElement
726+ , _inputElement_raw = undefined -- domInputElement
711− , _inputElement_files = files
727+ , _inputElement_files = undefined -- files
713 {-# INLINABLE textAreaElement #-}
730 {-# INLINABLE textAreaElement #-}
714 textAreaElement cfg = do --TODO
731 textAreaElement cfg = do --TODO
715− ((e, _), domElement) <- makeElement "textarea" (cfg ^. textAreaElementConfig_elementConfig) $ return ()
732+ ((e, _), domElement') <- makeElement "textarea" (cfg ^. textAreaElementConfig_elementConfig) $ return ()
733+ let domElement = undefined :: DOM.HTMLTextAreaElement
716 let domTextAreaElement = uncheckedCastTo DOM.HTMLTextAreaElement domElement
734 let domTextAreaElement = uncheckedCastTo DOM.HTMLTextAreaElement domElement
717 TextArea.setValue domTextAreaElement $ cfg ^. textAreaElementConfig_initialValue
735 TextArea.setValue domTextAreaElement $ cfg ^. textAreaElementConfig_initialValue
718 v0 <- TextArea.getValue domTextAreaElement
736 v0 <- TextArea.getValue domTextAreaElement
@@ -737,7 +755,8 @@ instance (SupportsHydrationDomBuilder t m) => DomBuilder t (HydrationDomBuilderT
738 {-# INLINABLE selectElement #-}
756 {-# INLINABLE selectElement #-}
739 selectElement cfg child = do
757 selectElement cfg child = do
740− ((e, result), domElement) <- makeElement "select" (cfg ^. selectElementConfig_elementConfig) child
758+ ((e, result), domElement') <- makeElement "select" (cfg ^. selectElementConfig_elementConfig) child
759+ let domElement = undefined :: DOM.HTMLSelectElement
741 let domSelectElement = uncheckedCastTo DOM.HTMLSelectElement domElement
760 let domSelectElement = uncheckedCastTo DOM.HTMLSelectElement domElement
742 Select.setValue domSelectElement $ cfg ^. selectElementConfig_initialValue
761 Select.setValue domSelectElement $ cfg ^. selectElementConfig_initialValue
743 v0 <- Select.getValue domSelectElement
762 v0 <- Select.getValue domSelectElement