htmlhaskellblaze-html

Blaze-html class attribute chaining / appending / concatenation?


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...


Solution

  • TL;DR: The current version of blaze doesn't support monoidal attribute manipulation.

    Technical details

    Problem

    An Attribute is a newtype wrapper:

    newtype Attribute = Attribute (forall a. MarkupM a -> MarkupM a)
    

    Most (X)HTML attributes are created with Text.Blaze.Internal.attribute:

    attribute :: Tag             -- ^ Raw key
          -> Tag             -- ^ Shared key string for the HTML attribute.
          -> AttributeValue  -- ^ Value for the HTML attribute.
          -> Attribute       -- ^ Resulting HTML attribute.
    attribute rawKey key value = Attribute $
        AddAttribute (unTag rawKey) (unTag key) (unAttributeValue value)
    

    where AddAttribute is one of MarkupM's constructors:

    data MarkupM a
        = {- ... -}
        -- | Add an attribute to the inner HTML. Raw key, key, value, HTML to
        -- receive the attribute.
        | AddAttribute StaticString StaticString ChoiceString (MarkupM a)
          {- ... -}
    

    Now, (!) from Attributable will basically apply the Attribute. So given an attribute foo, and an attribute bar, tag ! bar ! foo is the same as foo $ bar $ tag. The renderer later unwraps the AddAttribute constructors:

    go attrs (AddAttribute _ key value h) =
        go (B.copyByteString (getUtf8ByteString key)
            `mappend` fromChoiceString value
            `mappend` B.fromChar '"'
            `mappend` attrs) h
    

    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

    This is a monad, after all!

    No, it's not, it doesn't follow the monad laws.

    Is there any way we could integrate this kind of logic into blaze-html?

    See above, but keep in mind that the internal API can change.

    Or is that beyond the scope of the templating framework?

    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.

    Are there any methods of selection (like jQuery), such that I could do something along the lines of...

    See above, MarkupM isn't a real monad. You could unravel the constructors to get to the correct AddAttributes, but again, that's an internal type and constructor.