hsx-jmacro 7.2.0 → 7.3.0
raw patch · 5 files changed
+347/−332 lines, 5 filesdep +hspdep −hsxdep ~happstack-hsp
Dependencies added: hsp
Dependencies removed: hsx
Dependency ranges changed: happstack-hsp
Files
- HSP/JMacro.hs +192/−0
- HSP/JMacroT.hs +148/−0
- HSX/JMacro.hs +0/−178
- HSX/JMacroT.hs +0/−147
- hsx-jmacro.cabal +7/−7
+ HSP/JMacro.hs view
@@ -0,0 +1,192 @@+{-# LANGUAGE FlexibleContexts, FlexibleInstances, MultiParamTypeClasses, UndecidableInstances, QuasiQuotes, TypeSynonymInstances, OverloadedStrings, TypeFamilies #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+-- | This module provides support for:+--+-- 1. embedding Javascript generated by JMacro into HSX.+--+-- 2. turning XML generated by HSX into a DOM node in Javascript+--+-- It provides the following instances:+--+-- > instance (XMLGenerator m, IntegerSupply m) => EmbedAsChild m JStat+-- > instance (IntegerSupply m, IsName n, EmbedAsAttr m (Attr Name String)) => EmbedAsAttr m (Attr n JStat)+-- > instance ToJExpr XML+-- > instance ToJExpr DOMNode+-- > instance ToJExpr XMLToInnerHTML+-- > instance ToJExpr XMLToDOM+--+-- In order to ensure that each embedded 'JStat' block has unique+-- variable names, the monad must supply a source of unique+-- names. This is done by adding an instance of 'IntegerSupply' for+-- the monad being used with 'XMLGenerator'.+--+-- For example, we can use 'StateT' to provide an 'IntegerSupply' instance for 'ServerPartT':+--+-- > instance IntegerSupply (ServerPartT (StateT Integer IO)) where+-- > nextInteger = nextInteger'+--+-- Alternatively, we can exploit the IO monad to provide an 'IntegerSupply' instance for 'ServerPartT':+--+-- > instance IntegerSupply (ServerPartT IO) where+-- > nextInteger = fmap (fromIntegral . (`mod` 1024) . hashUnique) (liftIO newUnique)+--+-- The @ToJExpr XML@ instance allows you to splice in XML lifted out of an+-- arbitrary monad to generate DOM nodes with JMacro antiquotation:+--+-- > js = do html <- unXMLGenT <p>I'm in a Monad!</p>+-- > return [jmacro| document.getElementById("messages").appendChild(`(html)`); |]+--+-- The @ToJExpr DOMNode@ instance allows you to run HSP in the Identity+-- monad to render JMacro in pure code:+--+-- > html :: DOMNode+-- > html = <p>I'm using <em>JavaScript</em>!</p>+-- > js = [jmacro| var language = `(html)`.getElementsByTagName("em")[0].textContent; |]+--+-- You can see here that you get an actual DOM tree in JavaScript. This is+-- also compatible with libraries such as jQuery and YUI which are able to+-- wrap DOM nodes in their own type, for example with jQuery:+--+-- > js = [jmacro| var languages = $(`(html)`).find("em").text(); |]+--+-- Or with YUI:+--+-- > js = [jmacro| var languages = Y.one(`(html)`).one("em").get("text"); |]+--+-- There are two ways to turn HTML into a a DOM node in the+-- browser. One way is to render the HTML to a string, and pass the+-- string to @element.innerHTML@. The other way is to us the use the+-- DOM functions like @createElement@, @setAttribute@, to+-- programatically create the DOM on the client.+--+-- In webkit-based browsers like Chrome and Safari, the DOM method+-- appears to be slightly faster. In other browsers, the @innerHTML@+-- method appears to be faster. The @innerHTML@ method will almost+-- always required fewer bytes to be transmitted. Additionally, if+-- your XML/HTML contains pre-escaped content, you are required to use+-- @innerHTML@ anyway.+--+-- So, by default the 'ToJExpr' 'XML' instance uses the @innerHTML@+-- method. Though, that could change in the future. If you care about+-- using one method over the other you can use the @newtype@ wrappers+-- 'XMLToInnerHTML' or 'XMLToDOM' to select which method to use.++module HSP.JMacro where++import Control.Monad.Identity+import Control.Monad.Trans (lift)+import Control.Monad.State (MonadState(get,put))+import Data.Text.Lazy (Text, unpack)+import HSP.XML+import HSP.HTML4 (renderAsHTML)+import HSP.XMLGenerator+import HSP.Monad (HSPT(..))++import Language.Javascript.JMacro (JStat(..), JExpr(..), JVal(..), Ident(..), ToJExpr(..), toStat, jmacroE, jLam, jVarTy, jsToDoc, jsSaturate, renderPrefixJs)+import Text.PrettyPrint.Leijen.Text (Doc, displayT, renderOneLine)++-- | This class provides a monotonically increasing supply of non-duplicate 'Integer' values+class IntegerSupply m where+ nextInteger :: m Integer++-- | This help function allows you to easily create an 'IntegerSupply'+-- instance for monads that have a 'MonadState' 'Integer' instance.+--+-- For example:+--+-- > instance IntegerSupply (ServerPartT (StateT Integer IO)) where+-- > nextInteger = nextInteger'+nextInteger' :: (MonadState Integer m) => m Integer+nextInteger' =+ do i <- get+ put (succ i)+ return i++instance (XMLGenerator m, IntegerSupply m, EmbedAsChild m Text, StringType m ~ Text) => EmbedAsChild m JStat where+ asChild jstat =+ do i <- lift nextInteger+ asChild $ genElement (Nothing, fromStringLit "script")+ [asAttr ((fromStringLit "type" := fromStringLit "text/javascript") :: Attr Text Text)]+ [asChild (displayT $ renderOneLine $ renderPrefixJs (show i) jstat)]++instance (IntegerSupply m, EmbedAsAttr m (Attr n Text)) => EmbedAsAttr m (Attr n JStat) where+ asAttr (n := jstat) =+ do i <- lift nextInteger+ asAttr $ (n := (displayT $ renderOneLine $ renderPrefixJs (show i) jstat))++-- | Provided for convenience since @Ident@ is exported by both+-- @HSP.Identity@ and @JMacro@. Using this you can avoid the need for an+-- extra and qualified import.+type DOMNode = HSPT XML Identity XML++instance ToJExpr DOMNode where+ toJExpr = toJExpr . runIdentity . unHSPT++-- | newtype which can be used with 'toJExpr' to specify that the XML+-- should be converted to a DOM in javascript by using 'innerHTML'+newtype XMLToInnerHTML = XMLToInnerHTML XML++instance ToJExpr XMLToInnerHTML where+ toJExpr (XMLToInnerHTML xml) =+ [jmacroE| (function { var node = document.createElement('div')+ ; node.innerHTML = `(unpack $ renderAsHTML xml)`+ ; return node.childNodes[0]+ })() |]++-- | newtype which can be used with 'toJExpr' to specify that the XML+-- should be converted to a DOM in javascript by using+-- @createElement@, @appendChild@, and other DOM functions.+--+-- WARNING: @CDATA FALSE@ values are assumed to be pre-escaped HTML and will be converted to a DOM node by using @innerHTML@. Additionally, if the call to @innerHTML@ returns more than one node, only the first node is used.+newtype XMLToDOM = XMLToDOM XML++instance ToJExpr XMLToDOM where+ toJExpr (XMLToDOM (Element (dm', n') attrs children)) =+ let dm = fmap unpack dm'+ n = unpack n'+ in+ [jmacroE| (function { var node = `(createElement (dm) (n))`+ ; `(map (setAttribute node) attrs)`+ ; `(map (appendChild node . XMLToDOM) children)`+ ; return node+ })()+ |]+ where+ createElement Nothing n = [jmacroE| document.createElement(`(n)`) |]+ createElement (Just ns) n = [jmacroE| document.createElementNS(`(ns)`, `(n)`) |]+ appendChild node c' =+ [jmacroE| (function () {+ var c = `(c')`;+ if (Object.prototype.toString.call(c) === '[object Array]') {+ for (var i = 0; i < c.length; i++)+ `(node)`.appendChild(c[i]);+ } else {+ `(node)`.appendChild(`(c)`);+ }+ })()+ |]+ setAttribute node (MkAttr ((Nothing, nm'), (Value True val'))) =+ let nm = unpack nm'+ val = unpack val'+ in+ [jmacroE| `(node)`.setAttribute(`(nm)`, `(val)`) |]+ setAttribute node (MkAttr ((Just ns', nm'), (Value True val'))) =+ let ns = unpack ns'+ nm = unpack nm'+ val = unpack val'+ in+ [jmacroE| `(node)`.setAttributeNS(`(ns)`, `(nm)`, `(val)`) |]++ toJExpr (XMLToDOM (CDATA True txt')) =+ let txt = unpack txt' in+ [jmacroE| document.createTextNode(`(txt)`) |]++ toJExpr (XMLToDOM (CDATA False txt')) =+ let txt = unpack txt' in+ [jmacroE| (function { var node = document.createElement('div')+ ; node.innerHTML = `(txt)`+ ; return node+ })() |]++instance ToJExpr XML where+ toJExpr = toJExpr . XMLToInnerHTML
+ HSP/JMacroT.hs view
@@ -0,0 +1,148 @@+{-# 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)
− HSX/JMacro.hs
@@ -1,178 +0,0 @@-{-# LANGUAGE FlexibleContexts, FlexibleInstances, MultiParamTypeClasses, UndecidableInstances, QuasiQuotes, TypeSynonymInstances #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--- | This module provides support for:------ 1. embedding Javascript generated by JMacro into HSX.------ 2. turning XML generated by HSX into a DOM node in Javascript------ It provides the following instances:------ > instance (XMLGenerator m, IntegerSupply m) => EmbedAsChild m JStat--- > instance (IntegerSupply m, IsName n, EmbedAsAttr m (Attr Name String)) => EmbedAsAttr m (Attr n JStat)--- > instance ToJExpr XML--- > instance ToJExpr DOMNode--- > instance ToJExpr XMLToInnerHTML--- > instance ToJExpr XMLToDOM------ In order to ensure that each embedded 'JStat' block has unique--- variable names, the monad must supply a source of unique--- names. This is done by adding an instance of 'IntegerSupply' for--- the monad being used with 'XMLGenerator'.------ For example, we can use 'StateT' to provide an 'IntegerSupply' instance for 'ServerPartT':------ > instance IntegerSupply (ServerPartT (StateT Integer IO)) where--- > nextInteger = nextInteger'------ Alternatively, we can exploit the IO monad to provide an 'IntegerSupply' instance for 'ServerPartT':------ > instance IntegerSupply (ServerPartT IO) where--- > nextInteger = fmap (fromIntegral . (`mod` 1024) . hashUnique) (liftIO newUnique)------ The @ToJExpr XML@ instance allows you to splice in XML lifted out of an--- arbitrary monad to generate DOM nodes with JMacro antiquotation:------ > js = do html <- unXMLGenT <p>I'm in a Monad!</p>--- > return [jmacro| document.getElementById("messages").appendChild(`(html)`); |]------ The @ToJExpr DOMNode@ instance allows you to run HSP in the Identity--- monad to render JMacro in pure code:------ > html :: DOMNode--- > html = <p>I'm using <em>JavaScript</em>!</p>--- > js = [jmacro| var language = `(html)`.getElementsByTagName("em")[0].textContent; |]------ You can see here that you get an actual DOM tree in JavaScript. This is--- also compatible with libraries such as jQuery and YUI which are able to--- wrap DOM nodes in their own type, for example with jQuery:------ > js = [jmacro| var languages = $(`(html)`).find("em").text(); |]------ Or with YUI:------ > js = [jmacro| var languages = Y.one(`(html)`).one("em").get("text"); |]------ There are two ways to turn HTML into a a DOM node in the--- browser. One way is to render the HTML to a string, and pass the--- string to @element.innerHTML@. The other way is to us the use the--- DOM functions like @createElement@, @setAttribute@, to--- programatically create the DOM on the client.------ In webkit-based browsers like Chrome and Safari, the DOM method--- appears to be slightly faster. In other browsers, the @innerHTML@--- method appears to be faster. The @innerHTML@ method will almost--- always required fewer bytes to be transmitted. Additionally, if--- your XML/HTML contains pre-escaped content, you are required to use--- @innerHTML@ anyway.------ So, by default the 'ToJExpr' 'XML' instance uses the @innerHTML@--- method. Though, that could change in the future. If you care about--- using one method over the other you can use the @newtype@ wrappers--- 'XMLToInnerHTML' or 'XMLToDOM' to select which method to use.--module HSX.JMacro where--import Control.Monad.Trans (lift)-import Control.Monad.State (MonadState(get,put))-import Data.Text.Lazy (Text)-import qualified Happstack.Server.HSP.HTML as HTML-import Happstack.Server.HSP.HTML (XML(..), Attribute(..), AttrValue(..))-import HSX.XMLGenerator (XMLGenerator(..), XMLGen(..), EmbedAsChild(..), EmbedAsAttr(..), IsName(..), Attr(..), Name)-import qualified HSP.Identity as HSP-import Language.Javascript.JMacro (JStat(..), JExpr(..), JVal(..), Ident(..), ToJExpr(..), toStat, jmacroE, jLam, jVarTy, jsToDoc, jsSaturate, renderPrefixJs)-import Text.PrettyPrint.Leijen.Text (Doc, displayT, renderOneLine)---- | This class provides a monotonically increasing supply of non-duplicate 'Integer' values-class IntegerSupply m where- nextInteger :: m Integer---- | This help function allows you to easily create an 'IntegerSupply'--- instance for monads that have a 'MonadState' 'Integer' instance.------ For example:------ > instance IntegerSupply (ServerPartT (StateT Integer IO)) where--- > nextInteger = nextInteger'-nextInteger' :: (MonadState Integer m) => m Integer-nextInteger' =- do i <- get- put (succ i)- return i--instance (XMLGenerator m, IntegerSupply m, EmbedAsChild m Text) => EmbedAsChild m JStat where- asChild jstat =- do i <- lift nextInteger- asChild $ genElement (Nothing, "script")- [asAttr ("type" := "text/javascript")]- [asChild (displayT $ renderOneLine $ renderPrefixJs (show i) jstat)]--instance (IntegerSupply m, IsName n, EmbedAsAttr m (Attr Name Text)) => EmbedAsAttr m (Attr n JStat) where- asAttr (n := jstat) =- do i <- lift nextInteger- asAttr $ (toName n := (displayT $ renderOneLine $ renderPrefixJs (show i) jstat))---- | Provided for convenience since @Ident@ is exported by both--- @HSP.Identity@ and @JMacro@. Using this you can avoid the need for an--- extra and qualified import.-type DOMNode = HSP.Ident HTML.XML--instance ToJExpr DOMNode where- toJExpr = toJExpr . HSP.evalIdentity---- | newtype which can be used with 'toJExpr' to specify that the XML--- should be converted to a DOM in javascript by using 'innerHTML'-newtype XMLToInnerHTML = XMLToInnerHTML HTML.XML--instance ToJExpr XMLToInnerHTML where- toJExpr (XMLToInnerHTML xml) =- [jmacroE| (function { var node = document.createElement('div')- ; node.innerHTML = `(HTML.renderAsHTML xml)`- ; return node.childNodes[0]- })() |]---- | newtype which can be used with 'toJExpr' to specify that the XML--- should be converted to a DOM in javascript by using--- @createElement@, @appendChild@, and other DOM functions.------ WARNING: @CDATA FALSE@ values are assumed to be pre-escaped HTML and will be converted to a DOM node by using @innerHTML@. Additionally, if the call to @innerHTML@ returns more than one node, only the first node is used.-newtype XMLToDOM = XMLToDOM HTML.XML--instance ToJExpr XMLToDOM where- toJExpr (XMLToDOM (Element (dm, n) attrs children)) =- [jmacroE| (function { var node = `(createElement dm n)`- ; `(map (setAttribute node) attrs)`- ; `(map (appendChild node . XMLToDOM) children)`- ; return node- })()- |]- where- createElement Nothing n = [jmacroE| document.createElement(`(n)`) |]- createElement (Just ns) n = [jmacroE| document.createElementNS(`(ns)`, `(n)`) |]- appendChild node c' =- [jmacroE| (function () {- var c = `(c')`;- if (Object.prototype.toString.call(c) === '[object Array]') {- for (var i = 0; i < c.length; i++)- `(node)`.appendChild(c[i]);- } else {- `(node)`.appendChild(`(c)`);- }- })()- |]- setAttribute node (MkAttr ((Nothing, nm), (Value True val))) =- [jmacroE| `(node)`.setAttribute(`(nm)`, `(val)`) |]- setAttribute node (MkAttr ((Just ns, nm), (Value True val))) =- [jmacroE| `(node)`.setAttributeNS(`(ns)`, `(nm)`, `(val)`) |]-- toJExpr (XMLToDOM (CDATA True txt)) =- [jmacroE| document.createTextNode(`(txt)`) |]-- toJExpr (XMLToDOM (CDATA False txt)) =- [jmacroE| (function { var node = document.createElement('div')- ; node.innerHTML = `(txt)`- ; return node- })() |]--instance ToJExpr HTML.XML where- toJExpr = toJExpr . XMLToInnerHTML
− HSX/JMacroT.hs
@@ -1,147 +0,0 @@-{-# LANGUAGE FlexibleContexts, FlexibleInstances, GeneralizedNewtypeDeriving, MultiParamTypeClasses, TypeFamilies, TypeSynonymInstances, QuasiQuotes #-}--- | 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 HSX.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 HSX.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- newtype ChildType (JMacroT m) = JMChild { unJMChild :: JExpr }- newtype AttributeType (JMacroT m) = JMAttr { unJMAttr :: JExpr }- genElement = element- xmlToChild = JMChild- pcdataToChild str = JMChild $ [jmacroE| document.createTextNode(`(str)`) |]----- | generate an XML Element-element :: (Functor m, Monad m, EmbedAsAttr (JMacroT m) attr, EmbedAsChild (JMacroT m) child) =>- Name -- ^ 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 ns 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 String String) where- asAttr (n := v) =- return [JMAttr [jmacroE| (function (){ var attrNode = document.createAttribute(`(n)`)- ; attrNode.nodeValue = `(v)`- ; return attrNode;- })()- |]]--instance (Functor m, Monad m) => EmbedAsChild (JMacroT m) Char where- asChild c = return [pcdataToChild [c]]--instance (Functor m, Monad m) => EmbedAsChild (JMacroT m) String where- asChild str = return [pcdataToChild 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 String Bool) where- asAttr (n := True) = asAttr (n := "true")- asAttr (n := False) = asAttr (n := "false")--instance (Functor m, Monad m) => EmbedAsAttr (JMacroT m) (Attr String Int) where- asAttr (n := v) = asAttr (n := 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) => XMLGenerator (JMacroT m)
hsx-jmacro.cabal view
@@ -1,7 +1,7 @@ Name: hsx-jmacro-Version: 7.2.0-Synopsis: hsx+jmacro support-Description: HSX allows for the use of literal XML in Haskell program text. JMacro allows for the use of javascript-syntax for generating javascript in Haskell. This library makes it easy to embed JMacro generated javascript in HSX templates.+Version: 7.3.0+Synopsis: hsp+jmacro support+Description: HSP allows for the use of literal XML in Haskell program text. JMacro allows for the use of javascript-syntax for generating javascript in Haskell. This library makes it easy to embed JMacro generated javascript in HSX templates. Homepage: http://www.happstack.com/ License: BSD3 License-file: LICENSE@@ -15,13 +15,13 @@ example2.hs Library- Exposed-modules: HSX.JMacro- HSX.JMacroT+ Exposed-modules: HSP.JMacro+ HSP.JMacroT Build-depends: base > 4 && <5,- happstack-hsp == 7.1.*,- hsx >= 0.10.2 && <0.11,+ happstack-hsp == 7.2.*,+ hsp == 0.9.*, jmacro >= 0.6 && < 0.7, mtl >= 2.0 && < 2.2, wl-pprint-text == 1.1.*,