module Web.Framework.Plzwrk.Domify
( plzwrk
, plzwrk'
, plzwrk'_
, plzwrkSSR
, plzwrkSSR'
, plzwrkSSR'_
)
where
import Control.Applicative
import Control.Monad
import Control.Monad.Reader
import Control.Monad.Trans
import Control.Monad.Trans.Maybe
import qualified Data.HashMap.Strict as HM
import Data.IORef
import Data.Maybe ( catMaybes )
import qualified Data.Set as S
import Web.Framework.Plzwrk.Base
import Web.Framework.Plzwrk.Browserful
data DomifiedAttributes jsval = MkDomifiedAttributes
{ _d_style :: HM.HashMap String String
, _d_class :: S.Set String
, _d_simple :: HM.HashMap String String
, _d_handlers :: HM.HashMap String jsval
}
data DomifiedNode jsval = DomifiedElement
{ _dom_tag :: String
, _dom_attr :: (DomifiedAttributes jsval)
, _dom_kids :: [DomifiedNode jsval]
, _dom_ptr :: jsval
}
| DomifiedTextNode String jsval
data OldStuff state jsval = OldStuff {
_oldState :: state,
_oldDom :: Maybe (DomifiedNode jsval)
}
freeAttrFunctions
:: DomifiedAttributes jsval -> ReaderT (Browserful jsval) IO ()
freeAttrFunctions (MkDomifiedAttributes _ _ _ __d_handlers) = do
__freeCallback <- asks _freeCallback
liftIO $ void (mapM __freeCallback (HM.elems __d_handlers))
freeFunctions :: DomifiedNode jsval -> ReaderT (Browserful jsval) IO ()
freeFunctions (DomifiedElement _ b c _) = do
freeAttrFunctions b
mapM_ freeFunctions c
freeFunctions _ = pure ()
nodesEq
:: String
-> String
-> DomifiedAttributes jsval
-> Attributes state jsval
-> Bool
nodesEq t0 t1 (MkDomifiedAttributes __d_style __d_class __d_simple _) (MkAttributes __style __class __simple _)
= (t0 == t1)
&& (__d_style == __style)
&& (__d_class == __class)
&& (__d_simple == __simple)
padr :: Int -> a -> [a] -> [a]
padr i v l = if (length l >= i) then l else padr i v (l ++ [v])
reconcile
:: Bool
-> IORef (OldStuff state jsval)
-> (state -> Node state jsval)
-> jsval
-> jsval
-> Maybe (DomifiedNode jsval)
-> Maybe (HydratedNode state jsval)
-> ReaderT
(Browserful jsval)
IO
(Maybe (DomifiedNode jsval))
reconcile touchDOM refToOldStuff domCreationF parentNode topLevelNode (Just (DomifiedElement currentTag currentAttributes currentChildren currentNode)) (Just maybeNewNode@(HydratedElement maybeNewTag maybeNewAttributes maybeNewChildren))
= if (nodesEq currentTag maybeNewTag currentAttributes maybeNewAttributes)
then
(do
let maxlen = max (length maybeNewChildren) (length currentChildren)
newChildren <- sequence $ getZipList
( (reconcile touchDOM
refToOldStuff
domCreationF
currentNode
topLevelNode
)
<$> (ZipList (padr maxlen Nothing (fmap Just currentChildren)))
<*> (ZipList (padr maxlen Nothing (fmap Just maybeNewChildren)))
)
currentAttributes <- hydratedAttrsToDomifiedAttrs refToOldStuff
domCreationF
parentNode
maybeNewAttributes
if touchDOM
then
(do
removeEventHandlers currentNode currentAttributes
setEventHandlers currentNode currentAttributes
)
else (pure ())
return $ Just
(DomifiedElement currentTag
currentAttributes
(catMaybes newChildren)
currentNode
)
)
else
(do
res <- domify touchDOM
refToOldStuff
domCreationF
parentNode
topLevelNode
(Just currentNode)
maybeNewNode
return $ Just res
)
reconcile touchDOM refToOldStuff domCreationF parentNode topLevelNode (Just currentDomifiedString@(DomifiedTextNode currentString currentNode)) (Just maybeNewNode@(HydratedTextNode maybeNewString))
= if (currentString == maybeNewString)
then pure (Just currentDomifiedString)
else
(do
res <- domify touchDOM
refToOldStuff
domCreationF
parentNode
topLevelNode
(Just currentNode)
maybeNewNode
return $ Just res
)
reconcile touchDOM refToOldStuff domCreationF parentNode topLevelNode (Just (DomifiedElement _ _ _ currentNode)) (Just maybeNewNode@(HydratedTextNode _))
= do
res <- domify touchDOM
refToOldStuff
domCreationF
parentNode
topLevelNode
(Just currentNode)
maybeNewNode
return $ Just res
reconcile touchDOM refToOldStuff domCreationF parentNode topLevelNode (Just (DomifiedTextNode _ currentNode)) (Just maybeNewNode@(HydratedElement _ _ _))
= do
res <- domify touchDOM
refToOldStuff
domCreationF
parentNode
topLevelNode
(Just currentNode)
maybeNewNode
return $ Just res
reconcile touchDOM refToOldStuff domCreationF parentNode topLevelNode Nothing (Just maybeNewNode)
= do
res <- domify touchDOM
refToOldStuff
domCreationF
parentNode
topLevelNode
Nothing
maybeNewNode
return $ Just res
reconcile touchDOM refToOldStuff domCreationF parentNode _ (Just (DomifiedElement _ _ _ currentNode)) Nothing
= if (touchDOM)
then
(do
_nodeRemoveChild <- asks nodeRemoveChild
liftIO $ _nodeRemoveChild parentNode currentNode
return Nothing
)
else (pure Nothing)
reconcile touchDOM refToOldStuff domCreationF parentNode _ (Just (DomifiedTextNode _ currentNode)) Nothing
= if (touchDOM)
then
(do
_nodeRemoveChild <- asks nodeRemoveChild
liftIO $ _nodeRemoveChild parentNode currentNode
return Nothing
)
else (pure Nothing)
reconcile _ _ _ _ _ _ _ = error "Inconsistent state"
reconcileAndAdd = reconcile True
cbMaker
:: IORef (OldStuff state jsval)
-> (state -> Node state jsval)
-> jsval
-> (jsval -> state -> IO state)
-> Browserful jsval
-> jsval
-> IO ()
cbMaker refToOldStuff domCreationF topLevelNode eventToState env event = do
oldStuff <- readIORef refToOldStuff
let oldDom = _oldDom oldStuff
let oldState = _oldState oldStuff
newState <- eventToState event oldState
let newHydratedDom = hydrate newState domCreationF
newDom <- runReaderT
(reconcileAndAdd refToOldStuff
domCreationF
topLevelNode
topLevelNode
oldDom
(Just newHydratedDom)
)
env
maybe (pure ()) (\x -> runReaderT (freeFunctions x) env) oldDom
writeIORef refToOldStuff (OldStuff newState newDom)
eventable
:: IORef (OldStuff state jsval)
-> (state -> Node state jsval)
-> jsval
-> (jsval -> state -> IO state)
-> ReaderT (Browserful jsval) IO jsval
eventable refToOldStuff domCreationF topLevelNode eventToState = do
__makeHaskellCallback <- asks _makeHaskellCallback
env <- ask
liftIO $ __makeHaskellCallback
(cbMaker refToOldStuff domCreationF topLevelNode eventToState env)
hydratedAttrsToDomifiedAttrs
:: IORef (OldStuff state jsval)
-> (state -> Node state jsval)
-> jsval
-> Attributes state jsval
-> ReaderT (Browserful jsval) IO (DomifiedAttributes jsval)
hydratedAttrsToDomifiedAttrs refToOldStuff domCreationF topLevelNode (MkAttributes __style __class __simple __handlers)
= do
handlers <- mapM
(\(k, v) -> do
func <- eventable refToOldStuff domCreationF topLevelNode v
return $ (k, func)
)
(HM.toList __handlers)
return
$ MkDomifiedAttributes __style __class __simple (HM.fromList handlers)
setAtts :: jsval -> DomifiedAttributes jsval -> ReaderT (Browserful jsval) IO ()
setAtts currentNode domifiedAttributes@(MkDomifiedAttributes __style __class __simple _)
= do
_elementSetAttribute <- asks elementSetAttribute
liftIO $ if (HM.null __style)
then (pure ())
else (_elementSetAttribute currentNode "style") . cssToStyle $ __style
liftIO $ if (S.null __class)
then (pure ())
else
((_elementSetAttribute currentNode "class") . unwords . S.toList)
$ __class
liftIO $ mapM_ (\x -> _elementSetAttribute currentNode (fst x) (snd x))
(HM.toList __simple)
setEventHandlers currentNode domifiedAttributes
handleOnlyEventListeners
:: (jsval -> String -> jsval -> IO ())
-> jsval
-> DomifiedAttributes jsval
-> IO ()
handleOnlyEventListeners eventListenerHandlerF currentNode domifiedAttributes =
void $ mapM (\(k, v) -> eventListenerHandlerF currentNode k v)
(HM.toList . _d_handlers $ domifiedAttributes)
setEventHandlers
:: jsval -> DomifiedAttributes jsval -> ReaderT (Browserful jsval) IO ()
setEventHandlers currentNode domifiedAttributes = do
_eventTargetAddEventListener <- asks eventTargetAddEventListener
liftIO $ handleOnlyEventListeners _eventTargetAddEventListener
currentNode
domifiedAttributes
removeEventHandlers
:: jsval -> DomifiedAttributes jsval -> ReaderT (Browserful jsval) IO ()
removeEventHandlers currentNode domifiedAttributes = do
_eventTargetRemoveEventListener <- asks eventTargetRemoveEventListener
liftIO $ handleOnlyEventListeners _eventTargetRemoveEventListener
currentNode
domifiedAttributes
domify
:: Bool
-> IORef (OldStuff state jsval)
-> (state -> Node state jsval)
-> jsval
-> jsval
-> Maybe jsval
-> HydratedNode state jsval
-> ReaderT (Browserful jsval) IO (DomifiedNode jsval)
domify touchDOM refToOldStuff domCreationF parentNode topLevelNode replacing (HydratedElement tag attrs children)
= do
_documentCreateElement <- asks documentCreateElement
_nodeAppendChild <- asks nodeAppendChild
_nodeInsertBefore <- asks nodeInsertBefore
_nodeRemoveChild <- asks nodeRemoveChild
newNode <- liftIO $ _documentCreateElement tag
newAttributes <- hydratedAttrsToDomifiedAttrs refToOldStuff
domCreationF
topLevelNode
attrs
if touchDOM then (setAtts newNode newAttributes) else (pure ())
newChildren <- mapM
(domify touchDOM refToOldStuff domCreationF newNode topLevelNode Nothing)
children
if touchDOM
then
(do
maybe
(liftIO $ _nodeAppendChild parentNode newNode)
(\x -> do
liftIO $ _nodeInsertBefore parentNode newNode x
liftIO $ _nodeRemoveChild parentNode x
)
replacing
)
else (pure ())
liftIO $ return (DomifiedElement tag newAttributes newChildren newNode)
domify touchDOM _ _ parentNode topLevelNode replacing (HydratedTextNode text) =
do
_documentCreateElement <- asks documentCreateElement
_nodeAppendChild <- asks nodeAppendChild
_nodeInsertBefore <- asks nodeInsertBefore
_nodeRemoveChild <- asks nodeRemoveChild
_documentCreateTextNode <- asks documentCreateTextNode
newTextNode <- liftIO $ _documentCreateTextNode text
if touchDOM
then
(do
maybe
(liftIO $ _nodeAppendChild parentNode newTextNode)
(\x -> do
liftIO $ _nodeInsertBefore parentNode newTextNode x
liftIO $ _nodeRemoveChild parentNode x
)
replacing
)
else (pure ())
liftIO $ return (DomifiedTextNode text newTextNode)
getChildren :: DomifiedNode jsval -> [DomifiedNode jsval]
getChildren (DomifiedElement _ _ x _) = x
getChildren _ = []
setEventHandlers_
:: jsval -> DomifiedNode jsval -> ReaderT (Browserful jsval) IO ()
setEventHandlers_ v (DomifiedElement _ a _ _) = setEventHandlers v a
setEventHandlers_ _ _ = liftIO $ pure ()
transformFromCurrentDom
:: jsval
-> [DomifiedNode jsval]
-> ReaderT (Browserful jsval) IO [DomifiedNode jsval]
transformFromCurrentDom parentNode children = do
_nodeChildNodes <- asks nodeChildNodes
kids <- liftIO $ _nodeChildNodes parentNode
newChildren <- sequence $ getZipList
( transformFromCurrentDom
<$> (ZipList kids)
<*> (ZipList $ fmap getChildren children)
)
sequence
$ getZipList (setEventHandlers_ <$> (ZipList kids) <*> (ZipList children))
return $ getZipList
( (\cur chldrn ptr -> cur { _dom_kids = chldrn, _dom_ptr = ptr })
<$> (ZipList children)
<*> (ZipList newChildren)
<*> (ZipList kids)
)
addHandlers
:: jsval
-> DomifiedNode jsval
-> ReaderT (Browserful jsval) IO (DomifiedNode jsval)
addHandlers parentNode curDom = do
transformed <- transformFromCurrentDom parentNode [curDom]
return $ (transformed !! 0)
__plzwrk
:: Bool
-> (state -> Node state jsval)
-> state
-> jsval
-> Browserful jsval
-> IO (Maybe (DomifiedNode jsval))
__plzwrk cleanDOM domF state parentNode env = do
refToOldStuff <- newIORef (OldStuff state Nothing)
newDom <- runReaderT
(reconcile cleanDOM
refToOldStuff
domF
parentNode
parentNode
Nothing
(Just $ hydrate state domF)
)
env
writeIORef refToOldStuff (OldStuff state newDom)
if (not cleanDOM)
then
(maybe
(pure Nothing)
(\y -> do
withHandlers <- runReaderT (addHandlers parentNode y) env
writeIORef refToOldStuff (OldStuff state (Just y))
return $ Just withHandlers
)
newDom
)
else pure newDom
_plzwrk
:: Bool
-> (state -> Node state jsval)
-> state
-> Browserful jsval
-> String
-> IO (Maybe (DomifiedNode jsval))
_plzwrk cleanDOM domF state env nodeId = do
parentNode <- (documentGetElementById env) nodeId
maybe (error ("Node with id not in DOM: " <> show nodeId))
(\x -> __plzwrk cleanDOM domF state x env)
parentNode
plzwrk
:: (state -> Node state jsval)
-> state
-> Browserful jsval
-> String
-> IO ()
plzwrk domF state env nodeId = void $ _plzwrk True domF state env nodeId
plzwrkSSR
:: (state -> Node state jsval)
-> state
-> Browserful jsval
-> String
-> IO ()
plzwrkSSR domF state env nodeId = void $ _plzwrk False domF state env nodeId
_plzwrk'
:: Bool
-> (state -> Node state jsval)
-> state
-> Browserful jsval
-> IO (Maybe (DomifiedNode jsval))
_plzwrk' cleanDOM domF state env = do
parentNode <- (documentBody env)
__plzwrk cleanDOM domF state parentNode env
plzwrk' :: (state -> Node state jsval) -> state -> Browserful jsval -> IO ()
plzwrk' domF state env = void $ _plzwrk' True domF state env
plzwrkSSR' :: (state -> Node state jsval) -> state -> Browserful jsval -> IO ()
plzwrkSSR' domF state env = void $ _plzwrk' False domF state env
plzwrk'_ :: (() -> Node () jsval) -> Browserful jsval -> IO ()
plzwrk'_ domF env = plzwrk' domF () env
plzwrkSSR'_ :: (() -> Node () jsval) -> Browserful jsval -> IO ()
plzwrkSSR'_ domF env = plzwrkSSR' domF () env