-- | Build HTML tables using @yesod@ and @colonnade@. To learn -- how to use this module, first read the documentation for @colonnade@, -- and then read the documentation for @blaze-colonnade@. This library -- and @blaze-colonnade@ are entirely distinct; neither depends on the -- other. However, the interfaces they expose are very similar, and -- the explanations provided counterpart are sufficient to understand -- this library. module Yesod.Colonnade ( -- * Build Cell(..) , cell , stringCell , textCell , builderCell , anchorCell , anchorWidget -- * Apply , encodeWidgetTable , encodeCellTable , encodeDefinitionTable , encodeListItems ) where import Yesod.Core import Yesod.Core.Types (Body(..),GWData(..),WidgetFor(..),wdRef) import Colonnade (Colonnade,Headed,Headless) import Data.Text (Text) import Control.Monad import Data.IORef (modifyIORef') import Data.Monoid import Data.String (IsString(..)) import Text.Blaze (Attribute,toValue) import Data.Foldable import Yesod.Elements (table_,thead_,tbody_,tr_,td_,th_,ul_,li_,a_) import Data.Semigroup (Semigroup) import qualified Data.Semigroup as SG import qualified Text.Blaze.Html5.Attributes as HA import qualified Text.Blaze.Html5 as H import qualified Colonnade.Encode as E import qualified Data.Text as Text import qualified Data.Text.Lazy as LText import qualified Data.Text.Lazy.Builder as TBuilder -- | The attributes that will be applied to a @@ and -- the HTML content that will go inside it. data Cell site = Cell { cellAttrs :: [Attribute] , cellContents :: !(WidgetFor site ()) } instance IsString (Cell site) where fromString = stringCell instance Semigroup (Cell site) where Cell a1 c1 <> Cell a2 c2 = Cell (mappend a1 a2) (mappend c1 c2) instance Monoid (Cell site) where mempty = Cell mempty mempty mappend = (SG.<>) -- | Create a 'Cell' from a 'Widget' cell :: WidgetFor site () -> Cell site cell = Cell mempty -- | Create a 'Cell' from a 'String' stringCell :: String -> Cell site stringCell = cell . fromString -- | Create a 'Cell' from a 'Text' textCell :: Text -> Cell site textCell = cell . toWidget . toHtml -- | Create a 'Cell' from a text builder builderCell :: TBuilder.Builder -> Cell site builderCell = cell . toWidget . toHtml . LText.toStrict . TBuilder.toLazyText -- | Create a 'Cell' whose content is hyperlinked by wrapping -- it in an @\@. anchorCell :: (a -> Route site) -- ^ Route that will go in @href@ attribute -> (a -> WidgetFor site ()) -- ^ Content wrapped by @@ tag -> a -- ^ Value -> Cell site anchorCell getRoute getContent = cell . anchorWidget getRoute getContent -- | Create a widget whose content is hyperlinked by wrapping -- it in an @\@. anchorWidget :: (a -> Route site) -- ^ Route that will go in @href@ attribute -> (a -> WidgetFor site ()) -- ^ Content wrapped by @@ tag -> a -- ^ Value -> WidgetFor site () anchorWidget getRoute getContent a = do urlRender <- getUrlRender a_ [HA.href (toValue (urlRender (getRoute a)))] (getContent a) -- | This determines the attributes that are added -- to the individual @li@s by concatenating the header\'s -- attributes with the data\'s attributes. encodeListItems :: (WidgetFor site () -> WidgetFor site ()) -- ^ Wrapper for items, often @ul@ -> (WidgetFor site () -> WidgetFor site () -> WidgetFor site ()) -- ^ Combines header with data -> Colonnade Headed a (Cell site) -- ^ How to encode data as a row -> a -- ^ The value to display -> WidgetFor site () encodeListItems ulWrap combine enc = ulWrap . E.bothMonadic_ enc (\(Cell ha hc) (Cell ba bc) -> li_ (ha <> ba) (combine hc bc) ) -- | A two-column table with the header content displayed in the -- first column and the data displayed in the second column. Note -- that the generated HTML table does not have a @thead@. encodeDefinitionTable :: [Attribute] -- ^ Attributes of @table@ element. -> Colonnade Headed a (Cell site) -- ^ How to encode data as a row -> a -- ^ The value to display -> WidgetFor site () encodeDefinitionTable attrs enc a = table_ attrs $ tbody_ [] $ E.bothMonadic_ enc (\theKey theValue -> tr_ [] $ do widgetFromCell td_ theKey widgetFromCell td_ theValue ) a -- | Encode an html table with attributes on the table cells. -- If you are using the bootstrap css framework, then you may want -- to call this with the first argument as: -- -- > encodeCellTable (HA.class_ "table table-striped") ... encodeCellTable :: (Foldable f, E.Headedness h) => [Attribute] -- ^ Attributes of @table@ element -> Colonnade h a (Cell site) -- ^ How to encode data as a row -> f a -- ^ Rows of data -> WidgetFor site () encodeCellTable = encodeTable (E.headednessPure mempty) mempty (const mempty) widgetFromCell -- | Encode an html table. encodeWidgetTable :: (Foldable f, E.Headedness h) => [Attribute] -- ^ Attributes of @\@ element -> Colonnade h a (WidgetFor site ()) -- ^ How to encode data as columns -> f a -- ^ Rows of data -> WidgetFor site () encodeWidgetTable = encodeTable (E.headednessPure mempty) mempty (const mempty) ($ mempty) -- | Encode a table. This handles a very general case and -- is seldom needed by users. One of the arguments provided is -- used to add attributes to the generated @\@ elements. encodeTable :: (Foldable f, E.Headedness h) => h [Attribute] -- ^ Attributes of @\@ -> [Attribute] -- ^ Attributes of @\@ element -> (a -> [Attribute]) -- ^ Attributes of each @\@ element -> (([Attribute] -> WidgetFor site () -> WidgetFor site ()) -> c -> WidgetFor site ()) -- ^ Wrap content and convert to 'Html' -> [Attribute] -- ^ Attributes of @\@ element -> Colonnade h a c -- ^ How to encode data as a row -> f a -- ^ Collection of data -> WidgetFor site () encodeTable theadAttrs tbodyAttrs trAttrs wrapContent tableAttrs colonnade xs = table_ tableAttrs $ do for_ E.headednessExtract $ \unhead -> thead_ (unhead theadAttrs) $ do E.headerMonadicGeneral_ colonnade (wrapContent th_) tbody_ tbodyAttrs $ do forM_ xs $ \x -> do tr_ (trAttrs x) (E.rowMonadic_ colonnade (wrapContent td_) x) widgetFromCell :: ([Attribute] -> WidgetFor site () -> WidgetFor site ()) -> Cell site -> WidgetFor site () widgetFromCell f (Cell attrs contents) = f attrs contents