@@ -10,6 +10,7 @@
1010{-# LANGUAGE OverloadedStrings #-}
1111{-# LANGUAGE PolyKinds #-}
1212{-# LANGUAGE RankNTypes #-}
13+{-# LANGUAGE RecursiveDo #-}
1314{-# LANGUAGE ScopedTypeVariables #-}
1415{-# LANGUAGE TemplateHaskell #-}
1516{-# LANGUAGE TypeFamilies #-}
@@ -17,23 +18,32 @@
1718module Reflex.Dom.Builder.Class
1819 ( module Reflex.Dom.Builder.Class
1920 , module Reflex.Dom.Builder.Class.Events
20− , module Reflex.Dom.Deletable.Class
21+ , module Reflex.Deletable.Class
2122 ) where
2223
23−import Reflex
24+import Reflex.Class as Reflex
2425import Reflex.Dom.Builder.Class.Events
25−import Reflex.Dom.Deletable.Class
26+import Reflex.Deletable.Class
27+import Reflex.DynamicWriter
28+import Reflex.PerformEvent.Class
29+import Reflex.PostBuild.Class
2630
2731import qualified Control.Category
2832import Control.Lens hiding (element)
2933import Control.Monad.Reader
34+import Control.Monad.State.Strict
3035import Control.Monad.Trans.Control
3136import Data.Default
37+import Data.Foldable
3238import Data.Functor.Misc
39+import Data.List.NonEmpty (NonEmpty, nonEmpty)
3340import Data.Map (Map)
41+import qualified Data.Map as Map
3442import Data.Proxy
3543import Data.Semigroup
44+import Data.String
3645import Data.Text (Text)
46+import Data.Traversable
3747import Data.Type.Coercion
3848import GHC.Exts (Constraint)
3949
@@ -43,19 +53,97 @@ class Default (EventSpec d EventResult) => DomSpace d where
4353 type RawElement d :: *
4454 type RawInputElement d :: *
4555 type RawTextAreaElement d :: *
56+ type RawSelectElement d :: *
57+ addEventSpecFlags :: proxy d -> EventName en -> (Maybe (er en) -> EventFlags) -> EventSpec d er -> EventSpec d er
4658
47−-- | @'DomBuilder' t m@ indicates that @m@ is a 'Monad' capable of building dynamic DOM in the 'Reflex' timeline @t@
59+{-# INLINABLE liftElementConfig #-}
60+liftElementConfig :: (DomBuilderSpace (f m) ~ DomBuilderSpace m) => ElementConfig er t (f m) -> ElementConfig er t m
61+liftElementConfig cfg = cfg
62+ { _elementConfig_eventSpec = _elementConfig_eventSpec cfg
63+ }
64+
65+-- | @'DomBuilder' t m@ indicates that @m@ is a 'Monad' capable of building
66+-- dynamic DOM in the 'Reflex' timeline @t@
4867class (Monad m, Reflex t, Deletable t m, DomSpace (DomBuilderSpace m)) => DomBuilder t m | m -> t where
4968 type DomBuilderSpace m :: *
5069 textNode :: TextNodeConfig t -> m (TextNode (DomBuilderSpace m) t)
70+ default textNode :: ( MonadTrans f
71+ , m ~ f m'
72+ , DomBuilderSpace m' ~ DomBuilderSpace m
73+ , DomBuilder t m'
74+ )
75+ => TextNodeConfig t -> m (TextNode (DomBuilderSpace m) t)
76+ textNode = lift . textNode
77+ {-# INLINABLE textNode #-}
5178 element :: Text -> ElementConfig er t m -> m a -> m (Element er (DomBuilderSpace m) t, a)
79+ default element :: ( MonadTransControl f
80+ , StT f a ~ a
81+ , m ~ f m'
82+ , DomBuilderSpace m' ~ DomBuilderSpace m
83+ , DomBuilder t m'
84+ )
85+ => Text -> ElementConfig er t m -> m a -> m (Element er (DomBuilderSpace m) t, a)
86+ element t cfg child = liftWith $ \run -> element t (liftElementConfig cfg) $ run child
87+ {-# INLINABLE element #-}
5288 -- | Create a placeholder in the DOM, with the ability to insert new DOM before it
5389 -- The provided DOM will be executed after the current frame, so it will not be affected by any occurrences that are concurrent with the occurrence that created it
5490 placeholder :: PlaceholderConfig above t m -> m (Placeholder above t)
5591 inputElement :: InputElementConfig er t m -> m (InputElement er (DomBuilderSpace m) t)
92+ default inputElement :: ( MonadTransControl f
93+ , m ~ f m'
94+ , DomBuilderSpace m' ~ DomBuilderSpace m
95+ , DomBuilder t m'
96+ )
97+ => InputElementConfig er t m -> m (InputElement er (DomBuilderSpace m) t)
98+ inputElement cfg = lift $ inputElement $ cfg
99+ { _inputElementConfig_elementConfig = liftElementConfig $ _inputElementConfig_elementConfig cfg
100+ }
101+ {-# INLINABLE inputElement #-}
56102 textAreaElement :: TextAreaElementConfig er t m -> m (TextAreaElement er (DomBuilderSpace m) t)
103+ default textAreaElement :: ( MonadTransControl f
104+ , m ~ f m'
105+ , DomBuilderSpace m' ~ DomBuilderSpace m
106+ , DomBuilder t m'
107+ )
108+ => TextAreaElementConfig er t m -> m (TextAreaElement er (DomBuilderSpace m) t)
109+ textAreaElement cfg = lift $ textAreaElement $ cfg
110+ { _textAreaElementConfig_elementConfig = liftElementConfig $ _textAreaElementConfig_elementConfig cfg
111+ }
112+ {-# INLINABLE textAreaElement #-}
113+ selectElement :: SelectElementConfig er t m -> m a -> m (SelectElement er (DomBuilderSpace m) t, a)
114+ default selectElement :: ( MonadTransControl f
115+ , StT f a ~ a
116+ , m ~ f m'
117+ , DomBuilderSpace m' ~ DomBuilderSpace m
118+ , DomBuilder t m'
119+ )
120+ => SelectElementConfig er t m -> m a -> m (SelectElement er (DomBuilderSpace m) t, a)
121+ selectElement cfg child = do
122+ let cfg' = cfg
123+ { _selectElementConfig_elementConfig = liftElementConfig $ _selectElementConfig_elementConfig cfg
124+ }
125+ liftWith $ \run -> selectElement cfg' $ run child
126+ {-# INLINABLE selectElement #-}
57127 placeRawElement :: RawElement (DomBuilderSpace m) -> m ()
128+ default placeRawElement :: ( MonadTrans f
129+ , m ~ f m'
130+ , DomBuilderSpace m' ~ DomBuilderSpace m
131+ , DomBuilder t m'
132+ )
133+ => RawElement (DomBuilderSpace m) -> m ()
134+ placeRawElement = lift . placeRawElement
135+ {-# INLINABLE placeRawElement #-}
58136 wrapRawElement :: RawElement (DomBuilderSpace m) -> RawElementConfig er t m -> m (Element er (DomBuilderSpace m) t)
137+ default wrapRawElement :: ( MonadTrans f
138+ , m ~ f m'
139+ , DomBuilderSpace m' ~ DomBuilderSpace m
140+ , DomBuilder t m'
141+ )
142+ => RawElement (DomBuilderSpace m) -> RawElementConfig er t m -> m (Element er (DomBuilderSpace m) t)
143+ wrapRawElement e cfg = lift $ wrapRawElement e $ cfg
144+ { _rawElementConfig_eventSpec = _rawElementConfig_eventSpec cfg
145+ }
146+ {-# INLINABLE wrapRawElement #-}
59147
60148type Namespace = Text
61149
@@ -75,7 +163,11 @@ data TextNode d t = TextNode
75163 { _textNode_raw :: RawTextNode d
76164 }
77165
78−type AttributeName = (Maybe Namespace, Text)
166+data AttributeName = AttributeName !(Maybe Namespace) !Text deriving (Show, Read, Eq, Ord)
167+
168+-- | By default, AttributeNames are unnamespaced
169+instance IsString AttributeName where
170+ fromString = AttributeName Nothing . fromString
79171
80172data Propagation
81173 = Propagation_Continue
@@ -121,15 +213,6 @@ data ElementConfig er t m
121213 , _elementConfig_eventSpec :: EventSpec (DomBuilderSpace m) er
122214 }
123215
124−instance (Reflex t, er ~ EventResult, DomBuilder t m) => Default (ElementConfig er t m) where
125− {-# INLINABLE def #-}
126− def = ElementConfig
127− { _elementConfig_namespace = Nothing
128− , _elementConfig_initialAttributes = mempty
129− , _elementConfig_modifyAttributes = never
130− , _elementConfig_eventSpec = def
131− }
132−
133216data Element er d t
134217 = Element { _element_events :: EventSelector t (WrapArg er EventName) --TODO: EventSelector should have two arguments
135218 , _element_raw :: RawElement d
@@ -219,12 +302,36 @@ instance (Reflex t, DomSpace (DomBuilderSpace m)) => Default (RawElementConfig E
219302 , _rawElementConfig_eventSpec = def
220303 }
221304
222−makeLenses ''TextNodeConfig
223−makeLenses ''ElementConfig
224−makeLenses ''PlaceholderConfig
225−makeLenses ''InputElementConfig
226−makeLenses ''TextAreaElementConfig
227−makeLenses ''RawElementConfig
305+data SelectElementConfig er t m = SelectElementConfig
306+ { _selectElementConfig_initialValue :: Text
307+ , _selectElementConfig_setValue :: Event t Text
308+ , _selectElementConfig_elementConfig :: ElementConfig er t m
309+ }
310+
311+instance (Reflex t, er ~ EventResult, DomBuilder t m) => Default (SelectElementConfig er t m) where
312+ def = SelectElementConfig
313+ { _selectElementConfig_initialValue = ""
314+ , _selectElementConfig_setValue = never
315+ , _selectElementConfig_elementConfig = def
316+ }
317+
318+data SelectElement er d t = SelectElement
319+ { _selectElement_element :: Element er d t
320+ , _selectElement_value :: Dynamic t Text
321+ , _selectElement_change :: Event t Text -- ^ Fires when the value is changed by the user, but not when it is set by setValue
322+ , _selectElement_hasFocus :: Dynamic t Bool
323+ , _selectElement_raw :: RawSelectElement d
324+ }
325+
326+concat <$> mapM makeLenses
327+ [ ''TextNodeConfig
328+ , ''ElementConfig
329+ , ''PlaceholderConfig
330+ , ''InputElementConfig
331+ , ''TextAreaElementConfig
332+ , ''SelectElementConfig
333+ , ''RawElementConfig
334+ ]
228335
229336class CanDeleteSelf t a | a -> t where
230337 deleteSelf :: Lens' a (Event t ())
@@ -246,6 +353,14 @@ instance InitialAttributes (ElementConfig er t m) where
246353 {-# INLINABLE initialAttributes #-}
247354 initialAttributes = elementConfig_initialAttributes
248355
356+instance InitialAttributes (InputElementConfig er t m) where
357+ {-# INLINABLE initialAttributes #-}
358+ initialAttributes = inputElementConfig_elementConfig . elementConfig_initialAttributes
359+
360+instance InitialAttributes (TextAreaElementConfig er t m) where
361+ {-# INLINABLE initialAttributes #-}
362+ initialAttributes = textAreaElementConfig_elementConfig . elementConfig_initialAttributes
363+
249364class ModifyAttributes t a | a -> t where
250365 modifyAttributes :: Lens' a (Event t (Map AttributeName (Maybe Text)))
251366
@@ -253,6 +368,14 @@ instance ModifyAttributes t (ElementConfig er t m) where
253368 {-# INLINABLE modifyAttributes #-}
254369 modifyAttributes = elementConfig_modifyAttributes
255370
371+instance ModifyAttributes t (InputElementConfig er t m) where
372+ {-# INLINABLE modifyAttributes #-}
373+ modifyAttributes = inputElementConfig_elementConfig . elementConfig_modifyAttributes
374+
375+instance ModifyAttributes t (TextAreaElementConfig er t m) where
376+ {-# INLINABLE modifyAttributes #-}
377+ modifyAttributes = textAreaElementConfig_elementConfig . elementConfig_modifyAttributes
378+
256379instance ModifyAttributes t (RawElementConfig er t m) where
257380 {-# INLINABLE modifyAttributes #-}
258381 modifyAttributes = rawElementConfig_modifyAttributes
@@ -264,6 +387,74 @@ instance HasNamespace (ElementConfig er t m) where
264387 {-# INLINABLE namespace #-}
265388 namespace = elementConfig_namespace
266389
390+instance (Reflex t, er ~ EventResult, DomBuilder t m) => Default (ElementConfig er t m) where
391+ {-# INLINABLE def #-}
392+ def = ElementConfig
393+ { _elementConfig_namespace = Nothing
394+ , _elementConfig_initialAttributes = mempty
395+ , _elementConfig_modifyAttributes = never
396+ , _elementConfig_eventSpec = def
397+ }
398+
399+instance (DomBuilder t m, PerformEvent t m, MonadFix m, MonadHold t m) => DomBuilder t (PostBuildT t m) where
400+ type DomBuilderSpace (PostBuildT t m) = DomBuilderSpace m
401+ {-# INLINABLE placeholder #-}
402+ placeholder cfg = lift $ do
403+ rec childPostBuild <- deletable (_placeholder_deletedSelf p) $ performEvent $ return () <$ _placeholder_insertedAbove p
404+ p <- placeholder $ cfg
405+ { _placeholderConfig_insertAbove = ffor (_placeholderConfig_insertAbove cfg) $ \a -> runPostBuildT a =<< headE childPostBuild
406+ }
407+ return p
408+ wrapRawElement e cfg = liftWith $ \run -> wrapRawElement e $ fmap1 run cfg
409+
410+instance (DomBuilder t m, Monoid w, MonadHold t m, MonadFix m) => DomBuilder t (DynamicWriterT t w m) where
411+ type DomBuilderSpace (DynamicWriterT t w m) = DomBuilderSpace m
412+ textNode = liftTextNode
413+ element elementTag cfg (DynamicWriterT child) = DynamicWriterT $ do
414+ s <- get
415+ let cfg' = liftElementConfig cfg
416+ (el, (a, newS)) <- lift $ element elementTag cfg' $ runStateT child s
417+ put newS
418+ return (el, a)
419+ placeholder cfg = do
420+ let cfg' = cfg
421+ { _placeholderConfig_insertAbove = runDynamicWriterTInternal <$> _placeholderConfig_insertAbove cfg
422+ }
423+ let manageChildren :: Event t (NonEmpty (Replaceable t (Dynamic t w))) -- ^ Add nodes on the right; these are in reverse order
424+ -> Event t () -- ^ No more nodes will be added after this event fires
425+ -> m (Replaceable t (Dynamic t w))
426+ manageChildren newChildren additionsCeased = do
427+ rec nextId <- hold (0 :: Int) newNextId -- We assume this will never wrap around
428+ let numberedNewChildren :: Event t (Int, PatchMap (Map Int (Replaceable t (Dynamic t w))))
429+ numberedNewChildren = flip pushAlways newChildren $ \rcs -> do
430+ let cs = reverse $ toList rcs
431+ myFirstId <- sample nextId
432+ let (myNextId, numbered) = mapAccumL (\n v -> (succ n, (n, Just v))) myFirstId cs
433+ return (myNextId, PatchMap $ Map.fromList numbered)
434+ newNextId = fst <$> numberedNewChildren
435+ mconcatIncrementalReplaceableDynMap Map.empty (snd <$> numberedNewChildren) additionsCeased
436+ rec children <- lift $ manageChildren childOutputs $ cfg ^. deleteSelf
437+ p <- DynamicWriterT $ do
438+ modify (children:)
439+ lift $ placeholder cfg'
440+ let result = fst <$> _placeholder_insertedAbove p
441+ childOutputs = fmapMaybe (nonEmpty . snd) $ _placeholder_insertedAbove p
442+ return $ p
443+ { _placeholder_insertedAbove = result
444+ }
445+ inputElement cfg = lift $ inputElement $ cfg & inputElementConfig_elementConfig %~ liftElementConfig
446+ textAreaElement cfg = lift $ textAreaElement $ cfg & textAreaElementConfig_elementConfig %~ liftElementConfig
447+ selectElement cfg (DynamicWriterT child) = DynamicWriterT $ do
448+ s <- get
449+ let cfg' = cfg & selectElementConfig_elementConfig %~ liftElementConfig
450+ (el, (a, newS)) <- lift $ selectElement cfg' $ runStateT child s
451+ put newS
452+ return (el, a)
453+ placeRawElement = lift . placeRawElement
454+ wrapRawElement e cfg = lift $ wrapRawElement e $ cfg
455+ { _rawElementConfig_eventSpec = _rawElementConfig_eventSpec cfg
456+ }
457+
267458-- * Convenience functions
268459
269460--TODO: Move/replace
@@ -293,6 +484,10 @@ instance Functor1 (TextAreaElementConfig er t) where
293484 type Functor1Constraint (TextAreaElementConfig er t) a b = Functor1Constraint (ElementConfig er t) a b
294485 fmap1 f cfg = cfg & textAreaElementConfig_elementConfig %~ fmap1 f
295486
487+instance Functor1 (SelectElementConfig er t) where
488+ type Functor1Constraint (SelectElementConfig er t) a b = Functor1Constraint (ElementConfig er t) a b
489+ fmap1 f cfg = cfg & selectElementConfig_elementConfig %~ fmap1 f
490+
296491instance Functor1 (RawElementConfig er t) where
297492 type Functor1Constraint (RawElementConfig er t) a b = DomBuilderSpace a ~ DomBuilderSpace b
298493 {-# INLINABLE fmap1 #-}
@@ -321,14 +516,7 @@ instance Reflex t => HasDomEvent t (TextAreaElement EventResult d t) en where
321516
322517instance DomBuilder t m => DomBuilder t (ReaderT r m) where
323518 type DomBuilderSpace (ReaderT r m) = DomBuilderSpace m
324− {-# INLINABLE textNode #-}
325− textNode = liftTextNode
326− element = liftElement
327519 placeholder = liftPlaceholder
328− inputElement = liftInputElement
329− textAreaElement = liftTextAreaElement
330− placeRawElement = lift . placeRawElement
331− wrapRawElement = liftWrapRawElement
332520
333521type LiftDomBuilder t f m =
334522 ( Reflex t