Composable template layouts with Happstack & HSP
Since I just went through a very difficult experience trying to get this working, now that I have it I'm sharing the solution.
The Problem
In any civilized web framework you can extract your generic, repeated markup into layout files. In better ones you can even compose your templates into your layout. For a Rails developer this is done using yield
/content_for
.
In my latest attempt to learn Haskell, I've started playing with the web framework Happstack. I'm trying out the templating library "HSP", which lets me embed literal XML right in with my Haskell code. Why would I want that? If you're curious, I'd be happy to talk about it more, but this post isn't the place for that explanation.
So, I'm learning Happstack, implementing HSP, the Happstack guide briefly touches on a function called defaultTemplate
. It's what I'm looking for, but I want to use my own markup.
The Solution
Since I care so much about you, dear reader, I'll save you the frustrating details and cut to the solution (feel free to share in my newbtastic difficulties on reddit). If you want to implement your own layout function you can use the following snippet:
template :: (EmbedAsChild WebPage headers, EmbedAsChild WebPage body) => String -> headers -> body -> ServerPart XML
template title headers body = unHSPT $ (unXMLGenT
<html>
<head>
<title>A Page</title>
<% headers %>
</head>
<body>
<% body %>
</body>
</html> :: WebPage XML)
Now, I found similar examples to this online when I was starting, but of course, I couldn't find examples of using it. So, just for you, here is how I'm using it:
homePage :: ServerPart Response
homePage =
ok . toResponse =<< template "Home"
()
<div>
<h1>Welcome to the Happstack CMS!</h1>
<p>This is a demo CMS written in Happstack. Cool stuff, eh?</p>
<a href="/announcements">Announcements</a>
</div>
Note that homePage is a route in my app.
Now, this was great, but it also means that every single template I render must be composed with ok . toResponse
. That can get annoying. So let's take it one step further:
renderTemplate :: String -> headers -> body -> ServerPart Response
renderTemplate title headers body =
ok . toResponse =<< (template title headers body)
Now the route can be a little simpler:
homePage :: ServerPart Response
homePage =
renderTemplate "Home"
()
<div>
<h1>Welcome to the Happstack CMS!</h1>
<p>This is a demo CMS written in Happstack. Cool stuff, eh?</p>
<a href="/announcements">Announcements</a>
</div>
There we go! I don't have time right now to write up a detailed explanation of how it works. If you want to know, check out @stepcut251's comment thread on my reddit post. Special thanks to @stepcut251 for his help figuring this out!
Now hopefully I've given you the help that I couldn't find when I was struggling. Score 1 for the Haskell community.