Wednesday, April 13, 2011

Views, Controllers, and Heist

A very common question when using Heist is how to specify views in a splice. There are three different approaches one can take:

1. Specify a splice's view in the Haskell code implementing the splice.
2. Get a splice's view from a template.
3. Pass the desired view to the splice as the spliced tag's child nodes.

Aproach #1 tends to be ugly and violates the principle of separating logic and view.  There are always exceptions to every rule, so we've done a little bit to ease the implementation of #1 by allowing xmlhtml DOM structures to be built with blaze-html syntax.  However, I believe that one of the other approaches will usually be preferable.

Approach #2 seems kind of awkward.  It would result in a whole bunch of small templates on disk that define little pieces of web pages.  An architecture like this might be desireable in some situations as a way to eliminate repetition (and it certainly fits into the functional programming mentality of lots of small functions), but it is quite different from the page-oriented structure most designers are used to.

Approach #3 is our recommended pattern.  It allows you to keep your view completely in the template and the logic completely in the splice.  Here's an example of how this might work.  Let's use the simple example of displaying blog posts.  We want to create a <post> splice.

data Post = Post
  { postTitle :: Text
  , postBody :: Text
  }

postSplice = do
  post <- getPostFromDatabase
  runChildrenWithText [("postTitle", postTitle post)
                      ,("postBody", postBody post)]

Then we might use it in a template like this:

<post>
  <h1><postTitle/></h1>
  <div><postBody/></div>
</post>

The template is basically saying, "Give me a post, but here's how I want it to look."  The splice is only responsible for getting the appropriate data and passing it back to the template as raw text.

The function runChildrenWithText makes this more convenient.  It binds a list of constant text splices before getting the children from the param node, "running" them to expand all referenced splices, and returning the result. After the child nodes are run, the newly bound splices are unbound. They only exist in the scope of the child nodes.

This model provides a nice division of labor between programmers and designers. The programmers build a library of splices that provide access to dynamic site data. This creates a domain-specific markup language with syntax that designers are already familiar with. It's similar to what Facebook has done with FBML. And I have found that from my perspective as a Haskell programmer, it even makes writing HTML kind of fun.

2 comments:

Anonymous said...

Maybe I'm not understanding the example you give but doesn't bindStrings do this already ?

I thought runChildren might take a list of posts and apply the template for every post in the list and concatenate the output ready to be included as a bind in another template ?

Andrew

mightybyte said...

runChildrenWithText is similar to bindStrings, but it does a little more. It is a combination of localTS, bindStrings, and runChildren. The main point is that it runs the children of the param node, which is what allows you to pass in the desired view.