@@ -36,6 +36,7 @@ import Data.Functor.Misc
36import qualified Data.Map as Map
36import qualified Data.Map as Map
37import Data.Monoid
37import Data.Monoid
38import qualified Data.Set as Set
38import qualified Data.Set as Set
39+import Data.Text (Text)
39import Data.Text.Encoding
40import Data.Text.Encoding
40import Data.Tuple
41import Data.Tuple
41import GHC.Generics
42import GHC.Generics
@@ -50,10 +51,17 @@ import Reflex.PostBuild.Base
50import Reflex.Spider
51import Reflex.Spider
51import Reflex.TriggerEvent.Class
52import Reflex.TriggerEvent.Class
53−data StaticDomBuilderEnv = StaticDomBuilderEnv { _staticDomBuilderEnv_shouldEscape :: Bool }
54+data StaticDomBuilderEnv t = StaticDomBuilderEnv
55+ { _staticDomBuilderEnv_shouldEscape :: Bool
56+ , _staticDomBuilderEnv_selectValue :: Maybe (Behavior t Text)
57+ -- ^ When the parent element is a "select" whose value has been set, this value tells us the current value.
58+ -- We use this to add a "selected" attribute to the appropriate "option" child element.
59+ -- This is not yet a perfect simulation of what the browser does, but it is much closer than doing nothing.
60+ -- TODO: Handle edge cases, e.g. setting to a value for which there is no option, then adding that option dynamically afterwards.
55newtype StaticDomBuilderT t m a = StaticDomBuilderT
63newtype StaticDomBuilderT t m a = StaticDomBuilderT
56− { unStaticDomBuilderT :: ReaderT StaticDomBuilderEnv (StateT [Behavior t Builder] m) a -- Accumulated Html will be in reversed order
64+ { unStaticDomBuilderT :: ReaderT (StaticDomBuilderEnv t) (StateT [Behavior t Builder] m) a -- Accumulated Html will be in reversed order
58 deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadException, MonadAsyncException)
66 deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadException, MonadAsyncException)
@@ -64,7 +72,7 @@ instance PrimMonad m => PrimMonad (StaticDomBuilderT x m) where
64instance MonadTrans (StaticDomBuilderT t) where
72instance MonadTrans (StaticDomBuilderT t) where
65 lift = StaticDomBuilderT . lift . lift
73 lift = StaticDomBuilderT . lift . lift
67−runStaticDomBuilderT :: (Monad m, Reflex t) => StaticDomBuilderT t m a -> StaticDomBuilderEnv -> m (a, Behavior t Builder)
75+runStaticDomBuilderT :: (Monad m, Reflex t) => StaticDomBuilderT t m a -> StaticDomBuilderEnv t -> m (a, Behavior t Builder)
68runStaticDomBuilderT (StaticDomBuilderT a) e = do
76runStaticDomBuilderT (StaticDomBuilderT a) e = do
69 (result, a') <- runStateT (runReaderT a e) []
77 (result, a') <- runStateT (runReaderT a e) []
70 return (result, mconcat $ reverse a')
78 return (result, mconcat $ reverse a')
@@ -139,9 +147,9 @@ instance (Reflex t, MonadAdjust t m, MonadHold t m) => MonadAdjust t (StaticDomB
139 runWithReplace a0 a' = do
147 runWithReplace a0 a' = do
140 e <- StaticDomBuilderT ask
148 e <- StaticDomBuilderT ask
141 (result0, result') <- lift $ runWithReplace (runStaticDomBuilderT a0 e) (flip runStaticDomBuilderT e <$> a')
149 (result0, result') <- lift $ runWithReplace (runStaticDomBuilderT a0 e) (flip runStaticDomBuilderT e <$> a')
142− o <- hold (snd result0) $ snd <$> result'
150+ o <- hold (snd result0) $ fmapCheap snd result'
143 StaticDomBuilderT $ modify $ (:) $ join o
151 StaticDomBuilderT $ modify $ (:) $ join o
144− return (fst result0, fst <$> result')
152+ return (fst result0, fmapCheap fst result')
145 sequenceDMapWithAdjust (dm0 :: DMap k (StaticDomBuilderT t m)) dm' = do
153 sequenceDMapWithAdjust (dm0 :: DMap k (StaticDomBuilderT t m)) dm' = do
146 e <- StaticDomBuilderT ask
154 e <- StaticDomBuilderT ask
147 let loweredDm0 = mapKeyValuePairsMonotonic (\(k :=> v) -> WrapArg k :=> fmap swap (runStaticDomBuilderT v e)) dm0
155 let loweredDm0 = mapKeyValuePairsMonotonic (\(k :=> v) -> WrapArg k :=> fmap swap (runStaticDomBuilderT v e)) dm0
@@ -170,7 +178,7 @@ instance SupportsStaticDomBuilder t m => DomBuilder t (StaticDomBuilderT t m) wh
170 --TODO: Do not escape quotation marks; see https://stackoverflow.com/questions/25612166/what-characters-must-be-escaped-in-html-5
178 --TODO: Do not escape quotation marks; see https://stackoverflow.com/questions/25612166/what-characters-must-be-escaped-in-html-5
171 shouldEscape <- asks _staticDomBuilderEnv_shouldEscape
179 shouldEscape <- asks _staticDomBuilderEnv_shouldEscape
172 let escape = if shouldEscape then fromHtmlEscapedText else byteString . encodeUtf8
180 let escape = if shouldEscape then fromHtmlEscapedText else byteString . encodeUtf8
173− modify . (:) <=< hold (escape initialContents) $ fmap escape setContents
181+ modify . (:) <=< hold (escape initialContents) $ fmapCheap escape setContents --Only because it doesn't get optimized when profiling is on
174 return $ TextNode ()
182 return $ TextNode ()
175 {-# INLINABLE element #-}
183 {-# INLINABLE element #-}
176 element elementTag cfg child = do
184 element elementTag cfg child = do
@@ -181,14 +189,21 @@ instance SupportsStaticDomBuilder t m => DomBuilder t (StaticDomBuilderT t m) wh
181 es <- newFanEventWithTrigger $ \_ _ -> return (return ())
189 es <- newFanEventWithTrigger $ \_ _ -> return (return ())
182 StaticDomBuilderT $ do
190 StaticDomBuilderT $ do
183 let shouldEscape = elementTag `Set.notMember` noEscapeElements
191 let shouldEscape = elementTag `Set.notMember` noEscapeElements
184− (result, innerHtml) <- lift $ lift $ runStaticDomBuilderT child $ StaticDomBuilderEnv shouldEscape
192+ (result, innerHtml) <- lift $ lift $ runStaticDomBuilderT child $ StaticDomBuilderEnv shouldEscape Nothing
185 attrs0 <- foldDyn applyMap (cfg ^. initialAttributes) (cfg ^. modifyAttributes)
193 attrs0 <- foldDyn applyMap (cfg ^. initialAttributes) (cfg ^. modifyAttributes)
194+ selectValue <- asks _staticDomBuilderEnv_selectValue
195+ let addSelectedAttr attrs sel = case Map.lookup "value" attrs of
196+ Just v | v == sel -> attrs <> Map.singleton "selected" ""
197+ _ -> Map.delete "selected" attrs
198+ let attrs1 = case (elementTag, selectValue) of
199+ ("option", Just sv) -> pull $ addSelectedAttr <$> sample (current attrs0) <*> sample sv
186− let attrs1 = ffor (current attrs0) $ mconcat . fmap (\(k, v) -> " " <> toAttr k v) . Map.toList
201+ let attrs2 = ffor attrs1 $ mconcat . fmap (\(k, v) -> " " <> toAttr k v) . Map.toList
187 let tagBS = encodeUtf8 elementTag
202 let tagBS = encodeUtf8 elementTag
188 if Set.member elementTag voidElements
203 if Set.member elementTag voidElements
189− then modify $ (:) $ mconcat [constant ("<" <> byteString tagBS), attrs1, constant (byteString " />")]
204+ then modify $ (:) $ mconcat [constant ("<" <> byteString tagBS), attrs2, constant (byteString " />")]
191− let open = mconcat [constant ("<" <> byteString tagBS), attrs1, constant (byteString ">")]
206+ let open = mconcat [constant ("<" <> byteString tagBS), attrs2, constant (byteString ">")]
192 let close = constant $ byteString $ "</" <> tagBS <> ">"
207 let close = constant $ byteString $ "</" <> tagBS <> ">"
193 modify $ (:) $ mconcat [open, innerHtml, close]
208 modify $ (:) $ mconcat [open, innerHtml, close]
194 return (Element es (), result)
209 return (Element es (), result)
@@ -222,8 +237,11 @@ instance SupportsStaticDomBuilder t m => DomBuilder t (StaticDomBuilderT t m) wh
222 , _textAreaElement_raw = ()
237 , _textAreaElement_raw = ()
224 selectElement cfg child = do
239 selectElement cfg child = do
225− (e, result) <- element "select" (_selectElementConfig_elementConfig cfg) child
226 v <- holdDyn (cfg ^. selectElementConfig_initialValue) (cfg ^. selectElementConfig_setValue)
240 v <- holdDyn (cfg ^. selectElementConfig_initialValue) (cfg ^. selectElementConfig_setValue)
241+ (e, result) <- element "select" (_selectElementConfig_elementConfig cfg) $ do
242+ (a, innerHtml) <- StaticDomBuilderT $ lift $ lift $ runStaticDomBuilderT child $ StaticDomBuilderEnv False $ Just (current v)
243+ StaticDomBuilderT $ lift $ modify $ (:) innerHtml
227 let wrapped = SelectElement
245 let wrapped = SelectElement
228 { _selectElement_value = v
246 { _selectElement_value = v
229 , _selectElement_change = never
247 , _selectElement_change = never
@@ -243,7 +261,7 @@ renderStatic :: StaticWidget x a -> IO (a, ByteString)
243renderStatic w = do
261renderStatic w = do
244 runSpiderHost $ do
262 runSpiderHost $ do
245 (postBuild, postBuildTriggerRef) <- newEventWithTriggerRef
263 (postBuild, postBuildTriggerRef) <- newEventWithTriggerRef
246− let env0 = StaticDomBuilderEnv True
264+ let env0 = StaticDomBuilderEnv True Nothing
247 ((res, bs), FireCommand fire) <- hostPerformEventT $ runStaticDomBuilderT (runPostBuildT w postBuild) env0
265 ((res, bs), FireCommand fire) <- hostPerformEventT $ runStaticDomBuilderT (runPostBuildT w postBuild) env0
248 mPostBuildTrigger <- readRef postBuildTriggerRef
266 mPostBuildTrigger <- readRef postBuildTriggerRef
249 forM_ mPostBuildTrigger $ \postBuildTrigger -> fire [postBuildTrigger :=> Identity ()] $ return ()
267 forM_ mPostBuildTrigger $ \postBuildTrigger -> fire [postBuildTrigger :=> Identity ()] $ return ()