How to avoid passing parameters everywhere in play2?
In my opinion, the fact that templates are statically typed is actually a good thing: you’re guaranteed that calling your template will not fail if it compiles.
However, it indeed adds some boilerplate on the calling sites. But you can reduce it (without losing static typing advantages).
In Scala, I see two ways to achieve it: through actions composition or by using implicit parameters. In Java I suggest using the Http.Context.args
map to store useful values and retrieve them from the templates without having to explicitly pass as templates parameters.
Using implicit parameters
Place the menus
parameter at the end of your main.scala.html
template parameters and mark it as “implicit”:
@(title: String)(content: Html)(implicit menus: Seq[Menu])
<html>
<head><title>@title</title></head>
<body>
<div>
@for(menu<-menus) {
<a href="#">@menu.name</a>
}
</div>
@content
</body>
</html>
Now if you have templates calling this main template, you can have the menus
parameter implicitly passed for you to the main
template by the Scala compiler if it is declared as an implicit parameter in these templates as well:
@()(implicit menus: Seq[Menu])
@main("SubPage") {
...
}
But if you want to have it implicitly passed from your controller you need to provide it as an implicit value, available in the scope from where you call the template. For instance, you can declare the following method in your controller:
implicit val menu: Seq[Menu] = Menu.findAll
Then in your actions you’ll be able to just write the following:
def index = Action {
Ok(views.html.index())
}
def index2 = Action {
Ok(views.html.index2())
}
You can find more information about this approach in this blog post and in this code sample.
Update: A nice blog post demonstrating this pattern has also been written here.
Using actions composition
Actually, it’s often useful to pass the RequestHeader
value to the templates (see e.g. this sample). This does not add so much boilerplate to your controller code because you can easily write actions receiving an implicit request value:
def index = Action { implicit request =>
Ok(views.html.index()) // The `request` value is implicitly passed by the compiler
}
So, since templates often receive at least this implicit parameter, you could replace it with a richer value containing e.g. your menus. You can do that by using the actions composition mechanism of Play 2.
To do that you have to define your Context
class, wrapping an underlying request:
case class Context(menus: Seq[Menu], request: Request[AnyContent])
extends WrappedRequest(request)
Then you can define the following ActionWithMenu
method:
def ActionWithMenu(f: Context => Result) = {
Action { request =>
f(Context(Menu.findAll, request))
}
}
Which can be used like this:
def index = ActionWithMenu { implicit context =>
Ok(views.html.index())
}
And you can take the context as an implicit parameter in your templates. E.g. for main.scala.html
:
@(title: String)(content: Html)(implicit context: Context)
<html><head><title>@title</title></head>
<body>
<div>
@for(menu <- context.menus) {
<a href="#">@menu.name</a>
}
</div>
@content
</body>
</html>
Using actions composition allows you to aggregate all the implicit values your templates require into a single value, but on the other hand you can lose some flexibility…
Using Http.Context (Java)
Since Java does not have Scala’s implicits mechanism or similar, if you want to avoid to explicitly pass templates parameters a possible way is to store them in the Http.Context
object which lives only for the duration of a request. This object contains an args
value of type Map<String, Object>
.
Thus, you can start by writing an interceptor, as explained in the documentation:
public class Menus extends Action.Simple {
public Result call(Http.Context ctx) throws Throwable {
ctx.args.put("menus", Menu.find.all());
return delegate.call(ctx);
}
public static List<Menu> current() {
return (List<Menu>)Http.Context.current().args.get("menus");
}
}
The static method is just a shorthand to retrieve the menus from the current context.
Then annotate your controller to be mixed with the Menus
action interceptor:
@With(Menus.class)
public class Application extends Controller {
// …
}
Finally, retrieve the menus
value from your templates as follows:
@(title: String)(content: Html)
<html>
<head><title>@title</title></head>
<body>
<div>
@for(menu <- Menus.current()) {
<a href="#">@menu.name</a>
}
</div>
@content
</body>
</html>
The way I do it, is to just create a new controller for my navigation/menu and call it from the view
So you can define your NavController
:
object NavController extends Controller {
private val navList = "Home" :: "About" :: "Contact" :: Nil
def nav = views.html.nav(navList)
}
nav.scala.html
@(navLinks: Seq[String])
@for(nav <- navLinks) {
<a href="#">@nav</a>
}
Then in my main view I can call that NavController
:
@(title: String)(content: Html)
<!DOCTYPE html>
<html>
<head>
<title>@title</title>
</head>
<body>
@NavController.nav
@content
</body>
</html>