{-# LANGUAGE FlexibleContexts, FlexibleInstances, GeneralizedNewtypeDeriving, MultiParamTypeClasses, TypeFamilies, TypeSynonymInstances, QuasiQuotes, OverloadedStrings #-} -- | This experimental module provides a monad transformer 'JMacroT' -- and corresponding 'XMLGenerator' instance which can be used to -- directly generate javascript which builds an XML/HTML DOM. -- -- This is similar to the 'ToJExpr XMLToDOM' instance except that -- there is no intermediate XML type. The 'XMLGenerator' instance -- directly generates the javascript needed to build the DOM. -- -- This is intellectually fun. But it is not clear how it is valuable. -- That is why this module is marked as experimental. module HSP.JMacroT ( JMacroT(..) , evalJMacroT , mapJMacroT , JMacroM , evalJMacroM ) where import Control.Applicative (Applicative, Alternative) import Control.Monad (MonadPlus) import Control.Monad.Cont (MonadCont) import Control.Monad.Identity (Identity(..)) import Control.Monad.Error (MonadError) import Control.Monad.Reader (MonadReader) import Control.Monad.State (MonadState) import Control.Monad.Writer (MonadWriter) import Control.Monad.RWS (MonadRWS) import Control.Monad.Trans (MonadIO, MonadTrans(..)) import qualified Data.Text as Strict import qualified Data.Text.Lazy as Lazy import HSP.XMLGenerator (Attr(..), XMLGen(..), XMLGenT(..), XMLGenerator, AppendChild(..), EmbedAsAttr(..), EmbedAsChild(..), Name(..), SetAttr(..), unXMLGenT) import Language.Javascript.JMacro (ToJExpr(..), JExpr(..), JStat(..), JVal(JVar), Ident(StrI), ToStat(..), jmacroE, jLam, jVarTy) -- | isomorphic to IdentityT, but used for generating javascript that generates XML/HTML newtype JMacroT m a = JMacroT { unJMacroT :: m a } deriving ( Functor, Applicative, Alternative, Monad, MonadIO, MonadPlus, MonadState s, MonadReader r, MonadWriter w, MonadRWS r w s, MonadCont, MonadError e) instance MonadTrans JMacroT where lift = JMacroT -- | map a function over the inner monad mapJMacroT :: (m a -> n b) -> JMacroT m a -> JMacroT n b mapJMacroT f (JMacroT ma) = JMacroT (f ma) -- | unwrap the 'XMLGenT' and 'JMacroT' constructors evalJMacroT :: XMLGenT (JMacroT m) JExpr -> m JExpr evalJMacroT = unJMacroT . unXMLGenT -- | an alias for 'JMacroT Identity' type JMacroM = JMacroT Identity -- | evaluate 'JMacroM' evalJMacroM :: XMLGenT JMacroM a -> a evalJMacroM = runIdentity . unJMacroT . unXMLGenT instance (ToJExpr a) => ToJExpr (XMLGenT JMacroM a) where toJExpr = toJExpr . evalJMacroM instance (Functor m, Monad m) => XMLGen (JMacroT m) where type XMLType (JMacroT m) = JExpr type StringType (JMacroT m) = Lazy.Text newtype ChildType (JMacroT m) = JMChild { unJMChild :: JExpr } newtype AttributeType (JMacroT m) = JMAttr { unJMAttr :: JExpr } genElement = element xmlToChild = JMChild pcdataToChild str = JMChild $ [jmacroE| document.createTextNode(`(Lazy.unpack str)`) |] -- | generate an XML Element element :: (Functor m, Monad m, EmbedAsAttr (JMacroT m) attr, EmbedAsChild (JMacroT m) child) => Name Lazy.Text -- ^ element name -> [attr] -- ^ attributes -> [child] -- ^ children -> XMLGenT (JMacroT m) JExpr element (ns, nm) attrs childer = do ats <- fmap (map unJMAttr . concat) $ mapM asAttr attrs children <- fmap (map unJMChild . concat) $ mapM asChild childer return [jmacroE| (function { var node = `(createElement (fmap Lazy.unpack ns) (Lazy.unpack nm))`; `(map (setAttributeNode node) ats)`; `(map (appendChild node) children)`; return node; })() |] -- | javascript to create an element createElement Nothing n = [jmacroE| document.createElement(`(n)`) |] createElement (Just ns) n = [jmacroE| document.createElementNS(`(ns)`, `(n)`) |] -- | javascript to append a child to an element appendChild :: JExpr -> JExpr -> JExpr appendChild node c = [jmacroE| `(node)`.appendChild(`(c)`) |] -- | javascript to set the attribute node of an element setAttributeNode :: JExpr -> JExpr -> JExpr setAttributeNode node attr = [jmacroE| `(node)`.setAttributeNode(`(attr)`) |] instance (Functor m, Monad m) => EmbedAsAttr (JMacroT m) (Attr Lazy.Text Lazy.Text) where asAttr (n := v) = return [JMAttr [jmacroE| (function (){ var attrNode = document.createAttribute(`(Lazy.unpack n)`) ; attrNode.nodeValue = `(Lazy.unpack v)` ; return attrNode; })() |]] instance (Functor m, Monad m, StringType (JMacroT m) ~ Lazy.Text) => EmbedAsChild (JMacroT m) Char where asChild c = return [pcdataToChild $ Lazy.singleton c] instance (Functor m, Monad m, StringType (JMacroT m) ~ Lazy.Text) => EmbedAsChild (JMacroT m) String where asChild str = return [pcdataToChild $ Lazy.pack str] instance (Functor m, Monad m) => EmbedAsChild (JMacroT m) Strict.Text where asChild txt = return [JMChild $ [jmacroE| document.createTextNode(`(Strict.unpack txt)`) |]] instance (Functor m, Monad m) => EmbedAsChild (JMacroT m) Lazy.Text where asChild txt = return [JMChild $ [jmacroE| document.createTextNode(`(Lazy.unpack txt)`) |]] instance (Functor m, Monad m) => EmbedAsChild (JMacroT m) () where asChild () = return [] instance (Functor m, Monad m) => EmbedAsAttr (JMacroT m) (Attr Lazy.Text Bool) where asAttr (n := True) = asAttr (n := ("true" :: Lazy.Text)) asAttr (n := False) = asAttr (n := ("false" :: Lazy.Text)) instance (Functor m, Monad m) => EmbedAsAttr (JMacroT m) (Attr Lazy.Text Int) where asAttr (n := v) = asAttr (n := (Lazy.pack $ show v)) instance (Functor m, Monad m) => AppendChild (JMacroT m) JExpr where appChild parent child = do c <- child return $ [jmacroE| appendChild parent (unJMChild c) |] appAll parent children = do chs <- children return $ [jmacroE| `(map (appendChild parent) (map unJMChild chs))` |] instance (Functor m, Monad m) => SetAttr (JMacroT m) JExpr where setAttr elem attrNode = do a <- attrNode return $ [jmacroE| `(setAttributeNode elem (unJMAttr a))` |] setAll elem attrNodes = do as <- attrNodes return $ [jmacroE| `(map (setAttributeNode elem) (map unJMAttr as))` |] instance (Functor m, Monad m, StringType (JMacroT m) ~ Lazy.Text) => XMLGenerator (JMacroT m)