defaultLayout now works directly on widgets

This commit is contained in:
Michael Snoyman 2010-08-22 16:08:58 +03:00
parent 21becc6bda
commit f102b9882b
5 changed files with 163 additions and 151 deletions

View File

@ -6,6 +6,7 @@
{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE Rank2Types #-} {-# LANGUAGE Rank2Types #-}
{-# LANGUAGE OverloadedStrings #-}
--------------------------------------------------------- ---------------------------------------------------------
-- --
-- Module : Yesod.Helpers.Auth -- Module : Yesod.Helpers.Auth
@ -182,7 +183,9 @@ getOpenIdR = do
lookupGetParam "dest" >>= maybe (return ()) setUltDestString lookupGetParam "dest" >>= maybe (return ()) setUltDestString
rtom <- getRouteToMaster rtom <- getRouteToMaster
message <- getMessage message <- getMessage
applyLayout "Log in via OpenID" mempty [$hamlet| defaultLayout $ do
setTitle "Log in via OpenID"
addBody [$hamlet|
$maybe message msg $maybe message msg
%p.message $msg$ %p.message $msg$
%form!method=get!action=@rtom.OpenIdForwardR@ %form!method=get!action=@rtom.OpenIdForwardR@
@ -262,7 +265,9 @@ getDisplayName extra =
getCheckR :: Yesod master => GHandler (Auth master) master RepHtmlJson getCheckR :: Yesod master => GHandler (Auth master) master RepHtmlJson
getCheckR = do getCheckR = do
creds <- maybeCreds creds <- maybeCreds
applyLayoutJson "Authentication Status" mempty (html creds) (json creds) defaultLayoutJson (do
setTitle "Authentication Status"
addBody $ html creds) (json creds)
where where
html creds = [$hamlet| html creds = [$hamlet|
%h1 Authentication Status %h1 Authentication Status
@ -303,7 +308,7 @@ getEmailRegisterR :: Yesod master => GHandler (Auth master) master RepHtml
getEmailRegisterR = do getEmailRegisterR = do
_ae <- getAuthEmailSettings _ae <- getAuthEmailSettings
toMaster <- getRouteToMaster toMaster <- getRouteToMaster
applyLayout "Register a new account" mempty [$hamlet| defaultLayout $ setTitle "Register a new account" >> addBody [$hamlet|
%p Enter your e-mail address below, and a confirmation e-mail will be sent to you. %p Enter your e-mail address below, and a confirmation e-mail will be sent to you.
%form!method=post!action=@toMaster.EmailRegisterR@ %form!method=post!action=@toMaster.EmailRegisterR@
%label!for=email E-mail %label!for=email E-mail
@ -333,7 +338,7 @@ postEmailRegisterR = do
tm <- getRouteToMaster tm <- getRouteToMaster
let verUrl = render $ tm $ EmailVerifyR lid verKey let verUrl = render $ tm $ EmailVerifyR lid verKey
sendVerifyEmail ae email verKey verUrl sendVerifyEmail ae email verKey verUrl
applyLayout "Confirmation e-mail sent" mempty [$hamlet| defaultLayout $ setTitle "Confirmation e-mail sent" >> addBody [$hamlet|
%p A confirmation e-mail has been sent to $email$. %p A confirmation e-mail has been sent to $email$.
|] |]
@ -350,7 +355,9 @@ getEmailVerifyR lid key = do
Nothing) [] Nothing) []
toMaster <- getRouteToMaster toMaster <- getRouteToMaster
redirect RedirectTemporary $ toMaster EmailPasswordR redirect RedirectTemporary $ toMaster EmailPasswordR
_ -> applyLayout "Invalid verification key" mempty [$hamlet| _ -> defaultLayout $ do
setTitle "Invalid verification key"
addBody [$hamlet|
%p I'm sorry, but that was an invalid verification key. %p I'm sorry, but that was an invalid verification key.
|] |]
@ -359,7 +366,9 @@ getEmailLoginR = do
_ae <- getAuthEmailSettings _ae <- getAuthEmailSettings
toMaster <- getRouteToMaster toMaster <- getRouteToMaster
msg <- getMessage msg <- getMessage
applyLayout "Login" mempty [$hamlet| defaultLayout $ do
setTitle "Login"
addBody [$hamlet|
$maybe msg ms $maybe msg ms
%p.message $ms$ %p.message $ms$
%p Please log in to your account. %p Please log in to your account.
@ -414,7 +423,9 @@ getEmailPasswordR = do
setMessage $ string "You must be logged in to set a password" setMessage $ string "You must be logged in to set a password"
redirect RedirectTemporary $ toMaster EmailLoginR redirect RedirectTemporary $ toMaster EmailLoginR
msg <- getMessage msg <- getMessage
applyLayout "Set password" mempty [$hamlet| defaultLayout $ do
setTitle "Set password"
addBody [$hamlet|
$maybe msg ms $maybe msg ms
%p.message $ms$ %p.message $ms$
%h3 Set a new password %h3 Set a new password

View File

@ -3,6 +3,7 @@
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE Rank2Types #-} {-# LANGUAGE Rank2Types #-}
{-# LANGUAGE OverloadedStrings #-}
module Yesod.Helpers.Crud module Yesod.Helpers.Crud
( Item (..) ( Item (..)
, Crud (..) , Crud (..)
@ -17,7 +18,6 @@ import Yesod.Content
import Yesod.Handler import Yesod.Handler
import Text.Hamlet import Text.Hamlet
import Yesod.Form import Yesod.Form
import Data.Monoid (mempty)
import Language.Haskell.TH.Syntax import Language.Haskell.TH.Syntax
-- | An entity which can be displayed by the Crud subsite. -- | An entity which can be displayed by the Crud subsite.
@ -53,7 +53,9 @@ getCrudListR :: (Yesod master, Item item, SinglePiece (Key item))
getCrudListR = do getCrudListR = do
items <- getYesodSub >>= crudSelect items <- getYesodSub >>= crudSelect
toMaster <- getRouteToMaster toMaster <- getRouteToMaster
applyLayout "Items" mempty [$hamlet| defaultLayout $ do
setTitle "Items"
addBody [$hamlet|
%h1 Items %h1 Items
%ul %ul
$forall items item $forall items item
@ -111,7 +113,9 @@ getCrudDeleteR s = do
crud <- getYesodSub crud <- getYesodSub
item <- crudGet crud itemId >>= maybe notFound return -- Just ensure it exists item <- crudGet crud itemId >>= maybe notFound return -- Just ensure it exists
toMaster <- getRouteToMaster toMaster <- getRouteToMaster
applyLayout "Confirm delete" mempty [$hamlet| defaultLayout $ do
setTitle "Confirm delete"
addBody [$hamlet|
%form!method=post!action=@toMaster.CrudDeleteR.s@ %form!method=post!action=@toMaster.CrudDeleteR.s@
%h1 Really delete? %h1 Really delete?
%p Do you really want to delete $itemTitle.item$? %p Do you really want to delete $itemTitle.item$?
@ -151,7 +155,7 @@ crudHelper title me isPost = do
redirect RedirectTemporary $ toMaster $ CrudEditR redirect RedirectTemporary $ toMaster $ CrudEditR
$ toSinglePiece eid $ toSinglePiece eid
_ -> return () _ -> return ()
applyLayoutW $ do defaultLayout $ do
wrapWidget form (wrapForm toMaster enctype) wrapWidget form (wrapForm toMaster enctype)
setTitle $ string title setTitle $ string title
where where

View File

@ -1,3 +1,5 @@
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-- | Normal users should never need access to these. -- | Normal users should never need access to these.
module Yesod.Internal module Yesod.Internal
( -- * Error responses ( -- * Error responses
@ -6,8 +8,23 @@ module Yesod.Internal
, Header (..) , Header (..)
-- * Cookie names -- * Cookie names
, langKey , langKey
-- * Widgets
, Location (..)
, UniqueList (..)
, Script (..)
, Stylesheet (..)
, Title (..)
, Head (..)
, Body (..)
, locationToHamlet
, runUniqueList
, toUnique
) where ) where
import Text.Hamlet (Hamlet, hamlet, Html)
import Data.Monoid (Monoid (..))
import Data.List (nub)
-- | Responses to indicate some form of an error occurred. These are different -- | Responses to indicate some form of an error occurred. These are different
-- from 'SpecialResponse' in that they allow for custom error pages. -- from 'SpecialResponse' in that they allow for custom error pages.
data ErrorResponse = data ErrorResponse =
@ -28,3 +45,29 @@ data Header =
langKey :: String langKey :: String
langKey = "_LANG" langKey = "_LANG"
data Location url = Local url | Remote String
deriving (Show, Eq)
locationToHamlet :: Location url -> Hamlet url
locationToHamlet (Local url) = [$hamlet|@url@|]
locationToHamlet (Remote s) = [$hamlet|$s$|]
newtype UniqueList x = UniqueList ([x] -> [x])
instance Monoid (UniqueList x) where
mempty = UniqueList id
UniqueList x `mappend` UniqueList y = UniqueList $ x . y
runUniqueList :: Eq x => UniqueList x -> [x]
runUniqueList (UniqueList x) = nub $ x []
toUnique :: x -> UniqueList x
toUnique = UniqueList . (:)
newtype Script url = Script { unScript :: Location url }
deriving (Show, Eq)
newtype Stylesheet url = Stylesheet { unStylesheet :: Location url }
deriving (Show, Eq)
newtype Title = Title { unTitle :: Html }
newtype Head url = Head (Hamlet url)
deriving Monoid
newtype Body url = Body (Hamlet url)
deriving Monoid

View File

@ -1,5 +1,4 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE PackageImports #-} {-# LANGUAGE PackageImports #-}
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
@ -7,12 +6,9 @@
-- generator, allowing you to create truly modular HTML components. -- generator, allowing you to create truly modular HTML components.
module Yesod.Widget module Yesod.Widget
( -- * Datatype ( -- * Datatype
GWidget GWidget (..)
, Widget , Widget
, liftHandler , liftHandler
-- * Unwrapping
, widgetToPageContent
, applyLayoutW
-- * Creating -- * Creating
, newIdent , newIdent
, setTitle , setTitle
@ -31,46 +27,18 @@ module Yesod.Widget
, extractBody , extractBody
) where ) where
import Data.List (nub)
import Data.Monoid import Data.Monoid
import Control.Monad.Trans.Writer import Control.Monad.Trans.Writer
import Control.Monad.Trans.State import Control.Monad.Trans.State
import Yesod.Hamlet (PageContent (..))
import Text.Hamlet import Text.Hamlet
import Text.Cassius import Text.Cassius
import Text.Julius import Text.Julius
import Yesod.Handler (Route, GHandler, getUrlRenderParams) import Yesod.Handler (Route, GHandler)
import Yesod.Yesod (Yesod, defaultLayout, addStaticContent)
import Yesod.Content (RepHtml (..))
import Control.Applicative (Applicative) import Control.Applicative (Applicative)
import Control.Monad.IO.Class (MonadIO) import Control.Monad.IO.Class (MonadIO)
import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.Class (lift)
import "MonadCatchIO-transformers" Control.Monad.CatchIO (MonadCatchIO) import "MonadCatchIO-transformers" Control.Monad.CatchIO (MonadCatchIO)
import Yesod.Internal
data Location url = Local url | Remote String
deriving (Show, Eq)
locationToHamlet :: Location url -> Hamlet url
locationToHamlet (Local url) = [$hamlet|@url@|]
locationToHamlet (Remote s) = [$hamlet|$s$|]
newtype UniqueList x = UniqueList ([x] -> [x])
instance Monoid (UniqueList x) where
mempty = UniqueList id
UniqueList x `mappend` UniqueList y = UniqueList $ x . y
runUniqueList :: Eq x => UniqueList x -> [x]
runUniqueList (UniqueList x) = nub $ x []
toUnique :: x -> UniqueList x
toUnique = UniqueList . (:)
newtype Script url = Script { unScript :: Location url }
deriving (Show, Eq)
newtype Stylesheet url = Stylesheet { unStylesheet :: Location url }
deriving (Show, Eq)
newtype Title = Title { unTitle :: Html }
newtype Head url = Head (Hamlet url)
deriving Monoid
newtype Body url = Body (Hamlet url)
deriving Monoid
-- | A generic widget, allowing specification of both the subsite and master -- | A generic widget, allowing specification of both the subsite and master
-- site datatypes. This is basically a large 'WriterT' stack keeping track of -- site datatypes. This is basically a large 'WriterT' stack keeping track of
@ -151,78 +119,6 @@ addScriptRemote =
addJavascript :: Julius (Route master) -> GWidget sub master () addJavascript :: Julius (Route master) -> GWidget sub master ()
addJavascript = GWidget . lift . lift . lift . lift . lift. tell . Just addJavascript = GWidget . lift . lift . lift . lift . lift. tell . Just
-- | Apply the default layout to the given widget.
applyLayoutW :: (Eq (Route m), Yesod m)
=> GWidget sub m () -> GHandler sub m RepHtml
applyLayoutW w = widgetToPageContent w >>= fmap RepHtml . defaultLayout
-- | Convert a widget to a 'PageContent'.
widgetToPageContent :: (Eq (Route master), Yesod master)
=> GWidget sub master ()
-> GHandler sub master (PageContent (Route master))
widgetToPageContent (GWidget w) = do
w' <- flip evalStateT 0
$ runWriterT $ runWriterT $ runWriterT $ runWriterT
$ runWriterT $ runWriterT $ runWriterT w
let ((((((((),
Body body),
Last mTitle),
scripts'),
stylesheets'),
style),
jscript),
Head head') = w'
let title = maybe mempty unTitle mTitle
let scripts = map (locationToHamlet . unScript) $ runUniqueList scripts'
let stylesheets = map (locationToHamlet . unStylesheet)
$ runUniqueList stylesheets'
let cssToHtml (Css b) = Html b
celper :: Cassius url -> Hamlet url
celper = fmap cssToHtml
jsToHtml (Javascript b) = Html b
jelper :: Julius url -> Hamlet url
jelper = fmap jsToHtml
render <- getUrlRenderParams
let renderLoc x =
case x of
Nothing -> Nothing
Just (Left s) -> Just s
Just (Right (u, p)) -> Just $ render u p
cssLoc <-
case style of
Nothing -> return Nothing
Just s -> do
x <- addStaticContent "css" "text/css; charset=utf-8"
$ renderCassius render s
return $ renderLoc x
jsLoc <-
case jscript of
Nothing -> return Nothing
Just s -> do
x <- addStaticContent "js" "text/javascript; charset=utf-8"
$ renderJulius render s
return $ renderLoc x
let head'' = [$hamlet|
$forall scripts s
%script!src=^s^
$forall stylesheets s
%link!rel=stylesheet!href=^s^
$maybe style s
$maybe cssLoc s
%link!rel=stylesheet!href=$s$
$nothing
%style ^celper.s^
$maybe jscript j
$maybe jsLoc s
%script!src=$s$
$nothing
%script ^jelper.j^
^head'^
|]
return $ PageContent title head'' body
-- | Modify the given 'GWidget' by wrapping the body tag HTML code with the -- | Modify the given 'GWidget' by wrapping the body tag HTML code with the
-- given function. You might also consider using 'extractBody'. -- given function. You might also consider using 'extractBody'.
wrapWidget :: GWidget s m a wrapWidget :: GWidget s m a

View File

@ -18,10 +18,10 @@ module Yesod.Yesod
-- ** Breadcrumbs -- ** Breadcrumbs
, YesodBreadcrumbs (..) , YesodBreadcrumbs (..)
, breadcrumbs , breadcrumbs
-- * Convenience functions -- * Utitlities
, applyLayout
, applyLayoutJson
, maybeAuthorized , maybeAuthorized
, widgetToPageContent
, defaultLayoutJson
-- * Defaults -- * Defaults
, defaultErrorHandler , defaultErrorHandler
-- * Data types -- * Data types
@ -39,6 +39,7 @@ import Yesod.Content
import Yesod.Json import Yesod.Json
#endif #endif
import Yesod.Widget
import Yesod.Request import Yesod.Request
import Yesod.Hamlet import Yesod.Hamlet
import Yesod.Handler import Yesod.Handler
@ -46,7 +47,6 @@ import qualified Network.Wai as W
import Yesod.Internal import Yesod.Internal
import Web.ClientSession (getKey, defaultKeyFile) import Web.ClientSession (getKey, defaultKeyFile)
import qualified Web.ClientSession as CS import qualified Web.ClientSession as CS
import Data.Monoid (mempty)
import qualified Data.ByteString.UTF8 as BSU import qualified Data.ByteString.UTF8 as BSU
import Database.Persist import Database.Persist
import Control.Monad.Trans.Class (MonadTrans (..)) import Control.Monad.Trans.Class (MonadTrans (..))
@ -55,6 +55,12 @@ import qualified Data.ByteString as S
import qualified Network.Wai.Middleware.CleanPath import qualified Network.Wai.Middleware.CleanPath
import qualified Data.ByteString.Lazy as L import qualified Data.ByteString.Lazy as L
import Yesod.WebRoutes import Yesod.WebRoutes
import Data.Monoid
import Control.Monad.Trans.Writer
import Control.Monad.Trans.State hiding (get)
import Text.Hamlet
import Text.Cassius
import Text.Julius
#if TEST #if TEST
import Test.Framework (testGroup, Test) import Test.Framework (testGroup, Test)
@ -103,8 +109,10 @@ class Eq (Route a) => Yesod a where
errorHandler = defaultErrorHandler errorHandler = defaultErrorHandler
-- | Applies some form of layout to the contents of a page. -- | Applies some form of layout to the contents of a page.
defaultLayout :: PageContent (Route a) -> GHandler sub a Content defaultLayout :: GWidget sub a () -> GHandler sub a RepHtml
defaultLayout p = hamletToContent [$hamlet| defaultLayout w = do
p <- widgetToPageContent w
hamletToRepHtml [$hamlet|
!!! !!!
%html %html
%head %head
@ -226,41 +234,24 @@ breadcrumbs = do
(title, next) <- breadcrumb this (title, next) <- breadcrumb this
go ((this, title) : back) next go ((this, title) : back) next
-- | Apply the default layout ('defaultLayout') to the given title and body.
applyLayout :: Yesod master
=> String -- ^ title
-> Hamlet (Route master) -- ^ head
-> Hamlet (Route master) -- ^ body
-> GHandler sub master RepHtml
applyLayout t h b =
RepHtml `fmap` defaultLayout PageContent
{ pageTitle = string t
, pageHead = h
, pageBody = b
}
-- | Provide both an HTML and JSON representation for a piece of data, using -- | Provide both an HTML and JSON representation for a piece of data, using
-- the default layout for the HTML output ('defaultLayout'). -- the default layout for the HTML output ('defaultLayout').
applyLayoutJson :: Yesod master defaultLayoutJson :: Yesod master
=> String -- ^ title => GWidget sub master ()
-> Hamlet (Route master) -- ^ head -> Json
-> Hamlet (Route master) -- ^ body -> GHandler sub master RepHtmlJson
-> Json defaultLayoutJson w json = do
-> GHandler sub master RepHtmlJson RepHtml html' <- defaultLayout w
applyLayoutJson t h html json = do
html' <- defaultLayout PageContent
{ pageTitle = string t
, pageHead = h
, pageBody = html
}
json' <- jsonToContent json json' <- jsonToContent json
return $ RepHtmlJson html' json' return $ RepHtmlJson html' json'
applyLayout' :: Yesod master applyLayout' :: Yesod master
=> String -- ^ title => Html -- ^ title
-> Hamlet (Route master) -- ^ body -> Hamlet (Route master) -- ^ body
-> GHandler sub master ChooseRep -> GHandler sub master ChooseRep
applyLayout' s = fmap chooseRep . applyLayout s mempty applyLayout' title body = fmap chooseRep $ defaultLayout $ do
setTitle title
addBody body
-- | The default error handler for 'errorHandler'. -- | The default error handler for 'errorHandler'.
defaultErrorHandler :: Yesod y => ErrorResponse -> GHandler sub y ChooseRep defaultErrorHandler :: Yesod y => ErrorResponse -> GHandler sub y ChooseRep
@ -323,6 +314,73 @@ maybeAuthorized r isWrite = do
x <- isAuthorized r isWrite x <- isAuthorized r isWrite
return $ if x == Authorized then Just r else Nothing return $ if x == Authorized then Just r else Nothing
-- | Convert a widget to a 'PageContent'.
widgetToPageContent :: (Eq (Route master), Yesod master)
=> GWidget sub master ()
-> GHandler sub master (PageContent (Route master))
widgetToPageContent (GWidget w) = do
w' <- flip evalStateT 0
$ runWriterT $ runWriterT $ runWriterT $ runWriterT
$ runWriterT $ runWriterT $ runWriterT w
let ((((((((),
Body body),
Last mTitle),
scripts'),
stylesheets'),
style),
jscript),
Head head') = w'
let title = maybe mempty unTitle mTitle
let scripts = map (locationToHamlet . unScript) $ runUniqueList scripts'
let stylesheets = map (locationToHamlet . unStylesheet)
$ runUniqueList stylesheets'
let cssToHtml (Css b) = Html b
celper :: Cassius url -> Hamlet url
celper = fmap cssToHtml
jsToHtml (Javascript b) = Html b
jelper :: Julius url -> Hamlet url
jelper = fmap jsToHtml
render <- getUrlRenderParams
let renderLoc x =
case x of
Nothing -> Nothing
Just (Left s) -> Just s
Just (Right (u, p)) -> Just $ render u p
cssLoc <-
case style of
Nothing -> return Nothing
Just s -> do
x <- addStaticContent "css" "text/css; charset=utf-8"
$ renderCassius render s
return $ renderLoc x
jsLoc <-
case jscript of
Nothing -> return Nothing
Just s -> do
x <- addStaticContent "js" "text/javascript; charset=utf-8"
$ renderJulius render s
return $ renderLoc x
let head'' = [$hamlet|
$forall scripts s
%script!src=^s^
$forall stylesheets s
%link!rel=stylesheet!href=^s^
$maybe style s
$maybe cssLoc s
%link!rel=stylesheet!href=$s$
$nothing
%style ^celper.s^
$maybe jscript j
$maybe jsLoc s
%script!src=$s$
$nothing
%script ^jelper.j^
^head'^
|]
return $ PageContent title head'' body
#if TEST #if TEST
testSuite :: Test testSuite :: Test
testSuite = testGroup "Yesod.Yesod" testSuite = testGroup "Yesod.Yesod"