Sunday, January 23, 2011

Who needs a Template Engine?

My wife commissioned me for another web app this weekend, and we spec'd it out using an app on her iPad (We both had a lot of fun). I wanted to prototype this sucker as quick as possible, and to be frank, my template engine I wrote for Scalatra wasn't cutting it. It dawned on me rather suddenly, something my former co-woker mentioned to me in passing.

"Scala supports in-line XML literals... Why not just go with that?"

And here I am almost a year later, saying to myself: Huh... you have an excellent point....

Why not, indeed. Using raw Scala as my template engine, I get some great benefits right out of the box, which I will elaborate a little later. First I want to address the big negative aspect.

Web Designers would hate me

If a web designer (non-Scala coder) had to build an interface using this method, they would probably shoot me, or want to anyway. The purpose of a web template is explained quite beautifully here. And while this new proposed method achieves this goal, but they'd have to compile Scala code to see changes. Ouch!

Let's take a look at what I mean, though. Let's just say, a designer was instructed to format a blog post. All they would have to do is this:

object BlogPost extends Template[Post] {
  def template(post: Post) = {
    <div class="post">
      <div class="post-header">
        <h1>{ post.title }</h1>
      </div>
      <div class="post-body">
        <p>
          { post.body }
        </p>
      </div>
    </div>
  }
}

Now in a Scalatra servlet, rendering this template is rather simple:

get("/single/:id") {
  val post = Post.get(params("id"))
  BlogPost(post)
}

Scala *is* the Template Engine

Why bother building functionality for conditionals and loops, when the language supports this already. All I had to do was build a simple inheritance system that makes it easy to wrap templates in templates (for headers and footers and such). I was amazed at some of the implicit benefits this granted me:

  1. I know if my template is valid at compile-time! Scala won't even compile invalid XML literals. Very cool.
  2. My templates can be Unit Tested! Not sure how useful this is yet, but it's possible :P
  3. I don't have to learn another web template language! Hoo-ray!

Now, I'll spend less time talking about this switch, and more time prototyping the app.

Just for kicks, let's see more of it in action:

object HomePage extends WrappedTemplate {
  val master = MasterTemplate 

  def wrapped(context: Context) = context match {
    case Context(_, posts: List[Post]) =>
    <div id="content">
      <div id="posts">
        { posts.map(BlogPost(_)) }
      </div>
    </div>
  }
}

object MasterTemplate extends ParentTemplate {
  // I don't really like this, but it works for now
  def template(data: (WrappedTemplate, Context)) = data._2 match {
    case Context(title: String, _*) =>
    <html>
      <head>
        <title>{ title }</title>
      </head>
      <body>
        { wrapped(data) }
      </body>
    </html>
  }
}

// In Scalatra servlet
...
get("/") {
  val posts = Post.find()
  HomePage(Context("title" -> "Home", "post" -> posts))
  // Optionally, use can use this shorthand
  // HomePage(NoKeyContext("Home", posts))
}

(FYI, I'm not prototyping a blog. Code here is strictly for example purposes.)

-- Philip Cali

2 comments:

  1. whoa, thanks for the props! And I must admit, it's pretty cool to get compile time type safety.

    So, here's another crazy idea. If you were working with a designer, could these template classes be run as a Scala script? Your Scala server could invoke the actual Scala executable from the command line, pass it the name of the template to invoke, the template's main (defined in a common super class?) is invoked and returns HTML that the original Scala server displays.

    This would only be useful in development, but, if it worked, the server wouldn't have to be restarted for template changes!

    I have no idea how you'd pass the request and session variables though. Maybe those should be some type of proxy back to the original server. Ok, this is getting out of hand.

    ReplyDelete
  2. Ha! A very interesting idea, Brad!

    There's a way to achieve this by doing a small amount of legwork with the remote control library in the last post.

    Basically, you could have a TemplateFactory singleton object that allows for a custom signals to be sent, something like register template or something. It could also do preregistration of known templates on server start up.

    The template designer could write a template to be registered, and simply compile and run from the sbt command line to see changes. (I envision templates for an app to be a child project of the web app. This way, when a designer makes a new template or changes an existing one, packaging and publishing the jar would allow the server to pick up the changes on start up.)

    This approach could still give template designers access to request and session variables that would be passed in the context of the template which is passed to the template factory.

    The servlet will always request a template from the template factory who may contain real-time changes from the template designer's template.

    Unfortunately, neither of our solutions would work on appengine, where this particular web app will live.

    ReplyDelete