HTML Conditional Comments with blaze-html

2011-05-20

Why

A couple of days ago I ran across a really neat boilerplate for mobile-friendly development called Skeleton. This seemed great, and because I do my web development in Haskell and use Jasper’s excellent blaze-html, I wanted the index.html coded in Haskell.

No problem, right? Wrong.

As soon as you look at the index.html file, these top lines raise concern:

1<!--[if lt IE 7 ]><html class="ie ie6" lang="en"> <![endif]--> 2<!--[if IE 7 ]><html class="ie ie7" lang="en"> <![endif]--> 3<!--[if IE 8 ]><html class="ie ie8" lang="en"> <![endif]--> 4<!--[if (gte IE 9)|!(IE)]><html lang="en"><![endif]-->

Can blaze-html do comments like that, or more complex still, are its combinators capable of closing the final if statement but leaving the HTML tag unclosed?

How

The answer to this is yes, although I admit my solution is kind of hackish. As I got farther into this, I began to realize that perhaps I should have just hard-coded these if statements with preEscapedText and moved on with my life, but it was too late at this point. I was already deep in Text.Blaze.Internal’s source.

The first realization is that, due to the nature of the combinators, I had to define my own html and body tags that would allow me to:

  • Open the html tag without closing it
  • Open the body tag but close both it and html

This resulted in:

1{- These imports will serve us for the remainder of the post -} 2import Text.Blaze.Internal 3import Text.Blaze.Html5 4import Text.Blaze.Html5.Attributes 5import qualified Data.ByteString.Char8 as C8 6import qualified Data.Text as T 7 8-- | HTML tag with no closing. 9html' :: Html -> Html 10html' = Parent "html" "<html" "" 11 12-- | Special tag that opens only body but closes both body and html. 13htmlBody :: Html -> Html 14htmlBody = Parent "body" "<body" "</body></html>"

Here, Parent is used to define the opening and closing tags. Note that the missing &gt; is intentional in the opening string: blaze adds it automatically after appending any attributes.

Next came actually allowing for the creation of the comments. This was actually quite simple after StaticString/OverloadedStrings were understood.

1-- | Comment combinator. `if` is included, so only condition is needed. E.g., 2-- 3-- > comment "lt IE 7" 4comment :: String -> Html -> Html 5comment cond = Parent "comment" (ss cond') "<![endif]-->" 6 where cond' = "<!--[if " ++ cond ++ " ]" 7 ss s = StaticString (s ++) (C8.pack s) (T.pack s)

comment is very similar to html' and htmlBody from above, but with one key difference: the addition of ss. This is needed to repack the StaticString that blaze uses for efficiency, which is typically created automagically via the OverloadedStrings language pragma.

Usage

Usage is actually quite simple once the combinators are setup, which was of course my goal all along. First, I created htmlTag, a function with the specific task of outputting just the conditional HTML tags:

1-- | Special set of conditionals for the HTML tag to make Skeleton compatible 2-- with all browsers. 3htmlTag :: Html 4htmlTag = do 5 comment "lt IE 7" $ htmlEn ! class_ "ie ie6" 6 comment "IE 7" $ htmlEn ! class_ "ie ie7" 7 comment "IE 8" $ htmlEn ! class_ "ie ie8" 8 comment "(gte IE 9)|!(IE)" $ htmlEn 9 where htmlEn = html' ! lang "en" $ ""

I was then able to use this effortlessly in the skeletonBase code that actually output the template:

1skeletonBase :: String -> String -> Html -> AttributeValue -> AttributeValue 2 -> Html -> Html 3skeletonBase cssDir iconDir title desc author content = do 4 docType 5 htmlTag 6 htmlBody $ do 7 -- ...

And at last, we have HTML conditional comments in blaze!

Notes

As mentioned before, I know this seems a bit hackish, but it feels less hackish (and more fun!) than the alternative of just shoving a hard-coded string into the template via preEscapedText. Plus, it was a great reason to get to know blaze’s internals a bit better.

If you can think of a better way to do this, please do let me know.

← all posts

michael schade

I like learning new things. Previously: Kenchi founder, eng & ops teams at Stripe from 2012-2019. Say hi! 🏳️‍🌈