This library allows contructing data structures in a similar style how
Play! Framework's JSON library allows building JSON,
which can then be directly passed to the
Play! module for Google Closure Templates for rendering.
The goal is to avoid passing template data as Map[String, Any]
and use the Scala compiler's support for detecting
mistakes.
Add the following to your build.sbt
file:
libraryDependencies += "com.kinja" %% "soy" % "4.0.1-soy-2016-08-09"
All data types extend a base trait called SoyValue
. The types described below are direct mappings to
Google Closure Templates data types.
This represents the null
value.
A regular string.
A regular boolean value of true
or false
.
An integer, represented by an underlying Java int
.
A double precision floating point number, represented by an underlying Java double
.
A sequence of SoyValue
, not necessarily the same types.
A set of name/value pairs. Names are strings but values can be any SoyValue
.
A pre-escaped HTML fragment. By using this you guarantee the contents are correct and safe.
A pre-escaped URI. By using this you guarantee the contents are correct and safe.
A pre-escaped CSS fragment. By using this you guarantee the contents are correct and safe.
A pre-escaped Javascript fragment or JSON data. By using this you guarantee the contents are correct and safe.
You can build lists and maps directly using SoyValue
constructors.
import com.kinja.soy._
SoyMap(Seq(
"users" -> SoyList(Seq(
SoyMap(Seq(
"name" -> SoyString("Bob"),
"age" -> SoyNumber(31),
"email" -> SoyString("[email protected]")
)),
SoyMap(Seq(
"name" -> SoyString("Kiki"),
"age" -> SoyNumber(25),
"email" -> SoyNull
))
))
))
There is an easier way to construct complex data structures using implicit conversions.
import com.kinja.soy._
Soy.map(
"users" -> Soy.list(
Soy.map(
"name" -> "Bob",
"age" -> 31,
"email" -> "[email protected]"
),
Soy.map(
"name" -> "Kiki",
"age" -> 25,
"email" -> SoyNull
)
)
)
You can define your own implicit serializers which can be used by the library to convert your classes to SoyValue
.
Creating such a serializer is as simple as extending the SoyWrites trait and implementing the toSoy
method.
import com.kinja.soy._
case class User(name: String, age: Int, email: Option[String])
class UserSoyWrites extends SoyWrites[User] {
def toSoy(user: User): SoyValue = Soy.map(
"name" -> user.name,
"age" -> user.age,
"email" -> user.email
)
}
For the library to be able to use your serializer, you need to make it implicitly available. Here's a more realistic
example which uses Play! 2.1 plugin for Google Closure Templates to
render the template views.users
in users.soy
by passing a SoyMap
in which the list of users is under the
key users
. The implicit UserSoyWrites
is used by the library to convert List[User]
to a SoyList
by converting
each user in it.
import com.kinja.soy._
import com.kinja.play.plugins.Closure
object App {
implicit val userSoyWrites = new UserSoyWrites
def render(users: List[User]): String = {
Closure.render("views.users", Soy.map("users" -> users))
}
}
The users.soy
template file may look something like this:
{namespace views}
/**
* A list of users
* @param users The list of users
*/
{template .users}
{foreach $user in $users}
<div class="user">
{$user.name} ({$user.age})
{if $user.email}<a href="mailto:{$user.email}">{$user.email}</a>{/if}
</div>
{ifempty}
<div class="message">
There are no users.
</div>
{/foreach}
{/template}
You can explicitly convert a value which has an implicit SoyWrites
available to SoyValue
using Soy.toSoy()
import com.kinja.soy._
object App {
implicit val userSoyWrites = new UserSoyWrites
val users: List[User] = List(User(...), ...)
val soyUsers: SoyValue = Soy.toSoy(users)
}