Skate is a project mainly written in Scala, based on the View license.
Simple XML/XHTML templates in Scala
Skate is a simple web templating library for the Scala programming language. It is heavily influenced by (if not entirely derived from) the design principles of Lift, but focuses exclusively on templating.
Skate doesn't try to be a framework and is is easy to integrate with other technologies. It requires no external libraries and needs virtually no configuration to set up. Skate works particularly well with Scalatra, but is simple enough that it can be plugged in almost anywhere.
Comments, issues and pull requests are welcome. If you find this useful, please let me know!
ElementHandler
is similar in concept to a JSP BodyTag or a Lift snippet. It is a function of the form (body:NodeSeq, attributes:MetaData) => replacementBody:NodeSeq
that is used to insert content in a template in place of a XML element.AttributeHandler
performs the same function for attributes. Its signature is (attribute:MetaData, parentElement:Elem) => replacementAttribute:MetaData
ElementHandlers
or AttributeHandlers
ElementHandler
; if it is an attribute, AttributeHandler
.ElementHandlers
that deal with common structural conditions such as including files, described below.Build the project using sbt:
$ sbt
> update
> package
This will produce a jar file named skate_2.8.0-1.0.jar
that you can add to your application. Next, set up a web application project using your favorite application server and development tools. Make sure that the jar file produced above makes it into the project classpath and /WEB-INF/lib
directory of your web app.
Create a template in the /WEB-INF/templates
directory of your web application, e.g. "hello.shtml":
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:s="urn:skate:1">
<body>
<s:example.Hello.hello>
<p>Hello, world. The time is <e:time/>.</p>
</s:example.Hello.hello>
</body>
</html>
Create an ElementHandler that generates its dynamic content, e.g. "Hello.scala":
package example
import java.util.Date
import scala.xml.Elem
import scala.xml.MetaData
import scala.xml.NodeSeq
import scala.xml.Text
import skate.Template
class Hello {
def hello(body:NodeSeq, atts:MetaData) = {
Template.replace(body, atts) {
case Elem("e", "time", _, _, _*) => Text(new Date().toString)
}
}
}
Notice how the name of package/class/method names in the code above correspond to the tag names used in step #1. You can have as many ElementHandlers
in a particular class as you wish.
The next step is to call Template.eval()
to render your templates. The above will evaluate to something like:
<html>
<body>
<p>Hello, world. The time is Sat Oct 30 12:24:11 EDT 2010</p>
</body>
</html>
With Scalatra, this can be rigged up as follows:
import skate.Template
import skate.scalatra.SkateSupport
class MyApp extends ScalatraServlet with SkateSupport {
get("*.shtml") {
Template.eval(requestPath)
}
}
The following basic ElementHandlers are included.
ignore
Enclosed content is removed from the template. For example:
<p>Blah, blah, blah.</p>
<s:ignore xmlns:s='urn:skate:1'>
<p>There once was a man from Nantucket ... </p>
</s:ignore>
<p>Lorem ipsum ... </p>
Results in:
<p>Blah, blah, blah.</p>
<p>Lorem ipsum ... </p>
children
Enclosed content is returned verbatim. This is useful primarily in situations where a "container" is needed to create valid XML/XHTML. For example:
<s:children xmlns:s='urn:skate:1'>
<p>Foo, bar, baz ... </p>
</s:children>
Results in:
<p>Foo, bar, baz ... </p>
include
Inserts one template inside another. The included template will be evaluated. The value of the name parameter should be recognizable to Template.eval(). For example:
<div>
<s:include name="foo.shtml" xmlns:s='urn:skate:1'/>
</div>
Where "foo.shtml" contains:
<s:children xmlns:s='urn:skate:1'>
<p>Lorem ipsum ...</p>
<p>Etc.</p>
</s:children>
Results in:
<div>
<p>Lorem ipsum ...</p>
<p>Etc.</p>
</div>
bind/surround/bind-at
Inserts content inside a template a pre-defined points. Consider the following template (layout.shtml) that defines a very general page structure:
<html xmlns:s='urn:skate:1'>
<body>
<div id="header"><s:bind name="headerContent"/></div>
<div id="main"><s:bind name="mainContent"/></div>
<div id="footer"><s:bind name="footerContent"/></div>
</body>
</html>
When used with surround
, the referenced template will be included into the current template, and the bind
elements will be replaced with content supplied in corresponding bind-at
elements. For example:
<s:surround name="layout.shtml" xmlns:s='urn:skate:1'>
<s:bind-at name="headerContent">
... Header content here ...
</s:bind-at>
<s:bind-at name="mainContent">
... main here ...
</s:bind-at>
<s:bind-at name="footerContent">
... Footer content here ...
</s:bind-at>
</s:surround>
And will result in the following page:
<html>
<body>
<div id="header">
... Header content here ...
</div>
<div id="main">
... main content here ...
</div>
<div id="footer">
... Footer content here ...
</div>
</body>
</html>
First, create a prototype row that contains placeholder elements for actual data values. Pass this into an ElementHandler
that will substitute actual data values.
<table>
<tr>
<th>Name</th>
<th>Catchphrase</th>
</tr>
<t:example.Iteration.row>
<tr>
<td><e:name/></td>
<td><e:catchphrase/></td>
</tr>
</t:example.Iteration.row>
</table>
In the ElementHandler
, iterate over the data set with flatMap
, replacing the placeholder elements in the body with actual data values for each row in the table:
package example
class Iteration {
def row(body:NodeSeq, atts:MetaData):NodeSeq = {
val data = Map("Fred Flintstone" -> "Yabba Dabba Doo!",
"Homer Simpson" -> "Doh!",
"Stewie Griffin" -> "What the Deuce?")
data.toSeq.flatMap {
x => Template.replace(body, atts) {
case Elem(_, "name", _, _, _*) => Text(x._1)
case Elem(_, "catchphrase", _, _, _*) => Text(x._2)
}
}
}
}
And the output will look like:
<table>
<tr>
<th>Name</th>
<th>Catchphrase</th>
</tr>
<tr>
<td>Fred Flintsone</td>
<td>Yabba Dabba Doo!</td>
</tr>
<tr>
<td>Home Simpson</td>
<td>Doh!</td>
</tr>
<tr>
<td>Stewie Griffin</td>
<td>What the Deuce?</td>
</tr>
</table>
Simple conditionals (a la JSTL c:if
) can be implemented as a function that discards the body content by returning NodeSeq.Empty
if the condition is not satisfied. For example:
<s:example.Conditional.simple>
<p>Include this only if some condition is true</p>
</s:example.Conditional.simple>
And in the ElementHandler
:
package example
class Conditional {
def simple(body:NodeSeq, atts:MetaData):NodeSeq = {
val test:Boolean = doSomeLogic()
if (test) body
else NodeSeq.Empty
}
}
More complex structures like JSTL c:choose/c:when/c:otherwise
can be implemented by putting each branch as a separate element in the body content.
<p>
<s:example.Conditional.choose>
<e:foo> ... foo content here ... </e:foo>
<e:bar> ... bar content here ... </e:bar>
<e:otherwise> ... default content here ... </e:otherwise>
</s:example.Conditional.choose>
</p>
In the element handler, test which branch to return and select it from the body content, discarding the other pieces:
package example
class Conditional {
def doSomeLogic:Option[String] = {
// run your test here, returning the branch
// to be selected as an Option[String]; use
// None to indicate the "otherwise" case ...
}
def choose(body:NodeSeq, atts:MetaData):NodeSeq = {
val test = doSomeLogic.getOrElse("otherwise")
body.find(e => e.label == test).map(e => e.child).getOrElse(throw new Exception("No branch for " + test))
}
}
Assuming that the doSomeLogic
method returns Some("foo")
the tag will render:
<p>
... foo content here ...
</p>