I've been working on some quickly-growing Haskell based web applications, and am finding myself shot in the foot with this issue. Suppose I have some template that I've defined early in my code:
{-# LANGUAGE OverloadedStrings #-}
import Text.Blaze.Html5
import Text.Blaze.Html5.Attributes
import qualified Text.Blaze.Html5 as H
import qualified Text.Blaze.Html5.Attributes as A
foo = H.div ! class_ "foo"
and later, I decide to use foo
, but with a slight non-destructive amendment:
bar = foo ! class_ "bar" -- this should add bar to the classes available, imo
but alas, when I render the html, here is the result I get:
import Text.Blaze.Html.Renderer.String
λ: renderHtml $ bar "baz"
↪ "<div class=\"foo\" class=\"bar\"></div>"
This is a monad, after all! Is there any way we could integrate this kind of logic into blaze-html? Or is that beyond the scope of the templating framework? Are there any methods of selection (like jQuery), such that I could do something along the lines of...
bar' = do
classes <- classesOf foo
H.div ! class_ (classes ++ " bar")
Has anyone found a way around this? Are there any type-assisted Html tools for Haskell out there? This really has me scratching my head, and coming up with horrible ideas...
TL;DR: The current version of blaze doesn't support monoidal attribute manipulation.
Technical details
Problem
An
Attribute
is a newtype wrapper:Most (X)HTML attributes are created with
Text.Blaze.Internal.attribute
:where
AddAttribute
is one ofMarkupM
's constructors:Now,
(!)
fromAttributable
will basically apply theAttribute
. So given an attributefoo
, and an attributebar
,tag ! bar ! foo
is the same asfoo $ bar $ tag
. The renderer later unwraps theAddAttribute
constructors:A solution
In order to achieve your desired behaviour, you need to defer the attribute rendering a little bit further and collect the attributes in a intermediate data structure, for example a
Map ChoiceString AttributeValue
.However, keep in mind that all the types in this post are in
Text.Blaze.Internal
if you want to create your own renderer.Answers
No, it's not, it doesn't follow the monad laws.
See above, but keep in mind that the internal API can change.
Apparently, it's at least beyond the scope of blaze. Also, I don't whether an additional
Map
will introduce performance issues, which could undo the "blazingly fast" part of blaze.See above,
MarkupM
isn't a real monad. You could unravel the constructors to get to the correctAddAttributes
, but again, that's an internal type and constructor.