This got me thinking, since Scala supports XML literals, there should be some way to take advantage of them for templating. I had considered it before but had dismissed the idea because:
- It required some sort of compilation unit (class, object) for each template.
- It was not designer friendly (see above).
- I wasn't familiar enough with Scala to really try it.
So I gave it a try and I am REALLY surprised at how well it works. And you get TONs of benefits: Compile time checks, auto-complete in the IDE, full power of scala, familiar syntax, etc.
Layouts
Layouts were the first thing I needed. A way to create a widget that takes parameters (like html attributes) and that wraps around template code. So I started with an object to hold my layout templates. One method for each template. We can also leverage default values, multiple parameter lists, and by-name parameters to make the syntax for the client code extra nice:
object Templates {
// a page template
def page (title:String="Default title")(content: => xml.Elem) = {
<html>
<head>
<title>{title}</title>
</head>
<body>
{body}
</body>
</html>
}
// a panel template, just as an example
def panel (label:String="Some label")(content: => xml.Elem) = {
<div class="panel">
<div class="panel-label">{label}</div>
<div>{content}</div>
</div>
}
}
[Note: As you can see, the syntax highlighter in this blog is unfortunately not as sophisticated as the scala compiler when it comes to XML literals]
Now you can use these directly on your controllers:
import util.Templates._
object Application extends Controller {
def index =
page(title="Welcome to my Page!") {
<div>
<h1>Hello</h1>
<p>Some template markup</p>
{panel(label="Dashboard panel")(
<div>
Panel content
</div>
)}
</div>
}
}
Loops and Ifs
Within your XML, you can use "for" and "if" as you would in plain scala:
...
<div>
{for (user <- users if user.isAdmin) yield
<div>
{user.name}
{if (user.isAvailable)
<img src="available.png"/>
else
<img src="unavailable.png"/>
}
</div>
}
</div>
...
Intra-Application Links
Play! comes with a really cool way to go from a method-call-to-controller to a url (reverse routing), but there's no equivalent way to do that from scala. But it turns out it is really easy to create a method that will do that for you. I'll put it in a Utils object I can import on my pages:
import play.mvc.results._
object Utils {
def Url (action: => Any) = new ScalaAction(action).actionDefinition.url
}
We can now use it like this:
import util.Utils._ // import Url method and others
object Application extends Controller {
def users =
page(title="Users page") {
<div>
<a href={Url( Application.user(24) )}>Click to see user page</a>
</div>
}
def user (userId:Int) = {
...
}
}
As you can see, XML literals can work great as the templating solution for a web app.
Hi Alvaro,
ReplyDeleteAre you still using this approach 6 months after you posted?
I assumed that Scala's built-in Xml handling would mean Scala does not need a templating language. But this view does not seem to be shared by other people posting about the Play Framework or on the Play mailing lists. Normally when I'm in an extreme minority it means I'm wrong.
So I'm interested in how its worked out for you.
Nick,
ReplyDeleteI am still using this approach now and I am still amazed that it is not more common. I have found a couple of issues, such as you really can't have embedded scripts or styles because certain characters will be escaped even when you use CDATA. But generally it is a best practice to keep those in separate files anyway, so it hasn't really been a problem for me.
No other issues so far, and there are a LOT of benefits.
How are you dealing with the mismatch between the version of Scala that Play wants (2.8.1) and the version that Squeryl (2.9.1) wants? Are you using the Play 2.0 code or the 1.x code that can't move past Scala 2.8.1?
ReplyDelete@Nick - Whoa, just barely realized saw your comment, sorry for the delay. For posterity sake, I am still using play 1.2.x and using squeryl compiled for 2.8.1
ReplyDelete