Glitter is an embedded DSL allowing to write XML in a concise, type safe and composable way.
See this gist for a quick overview of what Glitter is. The first file defines some reusable HTML fragments and the two following files show the results of rendering them.
Download the latest jar from the downloads section and add it to your classpath.
You can use it in your sbt project by adding this line in the build.sbt
file (adapt it to the latest release version):
libraryDependencies += "glitter" % "glitter" % "0.1" from "file:///where/you/downloaded/glitter-0.1.jar"
Fetch the sources by cloning this repository:
git clone git://github.com/julienrf/glitter.git
Glitter is defined as a sbt project. You can run the tests using the sbt test
command, or run the integrated bigtable benchmark with sbt run
.
To use Glitter you first need to import the definitions (and implicits) of the glitter
package object:
import glitter._
Then write your templates, e.g. in an object:
object Templates {
def index: Xml = …
}
To write a tag, use a symbol:
scala> def index: Xml = 'div
index: glitter.Xml
If you try to render this template, you will get the <div />
string:
scala> index.render
res0: String = <div />
Under the hood it uses an implicit conversion from Symbol
to the EmptyTag
case class. You need to explicit the return type of your template to make it work.
You can also write plain text, using a String
:
scala> def index: Xml = "foo"
index: glitter.Xml
scala> index.render
res1: String = foo
Content can be nested using the ::
operator:
scala> def index = 'div :: "foo"
index: glitter.Tag
scala> index.render
res2: String = <div>foo</div>
You can also write a sequence of xml content, just separate each element (tag or text) with |
:
scala> def index = 'span :: "foo" | 'span :: "bar"
index: glitter.Xml
scala> index.render
res3: String = <span>foo</span><span>bar</span>
As you can see, the ::
operator has a higher precedence than the |
operator. If you want to nest a sequence of elements in a tag you need to enclose them in brackets:
scala> def index = 'div :: ('span :: "foo" | 'span :: "bar")
index: glitter.Tag
scala> index.render
res4: String = <div><span>foo</span><span>bar</span></div>
Actually you can also nest tags using parenthesis, so the previous example is the same as the following:
scala> def index = 'div ('span :: "foo" | 'span :: "bar")
index: glitter.Tag
scala> index.render
res5: String = <div><span>foo</span><span>bar</span></div>
You can add attributes to the tags using the %
operator:
scala> def img = 'img %('src->"/static/images/my_cat.jpg", 'alt->"picture of my cat", 'title->"My Cat")
img: glitter.EmptyTag
scala> img.render
res4: String = <img src="/static/images/my_cat.jpg" alt="picture of my cat" title="My Cat" />
The %
operator takes one or more tuples as parameters (it can be tuples of (String, String)
or (Symbol, String)
). You can use the Scala built-in syntax to define such tuples, like in the above example, but you can also use the Glitter ~
operator to define them, so the above example is equivalent to the following definitions:
def img = 'img % 'src~"/static/images/my_cat.jpg" % 'alt~"picture of my cat" % 'title~"My Cat"
def img = 'img % ('src~"/static/images/my_cat.jpg", 'alt~"picture of my cat", 'title~"My Cat")
def img = 'img % (('src,"/static/images/my_cat.jpg"), ('alt,"picture of my cat"), ('title,"My Cat"))
For convenience, Glitter allows to add attributes with no value:
scala> def radio = 'input % 'type~"radio" % 'checked
radio: glitter.EmptyTag
scala> radio.render
res9: String = <input type="radio" checked />
It can be useful to write HTML5 markup.
Since Glitter templates are nothing but Scala functions, you can use function composition to have reusable templates fragments:
import glitter._
object Layouts {
// Main layout def main(content: Xml): Xml = html5dtd | 'html % 'lang~"en" :: ( 'head :: 'title :: "Title" | 'body ( _header | _content(content) | _footer ) )
// Reusable header fragment def _header: Xml = 'header ( 'h1 :: 'a % 'href~"/" :: "Home" )
// Reusable footer fragment def _footer: Xml = 'footer :: "© 2011"
// Reusable content fragment def _content(content: Xml): Xml = 'section :: content }
object Templates {
// A template “extending” the main layout def index: Xml = Layout.main( 'h2 :: "Index" | 'p :: "…" ) }
I’m used to prefix templates fragments by an underscore, but that’s not mandatory at all. So, here you see how you can simulate what’s usually called template inheritance (the index
template “extends” the main
template layout) and template inclusion (main
layout “includes” the fragments _header
, _footer
and _content
).
You can use the Scala if
directly:
def _userInfo(user: User): Xml =
'div :: (if (user.isAdmin) "foo" else "bar")
To iterate through a collection (or any monadic object) you can use the monad comprehension (a.k.a for comprehension) notation:
def listArticles(articles: List[Article]): Xml =
'ul :: (
for (article <- articles) yield ('li :: article.name)
)
You can render any Xml
content by using its render(r: Renderer)
function. By default this function will use an implicit simple renderer writing the content into a string, as HTML (without any kind of formatting, empty attributes have no value).
By default string converted to xml text are HTML escaped (e.g. “<” are transformed into “<”). You can disable this behavior by using the raw
method:
def myHtml5dtd: Xml = "<!DOCTYPE html>\n".raw
Glitter provides an object of type Xml
but doing nothing: Empty
. It may be useful in some cases, e.g.:
def _adminArea(user: User): Xml =
if (user.isAdmin)
'a % 'href~"/admin" :: "Manage"
else
Empty