on develop help sign in
Merge pull request #66 from reflex-frp/static-builder

Add Static Builder

commit09af3e8624d9bfdb1b9b298d234a552a30c6f2db authorRyan Trinkle <ryan.trinkle@gmail.com> dateThu 16 Jun 2016, 18:32 (10 years ago) parent8be5ac1200537d
5 files changed+198−1
default.nix+2−1 reflex-dom.cabal+2 src/Reflex/Dom.hs+2 src/Reflex/Dom/Builder/Static.hs+151 src/Reflex/Dom/Internal.hs+41
default.nix+2−1
@@ -1,6 +1,6 @@
11{ mkDerivation, pkgs, dependent-map, ghcjs-dom, lens
22, mtl, ref-tf, reflex, text, these
3−, transformers, data-default, semigroups, aeson
3+, transformers, data-default, semigroups, blaze-builder, aeson
44, ghc, webkitgtk3-javascriptcore, exception-transformers
55, webkitgtk24x, dependent-sum-template, bifunctors, bimap
66, raw-strings-qq, zenc, random, monad-control
@@ -22,6 +22,7 @@ mkDerivation {
2222 data-default
2323 semigroups
2424 ref-tf
25+ blaze-builder
2526 aeson
2627 exception-transformers
2728 dependent-sum-template
reflex-dom.cabal+2
@@ -39,6 +39,7 @@ library
3939 transformers >= 0.3 || < 0.6,
4040 lens >= 4.7 && < 4.15,
4141 text == 1.2.*,
42+ blaze-builder,
4243 bytestring == 0.10.*,
4344 data-default >= 0.5 && < 0.7,
4445 aeson >= 0.8 && < 0.12,
@@ -75,6 +76,7 @@ library
7576 Reflex.Dom.Builder.Class
7677 Reflex.Dom.Builder.Class.Events
7778 Reflex.Dom.Builder.Immediate
79+ Reflex.Dom.Builder.Static
7880 Reflex.Dom.Class
7981 Reflex.Dom.Deletable.Class
8082 Reflex.Dom.DynamicWriter
src/Reflex/Dom.hs+2
@@ -1,6 +1,7 @@
11module Reflex.Dom ( module Reflex
22 , module Reflex.Dom.Builder.Class
33 , module Reflex.Dom.Builder.Immediate
4+ , module Reflex.Dom.Builder.Static
45 , module Reflex.Dom.Class
56 , module Reflex.Dom.DynamicWriter
67 , module Reflex.Dom.Internal
@@ -17,6 +18,7 @@ module Reflex.Dom ( module Reflex
1718import Reflex
1819import Reflex.Dom.Builder.Class
1920import Reflex.Dom.Builder.Immediate
21+import Reflex.Dom.Builder.Static
2022import Reflex.Dom.Class
2123import Reflex.Dom.DynamicWriter
2224import Reflex.Dom.Internal
src/Reflex/Dom/Builder/Static.hsnew file+151
@@ -0,0 +1,151 @@
1+{-# LANGUAGE OverloadedStrings, MultiParamTypeClasses, FlexibleInstances, GeneralizedNewtypeDeriving, UndecidableInstances, FunctionalDependencies, DataKinds, TypeFamilies, RankNTypes, ConstraintKinds, TypeOperators, FlexibleContexts, LambdaCase, ScopedTypeVariables, PolyKinds, EmptyDataDecls #-}
2+module Reflex.Dom.Builder.Static where
3+
4+import Reflex
5+import Reflex.Host.Class
6+import Reflex.Dom.Builder.Class
7+import Reflex.Dom.PerformEvent.Base
8+import Reflex.Dom.PerformEvent.Class
9+import Reflex.Dom.PostBuild.Class
10+import Reflex.Dom.Widget.Basic (applyMap)
11+import Control.Monad.Ref
12+import Control.Monad.Identity
13+import Data.Dependent.Sum (DSum (..))
14+
15+import Data.Monoid
16+import Data.Text (Text)
17+import qualified Data.Map as Map
18+import Data.Map (Map)
19+import Control.Lens hiding (element)
20+import qualified Data.ByteString.Lazy as BL
21+import qualified Data.ByteString.Char8 as B8
22+import Data.ByteString (ByteString)
23+import Control.Monad.State.Strict
24+import Control.Monad.Trans.Control
25+import Data.ByteString.Builder (toLazyByteString)
26+import Blaze.ByteString.Builder.Html.Utf8
27+import Data.Text.Encoding
28+import Control.Monad.Exception
29+
30+
31+newtype StaticDomBuilderT t m a = StaticDomBuilderT
32+ { unStaticDomBuilderT :: StateT [Behavior t ByteString] m a -- Accumulated Html will be in revesed order
33+ }
34+ deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadException, MonadAsyncException)
35+
36+instance MonadTransControl (StaticDomBuilderT t) where
37+ type StT (StaticDomBuilderT t) a = StT (StateT [Behavior t ByteString]) a
38+ liftWith = defaultLiftWith StaticDomBuilderT unStaticDomBuilderT
39+ restoreT = defaultRestoreT StaticDomBuilderT
40+
41+instance MonadTrans (StaticDomBuilderT t) where
42+ lift = StaticDomBuilderT . lift
43+
44+runStaticDomBuilderT :: (Monad m, Reflex t) => StaticDomBuilderT t m a -> m (a, Behavior t ByteString)
45+runStaticDomBuilderT (StaticDomBuilderT a) = do
46+ (result, a') <- runStateT a []
47+ return (result, mconcat $ reverse a')
48+
49+instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (StaticDomBuilderT t m) where
50+ {-# INLINABLE newEventWithTrigger #-}
51+ newEventWithTrigger = lift . newEventWithTrigger
52+ {-# INLINABLE newFanEventWithTrigger #-}
53+ newFanEventWithTrigger f = lift $ newFanEventWithTrigger f
54+
55+instance PerformEvent t m => PerformEvent t (StaticDomBuilderT t m) where
56+ type Performable (StaticDomBuilderT t m) = Performable m
57+ {-# INLINABLE performEvent_ #-}
58+ performEvent_ e = lift $ performEvent_ e
59+ {-# INLINABLE performEvent #-}
60+ performEvent e = lift $ performEvent e
61+
62+instance MonadSample t m => MonadSample t (StaticDomBuilderT t m) where
63+ {-# INLINABLE sample #-}
64+ sample = lift . sample
65+
66+instance MonadHold t m => MonadHold t (StaticDomBuilderT t m) where
67+ {-# INLINABLE hold #-}
68+ hold v0 v' = lift $ hold v0 v'
69+ {-# INLINABLE holdDyn #-}
70+ holdDyn v0 v' = lift $ holdDyn v0 v'
71+ {-# INLINABLE holdIncremental #-}
72+ holdIncremental v0 v' = lift $ holdIncremental v0 v'
73+
74+instance (Reflex t, Monad m, MonadHold t (StateT [Behavior t ByteString] m)) => Deletable t (StaticDomBuilderT t m) where
75+ {-# INLINABLE deletable #-}
76+ deletable delete (StaticDomBuilderT a) = StaticDomBuilderT $ do
77+ (result, a') <- lift $ runStateT a []
78+ let html = mconcat $ reverse a'
79+ b <- hold html (mempty <$ delete)
80+ modify (join b:)
81+ return result
82+
83+instance (Monad m, Ref m ~ Ref IO, Reflex t) => TriggerEvent t (StaticDomBuilderT t m) where
84+ {-# INLINABLE newTriggerEvent #-}
85+ newTriggerEvent = return (never, const $ return ())
86+ {-# INLINABLE newTriggerEventWithOnComplete #-}
87+ newTriggerEventWithOnComplete = return (never, \_ _ -> return ())
88+ {-# INLINABLE newEventWithLazyTriggerWithOnComplete #-}
89+ newEventWithLazyTriggerWithOnComplete _ = return never
90+
91+type SupportsStaticDomBuilder t m = (Reflex t, MonadIO m, MonadHold t m, MonadFix m, PerformEvent t m, Performable m ~ m, MonadReflexCreateTrigger t m, Deletable t m, MonadRef m, Ref m ~ Ref IO)
92+
93+data StaticDomSpace
94+instance DomSpace StaticDomSpace where
95+ type RawElement StaticDomSpace = ByteString
96+
97+instance SupportsStaticDomBuilder t m => DomBuilder t (StaticDomBuilderT t m) where
98+ type DomBuilderSpace (StaticDomBuilderT t m) = StaticDomSpace
99+ {-# INLINABLE textNode #-}
100+ textNode (TextNodeConfig contents) = StaticDomBuilderT $ do
101+ modify $ (:) $ constant $ BL.toStrict $ toLazyByteString $ fromHtmlEscapedText contents
102+ return TextNode
103+
104+ {-# INLINABLE element #-}
105+ element elementTag cfg child = do
106+ let toAttr (_mns, k) v = encodeUtf8 k <> "=\"" <> (BL.toStrict $ toLazyByteString $ fromHtmlEscapedText v) <> "\""
107+ es <- newFanEventWithTrigger $ \_ _ -> return (return ())
108+ StaticDomBuilderT $ do
109+ (result, innerHtml) <- lift $ runStaticDomBuilderT child
110+ attrs0 <- foldDyn applyMap (cfg ^. initialAttributes) (cfg ^. modifyAttributes)
111+ let attrs1 = ffor (current attrs0) $ mconcat . fmap (\(k, v) -> " " <> toAttr k v) . Map.toList
112+ let tag = encodeUtf8 elementTag
113+ let open = mconcat [constant ("<" <> tag <> " "), attrs1, constant ">"]
114+ let close = constant $ "</" <> tag <> ">" -- TODO handle elements without closing tags
115+ modify $ (:) $ mconcat $ [open, innerHtml, close]
116+ return (Element es (error "Static.element RawElement was used"), result)
117+
118+ {-# INLINABLE placeholder #-}
119+ placeholder (PlaceholderConfig toInsertAbove _delete) = StaticDomBuilderT $ do
120+ result <- lift $ performEvent (fmap runStaticDomBuilderT toInsertAbove)
121+ acc <- foldDyn (:) [] (fmap snd result)
122+ modify $ (:) $ join $ fmap (mconcat . reverse) $ current acc
123+ return $ Placeholder (fmap fst result) never
124+
125+ {-# INLINABLE inputElement #-}
126+ inputElement cfg = do
127+ (e, _result) <- element "input" (cfg ^. inputElementConfig_elementConfig) $ return ()
128+ let v0 = constDyn $ cfg ^. inputElementConfig_initialValue
129+ let c0 = constDyn $ cfg ^. inputElementConfig_initialChecked
130+ let hasFocus = constDyn False -- TODO should this be coming from initialAtttributes
131+ return $ InputElement v0 c0 never hasFocus e
132+
133+ {-# INLINABLE textAreaElement #-}
134+ textAreaElement cfg = do
135+ (e, _domElement) <- element "textarea" (cfg ^. textAreaElementConfig_elementConfig) $ return ()
136+ let v0 = constDyn $ cfg ^. textAreaElementConfig_initialValue
137+ let hasFocus = constDyn False -- TODO should this be coming from initialAtttributes
138+ return $ TextAreaElement v0 never hasFocus e
139+
140+--TODO: Make this more abstract --TODO: Put the WithWebView underneath PerformEventT - I think this would perform better
141+type StaticWidget x = PostBuildT Spider (StaticDomBuilderT Spider (PerformEventT Spider (SpiderHost Global)))
142+
143+renderStatic :: StaticWidget x a -> IO (a, ByteString)
144+renderStatic w = do
145+ runSpiderHost $ do
146+ (postBuild, postBuildTriggerRef) <- newEventWithTriggerRef
147+ ((res, bs), FireCommand fire) <- hostPerformEventT $ runStaticDomBuilderT (runPostBuildT w postBuild)
148+ mPostBuildTrigger <- readRef postBuildTriggerRef
149+ forM_ mPostBuildTrigger $ \postBuildTrigger -> fire [postBuildTrigger :=> Identity ()] $ return ()
150+ bs' <- sample bs
151+ return (res, bs')
src/Reflex/Dom/Internal.hs+41
@@ -57,6 +57,13 @@ mainWidgetWithCss css w = runWebGUI $ \webView -> withWebViewSingleton webView $
5757 Just body <- getBody doc
5858 attachWidget body webViewSing w
5959
60+{-# INLINABLE mainWidgetFragment #-}
61+mainWidgetFragment :: (forall x. Widget x ()) -> IO ()
62+mainWidgetFragment w = runWebGUI $ \webView -> withWebViewSingleton webView $ \webViewSing -> do
63+ Just doc <- liftM (fmap DOM.castToHTMLDocument) $ webViewGetDomDocument webView
64+ Just body <- getBody doc
65+ attachWidgetFragment body webViewSing w
66+
6067type Widget x = PostBuildT Spider (ImmediateDomBuilderT Spider (WithWebView x (PerformEventT Spider (SpiderHost Global)))) --TODO: Make this more abstract --TODO: Put the WithWebView underneath PerformEventT - I think this would perform better
6168
6269{-# INLINABLE attachWidget #-}
@@ -91,6 +98,40 @@ attachWidget' rootElement wv w = do
9198 return ()
9299 return (result, fc)
93100
101+{-# INLINABLE attachWidgetFragment #-}
102+attachWidgetFragment :: DOM.IsElement e => e -> WebViewSingleton x -> Widget x a -> IO a
103+attachWidgetFragment rootElement wv w = fst <$> attachWidgetFragment' rootElement wv w
104+
105+{-# INLINABLE attachWidgetFragment' #-}
106+attachWidgetFragment' :: DOM.IsElement e => e -> WebViewSingleton x -> Widget x a -> IO (a, FireCommand Spider (SpiderHost Global))
107+attachWidgetFragment' rootElement wv w = do
108+ Just doc <- getOwnerDocument rootElement
109+ Just df <- createDocumentFragment doc
110+ events <- newChan
111+ (result, fc@(FireCommand fire)) <- runSpiderHost $ do
112+ (postBuild, postBuildTriggerRef) <- newEventWithTriggerRef
113+ let builderEnv = ImmediateDomBuilderEnv
114+ { _immediateDomBuilderEnv_document = doc
115+ , _immediateDomBuilderEnv_parent = toNode df
116+ , _immediateDomBuilderEnv_events = events
117+ }
118+ results@(_, FireCommand fire) <- hostPerformEventT $ runWithWebView (runImmediateDomBuilderT (runPostBuildT w postBuild) builderEnv) wv
119+ mPostBuildTrigger <- readRef postBuildTriggerRef
120+ forM_ mPostBuildTrigger $ \postBuildTrigger -> fire [postBuildTrigger :=> Identity ()] $ return ()
121+ return results
122+ void $ forkIO $ forever $ do
123+ ers <- readChan events
124+ _ <- postGUISync $ runSpiderHost $ do
125+ mes <- liftIO $ forM ers $ \(TriggerRef er :=> TriggerInvocation a _) -> do
126+ me <- readIORef er
127+ return $ fmap (\e -> e :=> Identity a) me
128+ _ <- fire (catMaybes mes) $ return ()
129+ liftIO $ forM_ ers $ \(_ :=> TriggerInvocation _ cb) -> cb
130+ return ()
131+ setInnerHTML rootElement $ Just (""::String)
132+ appendChild rootElement $ Just df
133+ return (result, fc)
134+
94135data AppInput t = AppInput
95136 { _appInput_window :: Window t
96137 }