/yesod-core/Yesod/Widget.hs

https://github.com/PeterScott/yesod · Haskell · 267 lines · 176 code · 38 blank · 53 comment · 0 complexity · 803251b840698cb628e4ad967d6132ab MD5 · raw file

  1. {-# LANGUAGE GeneralizedNewtypeDeriving #-}
  2. {-# LANGUAGE FlexibleInstances #-}
  3. {-# LANGUAGE TypeFamilies #-}
  4. {-# LANGUAGE TemplateHaskell #-}
  5. {-# LANGUAGE MultiParamTypeClasses #-}
  6. -- | Widgets combine HTML with JS and CSS dependencies with a unique identifier
  7. -- generator, allowing you to create truly modular HTML components.
  8. module Yesod.Widget
  9. ( -- * Datatype
  10. GWidget
  11. , GGWidget (..)
  12. , PageContent (..)
  13. -- * Special Hamlet quasiquoter/TH for Widgets
  14. , whamlet
  15. , whamletFile
  16. , ihamletToRepHtml
  17. -- * Creating
  18. -- ** Head of page
  19. , setTitle
  20. , setTitleI
  21. , addHamletHead
  22. , addHtmlHead
  23. -- ** Body
  24. , addHamlet
  25. , addHtml
  26. , addWidget
  27. , addSubWidget
  28. -- ** CSS
  29. , addCassius
  30. , addCassiusMedia
  31. , addLucius
  32. , addLuciusMedia
  33. , addStylesheet
  34. , addStylesheetAttrs
  35. , addStylesheetRemote
  36. , addStylesheetRemoteAttrs
  37. , addStylesheetEither
  38. -- ** Javascript
  39. , addJulius
  40. , addJuliusBody
  41. , addCoffee
  42. , addCoffeeBody
  43. , addScript
  44. , addScriptAttrs
  45. , addScriptRemote
  46. , addScriptRemoteAttrs
  47. , addScriptEither
  48. -- * Utilities
  49. , extractBody
  50. ) where
  51. import Data.Monoid
  52. import Control.Monad.Trans.RWS
  53. import qualified Text.Blaze.Html5 as H
  54. import Text.Hamlet
  55. import Text.Cassius
  56. import Text.Lucius (Lucius)
  57. import Text.Julius
  58. import Text.Coffee
  59. import Yesod.Handler
  60. (Route, GHandler, GGHandler, YesodSubRoute(..), toMasterHandlerMaybe, getYesod
  61. , getMessageRender, getUrlRenderParams
  62. )
  63. import Yesod.Message (RenderMessage)
  64. import Yesod.Content (RepHtml (..), toContent)
  65. import Control.Applicative (Applicative)
  66. import Control.Monad.IO.Class (MonadIO (liftIO))
  67. import Control.Monad.Trans.Class (MonadTrans (lift))
  68. import Yesod.Internal
  69. import Control.Monad (liftM)
  70. import Data.Text (Text)
  71. import qualified Data.Map as Map
  72. import Language.Haskell.TH.Quote (QuasiQuoter)
  73. import Language.Haskell.TH.Syntax (Q, Exp (InfixE, VarE, LamE), Pat (VarP), newName)
  74. import Control.Monad.IO.Control (MonadControlIO)
  75. import qualified Text.Hamlet as NP
  76. import Data.Text.Lazy.Builder (fromLazyText)
  77. import Text.Blaze (toHtml, preEscapedLazyText)
  78. -- | A generic widget, allowing specification of both the subsite and master
  79. -- site datatypes. This is basically a large 'WriterT' stack keeping track of
  80. -- dependencies along with a 'StateT' to track unique identifiers.
  81. newtype GGWidget m monad a = GWidget { unGWidget :: GWInner m monad a }
  82. deriving (Functor, Applicative, Monad, MonadIO, MonadControlIO)
  83. instance MonadTrans (GGWidget m) where
  84. lift = GWidget . lift
  85. type GWidget s m = GGWidget m (GHandler s m)
  86. type GWInner master = RWST () (GWData (Route master)) Int
  87. instance (Monad monad, a ~ ()) => Monoid (GGWidget master monad a) where
  88. mempty = return ()
  89. mappend x y = x >> y
  90. addSubWidget :: (YesodSubRoute sub master) => sub -> GWidget sub master a -> GWidget sub' master a
  91. addSubWidget sub (GWidget w) = do
  92. master <- lift getYesod
  93. let sr = fromSubRoute sub master
  94. s <- GWidget get
  95. (a, s', w') <- lift $ toMasterHandlerMaybe sr (const sub) Nothing $ runRWST w () s
  96. GWidget $ put s'
  97. GWidget $ tell w'
  98. return a
  99. -- | Set the page title. Calling 'setTitle' multiple times overrides previously
  100. -- set values.
  101. setTitle :: Monad m => Html -> GGWidget master m ()
  102. setTitle x = GWidget $ tell $ GWData mempty (Last $ Just $ Title x) mempty mempty mempty mempty mempty
  103. -- | Set the page title. Calling 'setTitle' multiple times overrides previously
  104. -- set values.
  105. setTitleI :: (RenderMessage master msg, Monad m) => msg -> GGWidget master (GGHandler sub master m) ()
  106. setTitleI msg = do
  107. mr <- lift getMessageRender
  108. setTitle $ toHtml $ mr msg
  109. -- | Add a 'Hamlet' to the head tag.
  110. addHamletHead :: Monad m => Hamlet (Route master) -> GGWidget master m ()
  111. addHamletHead = GWidget . tell . GWData mempty mempty mempty mempty mempty mempty . Head
  112. -- | Add a 'Html' to the head tag.
  113. addHtmlHead :: Monad m => Html -> GGWidget master m ()
  114. addHtmlHead = addHamletHead . const
  115. -- | Add a 'Hamlet' to the body tag.
  116. addHamlet :: Monad m => Hamlet (Route master) -> GGWidget master m ()
  117. addHamlet x = GWidget $ tell $ GWData (Body x) mempty mempty mempty mempty mempty mempty
  118. -- | Add a 'Html' to the body tag.
  119. addHtml :: Monad m => Html -> GGWidget master m ()
  120. addHtml = addHamlet . const
  121. -- | Add another widget. This is defined as 'id', by can help with types, and
  122. -- makes widget blocks look more consistent.
  123. addWidget :: Monad mo => GGWidget m mo () -> GGWidget m mo ()
  124. addWidget = id
  125. -- | Add some raw CSS to the style tag. Applies to all media types.
  126. addCassius :: Monad m => Cassius (Route master) -> GGWidget master m ()
  127. addCassius x = GWidget $ tell $ GWData mempty mempty mempty mempty (Map.singleton Nothing x) mempty mempty
  128. -- | Identical to 'addCassius'.
  129. addLucius :: Monad m => Lucius (Route master) -> GGWidget master m ()
  130. addLucius = addCassius
  131. -- | Add some raw CSS to the style tag, for a specific media type.
  132. addCassiusMedia :: Monad m => Text -> Cassius (Route master) -> GGWidget master m ()
  133. addCassiusMedia m x = GWidget $ tell $ GWData mempty mempty mempty mempty (Map.singleton (Just m) x) mempty mempty
  134. -- | Identical to 'addCassiusMedia'.
  135. addLuciusMedia :: Monad m => Text -> Lucius (Route master) -> GGWidget master m ()
  136. addLuciusMedia = addCassiusMedia
  137. -- | Link to the specified local stylesheet.
  138. addStylesheet :: Monad m => Route master -> GGWidget master m ()
  139. addStylesheet = flip addStylesheetAttrs []
  140. -- | Link to the specified local stylesheet.
  141. addStylesheetAttrs :: Monad m => Route master -> [(Text, Text)] -> GGWidget master m ()
  142. addStylesheetAttrs x y = GWidget $ tell $ GWData mempty mempty mempty (toUnique $ Stylesheet (Local x) y) mempty mempty mempty
  143. -- | Link to the specified remote stylesheet.
  144. addStylesheetRemote :: Monad m => Text -> GGWidget master m ()
  145. addStylesheetRemote = flip addStylesheetRemoteAttrs []
  146. -- | Link to the specified remote stylesheet.
  147. addStylesheetRemoteAttrs :: Monad m => Text -> [(Text, Text)] -> GGWidget master m ()
  148. addStylesheetRemoteAttrs x y = GWidget $ tell $ GWData mempty mempty mempty (toUnique $ Stylesheet (Remote x) y) mempty mempty mempty
  149. addStylesheetEither :: Monad m => Either (Route master) Text -> GGWidget master m ()
  150. addStylesheetEither = either addStylesheet addStylesheetRemote
  151. addScriptEither :: Monad m => Either (Route master) Text -> GGWidget master m ()
  152. addScriptEither = either addScript addScriptRemote
  153. -- | Link to the specified local script.
  154. addScript :: Monad m => Route master -> GGWidget master m ()
  155. addScript = flip addScriptAttrs []
  156. -- | Link to the specified local script.
  157. addScriptAttrs :: Monad m => Route master -> [(Text, Text)] -> GGWidget master m ()
  158. addScriptAttrs x y = GWidget $ tell $ GWData mempty mempty (toUnique $ Script (Local x) y) mempty mempty mempty mempty
  159. -- | Link to the specified remote script.
  160. addScriptRemote :: Monad m => Text -> GGWidget master m ()
  161. addScriptRemote = flip addScriptRemoteAttrs []
  162. -- | Link to the specified remote script.
  163. addScriptRemoteAttrs :: Monad m => Text -> [(Text, Text)] -> GGWidget master m ()
  164. addScriptRemoteAttrs x y = GWidget $ tell $ GWData mempty mempty (toUnique $ Script (Remote x) y) mempty mempty mempty mempty
  165. -- | Include raw Javascript in the page's script tag.
  166. addJulius :: Monad m => Julius (Route master) -> GGWidget master m ()
  167. addJulius x = GWidget $ tell $ GWData mempty mempty mempty mempty mempty (Just x) mempty
  168. -- | Add a new script tag to the body with the contents of this 'Julius'
  169. -- template.
  170. addJuliusBody :: Monad m => Julius (Route master) -> GGWidget master m ()
  171. addJuliusBody j = addHamlet $ \r -> H.script $ preEscapedLazyText $ renderJulius r j
  172. -- | Add Coffesscript to the page's script tag. Requires the coffeescript
  173. -- executable to be present at runtime.
  174. addCoffee :: MonadIO m => Coffee (Route master) -> GGWidget master (GGHandler sub master m) ()
  175. addCoffee c = do
  176. render <- lift getUrlRenderParams
  177. t <- liftIO $ renderCoffee render c
  178. addJulius $ const $ Javascript $ fromLazyText t
  179. -- | Add a new script tag to the body with the contents of this Coffesscript
  180. -- template. Requires the coffeescript executable to be present at runtime.
  181. addCoffeeBody :: MonadIO m => Coffee (Route master) -> GGWidget master (GGHandler sub master m) ()
  182. addCoffeeBody c = do
  183. render <- lift getUrlRenderParams
  184. t <- liftIO $ renderCoffee render c
  185. addJuliusBody $ const $ Javascript $ fromLazyText t
  186. -- | Pull out the HTML tag contents and return it. Useful for performing some
  187. -- manipulations. It can be easier to use this sometimes than 'wrapWidget'.
  188. extractBody :: Monad mo => GGWidget m mo () -> GGWidget m mo (Hamlet (Route m))
  189. extractBody (GWidget w) =
  190. GWidget $ mapRWST (liftM go) w
  191. where
  192. go ((), s, GWData (Body h) b c d e f g) = (h, s, GWData (Body mempty) b c d e f g)
  193. -- | Content for a web page. By providing this datatype, we can easily create
  194. -- generic site templates, which would have the type signature:
  195. --
  196. -- > PageContent url -> Hamlet url
  197. data PageContent url = PageContent
  198. { pageTitle :: Html
  199. , pageHead :: Hamlet url
  200. , pageBody :: Hamlet url
  201. }
  202. whamlet :: QuasiQuoter
  203. whamlet = NP.hamletWithSettings rules NP.defaultHamletSettings
  204. whamletFile :: FilePath -> Q Exp
  205. whamletFile = NP.hamletFileWithSettings rules NP.defaultHamletSettings
  206. rules :: Q NP.HamletRules
  207. rules = do
  208. ah <- [|addHtml|]
  209. let helper qg f = do
  210. x <- newName "urender"
  211. e <- f $ VarE x
  212. let e' = LamE [VarP x] e
  213. g <- qg
  214. bind <- [|(>>=)|]
  215. return $ InfixE (Just g) bind (Just e')
  216. let ur f = do
  217. let env = NP.Env
  218. (Just $ helper [|lift getUrlRenderParams|])
  219. (Just $ helper [|liftM (toHtml .) $ lift getMessageRender|])
  220. f env
  221. return $ NP.HamletRules ah ur $ \_ b -> return b
  222. -- | Wraps the 'Content' generated by 'hamletToContent' in a 'RepHtml'.
  223. ihamletToRepHtml :: (Monad mo, RenderMessage master message)
  224. => NP.IHamlet message (Route master)
  225. -> GGHandler sub master mo RepHtml
  226. ihamletToRepHtml ih = do
  227. urender <- getUrlRenderParams
  228. mrender <- getMessageRender
  229. return $ RepHtml $ toContent $ ih (toHtml . mrender) urender