diff --git a/htmlt.cabal b/htmlt.cabal
index 04c0b14..dadeab8 100644
--- a/htmlt.cabal
+++ b/htmlt.cabal
@@ -95,11 +95,12 @@ library
other-modules: Paths_htmlt
build-depends:
base,
- ghcjs-base,
containers,
exceptions,
+ ghcjs-base,
+ monad-control,
mtl,
- text
+ text,
if impl(ghcjs)
build-depends:
ghcjs-prim,
diff --git a/src/HtmlT/Base.hs b/src/HtmlT/Base.hs
index 197bb96..987aabf 100644
--- a/src/HtmlT/Base.hs
+++ b/src/HtmlT/Base.hs
@@ -3,6 +3,7 @@ module HtmlT.Base where
import Control.Monad.Reader
import Data.Coerce
+import Data.Function hiding (on)
import Data.Foldable
import Data.IORef
import Data.JSString.Text as JSS
@@ -10,6 +11,8 @@ import Data.Text as T hiding (index)
import GHCJS.Marshal
import JavaScript.Object as Object
import JavaScript.Object.Internal
+import Data.Generics.Product
+import Control.Monad.Trans.Control
import HtmlT.DOM
import HtmlT.Decode
@@ -24,10 +27,10 @@ import HtmlT.Types
-- > el "div" do
-- > prop "className" "container"
-- > el "span" $ text "Lorem Ipsum"
-el :: Text -> Html a -> Html a
+el :: MonadHtml e m => Text -> m a -> m a
el tag child = do
newRootEl <- liftIO (createElement tag)
- appendHtmlT newRootEl child
+ appendHtml newRootEl child
-- | Same as 'el' but allows to specify element's namespace
-- https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS
@@ -37,19 +40,19 @@ el tag child = do
-- > prop "width" "400"
-- > elns "http://www.w3.org/2000/svg" "path" do
-- > prop "d" "M150 0 L75 200 L225 200 Z"
-elns :: Text -> Text -> Html a -> Html a
+elns :: MonadHtml e m => Text -> Text -> m a -> m a
elns ns tag child = do
newRootEl <- liftIO (createElementNS ns tag)
- appendHtmlT newRootEl child
+ appendHtml newRootEl child
-- | Create a TextNode and attach it to 'html_current_element'
-text :: Text -> Html ()
+text :: MonadHtml e m => Text -> m ()
text txt = do
textNode <- liftIO (createTextNode txt)
insertNode textNode
-- | Create a TextNode with dynamic content
-dynText :: Dynamic Text -> Html ()
+dynText :: MonadHtmlDyn e m => Dynamic Text -> m ()
dynText d = do
txt <- readDyn d
textNode <- liftIO (createTextNode txt)
@@ -60,20 +63,20 @@ dynText d = do
-- | Assign a property to 'html_current_element'. Don't confuse
-- attributes and properties
-- https://stackoverflow.com/questions/6003819/what-is-the-difference-between-properties-and-attributes-in-html
-prop :: ToJSVal v => Text -> v -> Html ()
+prop :: (ToJSVal v, MonadHtml e m) => Text -> v -> m ()
prop (JSS.textToJSString -> key) val = do
- rootEl <- asks html_current_element
+ rootEl <- asks (unCurrentDOMElement . getTyped)
v <- liftIO $ toJSVal val
liftIO $ Object.setProp key v (coerce rootEl)
-- | Assign a property with dynamic content to the root element
dynProp
- :: (ToJSVal v, FromJSVal v, Eq v)
+ :: (ToJSVal v, FromJSVal v, Eq v, MonadHtmlDyn e m)
=> Text
-> Dynamic v
- -> Html ()
+ -> m ()
dynProp textKey dyn = do
- rootEl <- asks html_current_element
+ rootEl <- asks (unCurrentDOMElement . getTyped)
forDyn_ dyn (liftIO . setup rootEl)
where
setup el t = toJSVal t
@@ -83,14 +86,14 @@ dynProp textKey dyn = do
-- | Assign an attribute to the root element. Don't confuse attributes
-- and properties
-- https://stackoverflow.com/questions/6003819/what-is-the-difference-between-properties-and-attributes-in-html
-attr :: Text -> Text -> Html ()
-attr k v = asks html_current_element
+attr :: MonadHtml e m => Text -> Text -> m ()
+attr k v = asks (unCurrentDOMElement . getTyped)
>>= \e -> liftIO (setAttribute e k v)
-- | Assign an attribute with dynamic content to the root element
-dynAttr :: Text -> Dynamic Text -> Html ()
+dynAttr :: MonadHtmlDyn e m => Text -> Dynamic Text -> m ()
dynAttr k d = do
- rootEl <- asks html_current_element
+ rootEl <- asks (unCurrentDOMElement . getTyped)
forDyn_ d $ liftIO . setAttribute rootEl k
-- | Attach listener to the root element. First agument is the name
@@ -101,28 +104,41 @@ dynAttr k d = do
-- > on "click" \_event -> do
-- > liftIO $ putStrLn "Clicked!"
-- > text "Click here"
-on :: EventName -> (DOMEvent -> Transact ()) -> Html ()
-on name f = ask >>= \HtmlEnv{..} ->
- onGlobalEvent defaultListenerOpts (nodeFromElement html_current_element) name f
+on :: MonadHtmlDyn e m => EventName -> (DOMEvent -> Transact ()) -> m ()
+on name f = do
+ curElm <- asks (unCurrentDOMElement . getTyped)
+ onGlobalEvent defaultListenerOpts (nodeFromElement curElm) name f
-- | Same as 'on' but ignores 'DOMEvent' inside the callback
-on_ :: EventName -> Transact () -> Html ()
+on_ :: MonadHtmlDyn e m => EventName -> Transact () -> m ()
on_ name = on name . const
-- | Same as 'on' but allows to specify 'ListenerOpts'
-onOptions :: EventName -> ListenerOpts -> (DOMEvent -> Transact ()) -> Html ()
-onOptions name opts f = ask >>= \HtmlEnv{..} ->
- onGlobalEvent opts (nodeFromElement html_current_element) name f
+onOptions
+ :: MonadHtmlDyn e m
+ => EventName
+ -> ListenerOpts
+ -> (DOMEvent -> Transact ())
+ -> m ()
+onOptions name opts f = do
+ curElm <- asks (unCurrentDOMElement . getTyped)
+ onGlobalEvent opts (nodeFromElement curElm) name f
-- | Attach listener, extract data of type @a@ using specified decoder
-onDecoder :: EventName -> Decoder a -> (a -> Transact ()) -> Html ()
+onDecoder
+ :: MonadHtmlDyn e m
+ => EventName
+ -> Decoder a
+ -> (a -> Transact ())
+ -> m ()
onDecoder name dec = on name . withDecoder dec
-- | Attach a listener to arbitrary target, not just the current root
-- element (usually that would be @window@, @document@ or @body@
-- objects)
onGlobalEvent
- :: ListenerOpts
+ :: MonadHtmlDyn e m
+ => ListenerOpts
-- ^ Specify whether to call @event.stopPropagation()@ and
-- @event.preventDefault()@ on the fired event
-> DOMNode
@@ -131,7 +147,7 @@ onGlobalEvent
-- ^ Event name
-> (DOMEvent -> Transact ())
-- ^ Callback that accepts reference to the DOM event
- -> Html ()
+ -> m ()
onGlobalEvent opts target name f = do
let
event = Event \_ callback -> liftIO do
@@ -162,9 +178,9 @@ withDecoder dec f domEvent =
-- > el "div" do
-- > classes "container row"
-- > classes "mt-1 mb-2"
-classes :: Text -> Html ()
+classes :: MonadHtml e m => Text -> m ()
classes cs = do
- rootEl <- asks html_current_element
+ rootEl <- asks (unCurrentDOMElement . getTyped)
for_ (T.splitOn " " cs) $ liftIO . classListAdd rootEl
-- | Assign a single CSS class dynamically based on the value held by
@@ -176,9 +192,9 @@ classes cs = do
-- > el "button" do
-- > on_ "click" $ modifyRef showRef not
-- > text "Toggle visibility"
-toggleClass :: Text -> Dynamic Bool -> Html ()
+toggleClass :: MonadHtmlDyn e m => Text -> Dynamic Bool -> m ()
toggleClass cs dyn = do
- rootEl <- asks html_current_element
+ rootEl <- asks (unCurrentDOMElement . getTyped)
forDyn_ dyn (liftIO . setup rootEl cs)
where
setup rootEl cs = \case
@@ -194,9 +210,9 @@ toggleClass cs dyn = do
-- > el "button" do
-- > on_ "click" $ modifyRef hiddenRef not
-- > text "Toggle visibility"
-toggleAttr :: Text -> Dynamic Bool -> Html ()
+toggleAttr :: MonadHtmlDyn e m => Text -> Dynamic Bool -> m ()
toggleAttr att dyn = do
- rootEl <- asks html_current_element
+ rootEl <- asks (unCurrentDOMElement . getTyped)
forDyn_ dyn (liftIO . setup rootEl att)
where
setup rootEl name = \case
@@ -211,9 +227,9 @@ toggleAttr att dyn = do
-- > dynStyle "background" $ bool "initial" "red" <$> fromRef colorRef
-- > on_ "click" $ modifyRef colorRef not
-- > text "Toggle background color"
-dynStyle :: Text -> Dynamic Text -> Html ()
+dynStyle :: MonadHtmlDyn e m => Text -> Dynamic Text -> m ()
dynStyle cssProp dyn = do
- rootEl <- asks html_current_element
+ rootEl <- asks (unCurrentDOMElement . getTyped)
forDyn_ dyn (liftIO . setup rootEl)
where
setup el t = do
@@ -237,20 +253,21 @@ blank = pure ()
-- > on_ "click" $ modifyRef listRef ("New Item":)
-- > text "Append new item"
simpleList
- :: forall a. DynRef [a]
+ :: forall a e m. MonadHtmlDyn e m
+ => DynRef [a]
-- ^ Some dynamic data from the above scope
- -> (Int -> DynRef a -> Html ())
+ -> (Int -> DynRef a -> m ())
-- ^ Function to build children widget. Accepts the index inside the
-- collection and dynamic data for that particular element
- -> Html ()
+ -> m ()
simpleList dynRef h = do
- htmlEnv <- ask
+ htmlEnv::e <- ask
prevValue <- liftIO $ newIORef []
- elemEnvsRef <- liftIO $ newIORef ([] :: [ElemEnv a])
+ elemEnvsRef <- liftIO $ newIORef ([] :: [ElemEnv e a])
let
- reactiveEnv = html_reactive_env htmlEnv
- setup :: Int -> [a] -> [a] -> [ElemEnv a] -> IO [ElemEnv a]
- setup idx old new refs = case (refs, old, new) of
+ reactiveEnv = getTyped htmlEnv
+ setup :: RunInBase m IO -> Int -> [a] -> [a] -> [ElemEnv e a] -> IO [ElemEnv e a]
+ setup liftBase idx old new refs = case (refs, old, new) of
(_, [], []) -> return []
([], [], x:xs) -> do
-- New list is longer, append new elements
@@ -260,14 +277,19 @@ simpleList dynRef h = do
controlledRef = elemRef
{dynref_modifier=elemModifier idx (fromRef elemRef)
}
- newEnv = htmlEnv
- { html_reactive_env = reactiveEnv {renv_finalizers = finalizers}
+ initEnv = HtmlEnv
+ { html_reactive_env = getTyped htmlEnv
+ , html_current_element = getTyped htmlEnv
+ , html_content_boundary = getTyped htmlEnv
}
- boundary <- execHtmlT newEnv insertBoundary
- execHtmlT newEnv {html_content_boundary = Just boundary} $
- h idx controlledRef
+ boundary <- execHtmlT initEnv insertBoundary
+ let
+ mkNewEnv = setTyped (reactiveEnv {renv_finalizers = finalizers})
+ . setTyped (MaybeContentBoundary (Just boundary))
+ newEnv = mkNewEnv htmlEnv
+ liftBase $ local (const newEnv) $ h idx controlledRef
let itemRef = ElemEnv newEnv (dynref_modifier elemRef) boundary
- (itemRef:) <$> setup (idx + 1) [] xs []
+ (itemRef:) <$> setup liftBase (idx + 1) [] xs []
(r:rs, _:_, []) -> do
-- New list is shorter, delete the elements that no longer
-- present in the new list
@@ -276,12 +298,12 @@ simpleList dynRef h = do
(r:rs, _:xs, y:ys) -> do
-- Update child elements along the way
liftIO $ sync $ ee_modifier r \_ -> y
- (r:) <$> setup (idx + 1) xs ys rs
+ (r:) <$> setup liftBase (idx + 1) xs ys rs
(_, _, _) -> do
error "simpleList: Incoherent internal state"
finalizeElems = traverse_ \ElemEnv{..} -> liftIO do
removeBoundary ee_boundary
- let fins = renv_finalizers $ html_reactive_env ee_html_env
+ let fins = renv_finalizers $ getTyped ee_html_env
readIORef fins >>= sequence_
elemModifier :: Int -> Dynamic a -> (a -> a) -> Transact ()
elemModifier i dyn f = do
@@ -292,11 +314,12 @@ simpleList dynRef h = do
overIx _ [] = []
dynref_modifier dynRef (overIx i)
addFinalizer $ readIORef elemEnvsRef >>= finalizeElems
- forDyn_ (fromRef dynRef) \new -> liftIO do
- old <- atomicModifyIORef' prevValue (new,)
- eenvs <- readIORef elemEnvsRef
- newEenvs <- setup 0 old new eenvs
- writeIORef elemEnvsRef newEenvs
+ liftBaseWith \liftBase -> execReactiveT (getTyped htmlEnv) $
+ forDyn_ (fromRef dynRef) \new -> liftIO do
+ old <- atomicModifyIORef' prevValue (new,)
+ eenvs <- readIORef elemEnvsRef
+ newEenvs <- setup liftBase 0 old new eenvs
+ writeIORef elemEnvsRef newEenvs
-- | First build a DOM with the widget that is currently held by the
-- given Dynamic, then rebuild it every time Dynamic's value
@@ -311,54 +334,54 @@ simpleList dynRef h = do
-- > el "button" do
-- > on_ "click" $ writeRef routeRef Blog
-- > text "Show my blog page"
-dyn :: Dynamic (Html ()) -> Html ()
-dyn d = do
+dyn :: forall e m. MonadHtmlDyn e m => Dynamic (m ()) -> m ()
+dyn contentDyn = do
htmlEnv <- ask
childRef <- liftIO (newIORef Nothing)
boundary <- insertBoundary
let
- finalizeEnv newEnv = do
+ replaceEnv newEnv = do
readIORef childRef >>= \case
- Just HtmlEnv{..} -> do
- finalizers <- readIORef $ renv_finalizers html_reactive_env
+ Just (oldEnv :: e) -> do
+ let finalizersRef = oldEnv & getTyped @ReactiveEnv & renv_finalizers
+ finalizers <- readIORef finalizersRef
sequence_ finalizers
- writeIORef (renv_finalizers html_reactive_env) []
+ writeIORef finalizersRef []
Nothing -> return ()
writeIORef childRef newEnv
- setup html = liftIO do
- finalizers <- newIORef []
+ setup liftBase html = do
+ finalizers <- liftIO $ newIORef []
let
- newEnv = htmlEnv
- { html_reactive_env = (html_reactive_env htmlEnv)
- { renv_finalizers = finalizers }
- , html_content_boundary = Just boundary
- }
- finalizeEnv (Just newEnv)
- clearBoundary boundary
- execHtmlT newEnv html
- addFinalizer (finalizeEnv Nothing)
- forDyn_ d setup
+ newREnv = (getTyped htmlEnv) {renv_finalizers = finalizers}
+ newEnv = htmlEnv & setTyped newREnv
+ & setTyped (MaybeContentBoundary (Just boundary))
+ liftIO $ replaceEnv (Just newEnv)
+ liftIO $ clearBoundary boundary
+ void $ liftBase $ local (const newEnv) html
+ addFinalizer (replaceEnv Nothing)
+ liftBaseWith \liftBase -> execReactiveT (getTyped htmlEnv) $
+ forDyn_ contentDyn (liftIO . setup liftBase)
-- | Run an action before the current node is detached from the DOM
-addFinalizer :: MonadReactive m => IO () -> m ()
+addFinalizer :: MonadReactive e m => IO () -> m ()
addFinalizer fin = do
- ReactiveEnv{..} <- askReactiveEnv
+ ReactiveEnv{..} <- asks getTyped
liftIO $ modifyIORef renv_finalizers (fin:)
-- | Attach resulting DOM to the given node instead of
-- 'html_current_element'. Might be useful for implementing modal
-- dialogs, tooltips etc. Similar to what called portals in React
-- ecosystem
-portal :: MonadIO m => DOMElement -> HtmlT m a -> HtmlT m a
+portal :: MonadHtmlDyn e m => DOMElement -> m a -> m a
portal newRootEl html = do
- boundary <- local (\e -> e
- { html_current_element = newRootEl
- , html_content_boundary = Nothing
- }) insertBoundary
- result <- local (\e -> e
- { html_current_element = newRootEl
- , html_content_boundary = Just boundary
- }) html
+ boundary <- local
+ ( setTyped (CurrentDOMElement newRootEl)
+ . setTyped (MaybeContentBoundary Nothing)
+ ) insertBoundary
+ result <- local
+ ( setTyped (CurrentDOMElement newRootEl)
+ . setTyped (MaybeContentBoundary (Just boundary))
+ ) html
addFinalizer $ removeBoundary boundary
return result
@@ -372,9 +395,9 @@ portal newRootEl html = do
-- > unsafeHtml ""
-unsafeHtml :: MonadIO m => Text -> HtmlT m ()
+unsafeHtml :: MonadHtml e m => Text -> m ()
unsafeHtml htmlText = do
- HtmlEnv{..} <- ask
- let anchor = fmap boundary_end html_content_boundary
- liftIO $ unsafeInsertHtml html_current_element anchor
- htmlText
+ contBoundary <- asks (unMaybeContentBoundary . getTyped)
+ curElm <- asks (unCurrentDOMElement . getTyped)
+ let anchor = fmap boundary_end contBoundary
+ liftIO $ unsafeInsertHtml curElm anchor htmlText
diff --git a/src/HtmlT/DOM.hs b/src/HtmlT/DOM.hs
index 9182ab3..d6a50a9 100644
--- a/src/HtmlT/DOM.hs
+++ b/src/HtmlT/DOM.hs
@@ -9,7 +9,6 @@ module HtmlT.DOM where
import Control.Monad.Reader
import Data.Coerce
-import Data.String
import Data.Text as T
import Data.JSString.Text
import GHC.Generics
@@ -401,12 +400,3 @@ foreign import javascript unsafe
}"
js_callbackWithOptions :: Bool -> Bool -> Callback (JSVal -> IO ()) -> IO (Callback (JSVal -> IO ()))
#endif
-
-instance (a ~ (), MonadIO m) => IsString (HtmlT m a) where
- fromString s = do
- HtmlEnv{..} <- ask
- textNode <- liftIO $ createTextNode (T.pack s)
- case html_content_boundary of
- Just ContentBoundary{..} -> liftIO $
- js_insertBefore html_current_element textNode boundary_end
- Nothing -> liftIO $ appendChild html_current_element textNode
diff --git a/src/HtmlT/Event.hs b/src/HtmlT/Event.hs
index 29221d5..7bb3bda 100644
--- a/src/HtmlT/Event.hs
+++ b/src/HtmlT/Event.hs
@@ -12,6 +12,7 @@ import Control.Monad.Identity
import Control.Monad.Reader
import Control.Monad.State
import Data.Foldable
+import Data.Generics.Product
import Data.IORef
import Data.Maybe
import Debug.Trace
@@ -76,7 +77,7 @@ data ReactiveEnv = ReactiveEnv
newtype ReactiveT m a = ReactiveT
{ unReactiveT :: ReaderT ReactiveEnv m a
} deriving newtype (Functor, Applicative, Monad, MonadIO
- , MonadFix, MonadCatch, MonadThrow, MonadMask)
+ , MonadFix, MonadCatch, MonadThrow, MonadMask, MonadReader ReactiveEnv)
-- | Identify events inside 'TransactState' and 'ReactiveEnv'
newtype EventId = EventId {unEventId :: Int}
@@ -86,8 +87,6 @@ newtype EventId = EventId {unEventId :: Int}
newtype SubscriptionId = SubscriptionId {unSubscriptionId :: Int}
deriving newtype (Eq, Ord, Show)
-class HasReactiveEnv m where askReactiveEnv :: m ReactiveEnv
-
type Lens' s a = forall f. Functor f => (a -> f a) -> s -> f s
type Callback a = a -> Transact ()
@@ -98,7 +97,11 @@ type Modifier a = (a -> a) -> Transact ()
type Canceller = IO ()
-type MonadReactive m = (HasReactiveEnv m, MonadIO m)
+type MonadReactive e m =
+ ( MonadReader e m
+ , MonadIO m
+ , HasType ReactiveEnv e
+ )
-- | Create new empty 'ReactiveEnv'
newReactiveEnv :: MonadIO m => m ReactiveEnv
@@ -112,9 +115,9 @@ newReactiveEnv = liftIO do
--
-- > (event, push) <- newEvent @String
-- > push "New Value" -- event fires with given value
-newEvent :: forall a m. MonadReactive m => m (Event a, Trigger a)
+newEvent :: forall a e m. MonadReactive e m => m (Event a, Trigger a)
newEvent = do
- renv <- askReactiveEnv
+ renv <- asks getTyped
eventId <- EventId <$> liftIO (nextIntId renv)
let event = Event $ subscribeImpl eventId
return (event, triggerImpl eventId renv)
@@ -123,7 +126,7 @@ newEvent = do
--
-- > showRef <- newRef False
-- > writeRef showRef True -- update event fires for showRef
-newRef :: forall a m. MonadReactive m => a -> m (DynRef a)
+newRef :: forall a e m. MonadReactive e m => a -> m (DynRef a)
newRef initial = do
ref <- liftIO $ newIORef initial
(ev, push) <- newEvent
@@ -202,9 +205,9 @@ updates = dynamic_updates
-- | Attach a listener to the event and return an action to detach the
-- listener
-subscribe :: MonadReactive m => Event a -> Callback a -> m Canceller
+subscribe :: MonadReactive e m => Event a -> Callback a -> m Canceller
subscribe (Event s) k = do
- e@ReactiveEnv{..} <- askReactiveEnv
+ e@ReactiveEnv{..} <- asks getTyped
cancel <- liftIO $ s e k
liftIO $ modifyIORef' renv_finalizers (cancel:)
return cancel
@@ -212,13 +215,13 @@ subscribe (Event s) k = do
-- | Perform an action with current value of the given 'Dynamic' and
-- each time the value changes. Return action to detach listener from
-- receiving new values
-forDyn :: MonadReactive m => Dynamic a -> Callback a -> m Canceller
+forDyn :: MonadReactive e m => Dynamic a -> Callback a -> m Canceller
forDyn d k = do
liftIO $ dynamic_read d >>= sync . k
subscribe (dynamic_updates d) k
-- | Same as 'forDyn', ignore the result
-forDyn_ :: MonadReactive m => Dynamic a -> Callback a -> m ()
+forDyn_ :: MonadReactive e m => Dynamic a -> Callback a -> m ()
forDyn_ = (void .) . forDyn
-- | Filter and map occurences
@@ -314,7 +317,7 @@ traceRefWith f DynRef{..} = DynRef
-- called once every time 'Dynamic a' value changes, whereas in 'fmap'
-- it would be called once for each subscription per change event
mapDyn
- :: MonadReactive m
+ :: MonadReactive e m
=> Dynamic a
-> (a -> b)
-> m (Dynamic b)
@@ -322,7 +325,7 @@ mapDyn dynA f = do
initialA <- liftIO $ dynamic_read dynA
latestA <- liftIO $ newIORef initialA
latestB <- liftIO $ newIORef (f initialA)
- renv <- askReactiveEnv
+ renv <- asks getTyped
eventId <- EventId <$> liftIO (nextIntId renv)
let
updates = Event $ subscribeImpl eventId
@@ -339,7 +342,7 @@ mapDyn dynA f = do
-- any of the two Dynamics changes its value
-- TODO: More general version of mapDynX
mapDyn2
- :: MonadReactive m
+ :: MonadReactive e m
=> Dynamic a
-> Dynamic b
-> (a -> b -> c)
@@ -350,7 +353,7 @@ mapDyn2 aDyn bDyn f = do
latestA <- liftIO $ newIORef initialA
latestB <- liftIO $ newIORef initialB
latestC <- liftIO $ newIORef (f initialA initialB)
- renv <- askReactiveEnv
+ renv <- asks getTyped
eventId <- EventId <$> liftIO (nextIntId renv)
let
fire = defer eventId do
@@ -454,6 +457,3 @@ instance Semigroup TransactState where
instance Monoid TransactState where
mempty = TransactState mempty
-
-instance Applicative m => HasReactiveEnv (ReactiveT m) where
- askReactiveEnv = ReactiveT $ ReaderT pure
diff --git a/src/HtmlT/Internal.hs b/src/HtmlT/Internal.hs
index a260f7b..266ee83 100644
--- a/src/HtmlT/Internal.hs
+++ b/src/HtmlT/Internal.hs
@@ -2,39 +2,42 @@ module HtmlT.Internal where
import Control.Monad.Reader
import GHC.Generics
+import Data.Generics.Product
import HtmlT.Event
import HtmlT.Types
import HtmlT.DOM
-- | Auxiliary type to help implement 'simpleList'
-data ElemEnv a = ElemEnv
- { ee_html_env :: HtmlEnv
+data ElemEnv e a = ElemEnv
+ { ee_html_env :: e
, ee_modifier :: Modifier a
, ee_boundary :: ContentBoundary
} deriving Generic
-- | Insert given node to @html_current_element@ and run action with
-- inserted node as a new root
-appendHtmlT :: MonadIO m => DOMElement -> HtmlT m a -> HtmlT m a
-appendHtmlT newRootEl html = do
- result <- local (\env -> env
- { html_current_element = newRootEl
- , html_content_boundary = Nothing }) html
+appendHtml :: MonadHtml e m => DOMElement -> m a -> m a
+appendHtml newRootEl html = do
+ result <- local
+ ( setTyped (CurrentDOMElement newRootEl)
+ . setTyped (MaybeContentBoundary Nothing)
+ ) html
result <$ insertNode (nodeFromElement newRootEl)
-- | Insert new node to the end of current boundary
-insertNode :: MonadIO m => DOMNode -> HtmlT m ()
+insertNode :: MonadHtml e m => DOMNode -> m ()
insertNode n = do
- HtmlEnv{..} <- ask
- case html_content_boundary of
+ contBoundary <- asks (unMaybeContentBoundary . getTyped)
+ curElm <- asks (unCurrentDOMElement . getTyped)
+ case contBoundary of
Just ContentBoundary{..} -> liftIO $
- js_insertBefore html_current_element n boundary_end
- Nothing -> liftIO $ appendChild html_current_element n
+ js_insertBefore curElm n boundary_end
+ Nothing -> liftIO $ appendChild curElm n
-- | Insert two DOM Comment nodes intended to be used as a boundary for
-- dynamic content.
-insertBoundary :: MonadIO m => HtmlT m ContentBoundary
+insertBoundary :: MonadHtml e m => m ContentBoundary
insertBoundary = do
boundary_begin <- liftIO $ createComment "ContentBoundary {{"
boundary_end <- liftIO $ createComment "}}"
diff --git a/src/HtmlT/Main.hs b/src/HtmlT/Main.hs
index 430fbc2..b5c5698 100644
--- a/src/HtmlT/Main.hs
+++ b/src/HtmlT/Main.hs
@@ -36,8 +36,8 @@ attachOptions StartOpts{..} render = mdo
let
boundary = ContentBoundary begin end
htmlEnv = HtmlEnv
- { html_current_element = startopts_root_element
- , html_content_boundary = Just boundary
+ { html_current_element = CurrentDOMElement startopts_root_element
+ , html_content_boundary = MaybeContentBoundary $ Just boundary
, html_reactive_env = startopts_reactive_env
}
runApp = RunningApp htmlEnv boundary
diff --git a/src/HtmlT/Types.hs b/src/HtmlT/Types.hs
index d36417f..96697bc 100644
--- a/src/HtmlT/Types.hs
+++ b/src/HtmlT/Types.hs
@@ -4,32 +4,57 @@ import Control.Applicative
import Control.Monad.Catch
import Control.Monad.Reader
import Data.Coerce
+import Data.Generics.Product
import Data.Text
import GHC.Generics
import GHCJS.Marshal.Pure
import GHCJS.Prim
import GHCJS.Types
import HtmlT.Event
+import Control.Monad.Trans.Control
+import Control.Monad.Base
+
-- | HtmlT is nothing more than just a newtype over ReaderT HtmlEnv
newtype HtmlT m a = HtmlT {unHtmlT :: ReaderT HtmlEnv m a}
deriving newtype (Functor, Applicative, Monad, MonadIO, MonadReader HtmlEnv
- , MonadFix, MonadCatch, MonadThrow, MonadMask, MonadTrans)
+ , MonadFix, MonadCatch, MonadThrow, MonadMask, MonadTrans, MonadTransControl)
data HtmlEnv = HtmlEnv
- { html_current_element :: DOMElement
+ { html_current_element :: CurrentDOMElement
-- ^ A DOMElement that will be used as a parent to insert new
-- content, attributes, properties, listeners etc.
- , html_content_boundary :: Maybe ContentBoundary
+ , html_content_boundary :: MaybeContentBoundary
-- ^ Boundary defined by parent scope where new content should be
-- attached, when Nothing whole parent element is available
, html_reactive_env :: ReactiveEnv
-- ^ Needed to implement 'HasReactiveEnv'
} deriving Generic
+newtype MaybeContentBoundary = MaybeContentBoundary
+ { unMaybeContentBoundary :: Maybe ContentBoundary
+ }
+
+newtype CurrentDOMElement = CurrentDOMElement
+ { unCurrentDOMElement :: DOMElement
+ }
+
-- | Most applications will only need HtmlT IO, hence this shortcut
type Html = HtmlT IO
+type MonadHtml e m =
+ ( MonadIO m
+ , MonadReader e m
+ , HasType CurrentDOMElement e
+ , HasType MaybeContentBoundary e
+ )
+
+type MonadHtmlDyn e m =
+ ( MonadHtml e m
+ , MonadBaseControl IO m
+ , HasType ReactiveEnv e
+ )
+
-- | A newtype over JSVal which is an instance of Node
-- https://developer.mozilla.org/en-US/docs/Web/API/Node
newtype DOMNode = DOMNode {unDOMNode :: JSVal}
@@ -76,5 +101,5 @@ instance (Semigroup a, Applicative m) => Semigroup (HtmlT m a) where
instance (Monoid a, Applicative m) => Monoid (HtmlT m a) where
mempty = HtmlT $ ReaderT \_ -> pure mempty
-instance Monad m => HasReactiveEnv (HtmlT m) where
- askReactiveEnv = asks html_reactive_env
+deriving newtype instance MonadBase b m => MonadBase b (HtmlT m)
+deriving newtype instance (MonadBase b (HtmlT m), MonadBaseControl b m) => MonadBaseControl b (HtmlT m)