在我看来,模板是静态类型的这一事实实际上是一件好事:你可以保证调用你的模板在编译时不会失败。
但是,它确实在调用站点上添加了一些样板。但是您可以减少它(不会失去静态类型的优势)。
在 Scala 中,我看到了两种实现方式:通过动作组合或使用隐式参数。在 Java 中,我建议使用Http.Context.args
映射来存储有用的值并从模板中检索它们,而不必显式地作为模板参数传递。
使用隐式参数
将menus
参数放在main.scala.html
模板参数的末尾并将其标记为“隐式”:
@(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>
现在,如果你有调用这个主模板的模板,你可以让Scala 编译器menus
为你隐式传递给main
模板的参数,前提是它在这些模板中也被声明为隐式参数:
@()(implicit menus: Seq[Menu])
@main("SubPage") {
...
}
但是如果你想让它从你的控制器隐式传递,你需要提供它作为一个隐式值,在你调用模板的范围内可用。例如,您可以在控制器中声明以下方法:
implicit val menu: Seq[Menu] = Menu.findAll
然后在您的操作中,您将能够编写以下内容:
def index = Action {
Ok(views.html.index())
}
def index2 = Action {
Ok(views.html.index2())
}
您可以在此博客文章和此代码示例中找到有关此方法的更多信息。
更新:这里也写了一篇很好的博客文章来展示这种模式。
使用动作组合
RequestHeader
实际上,将值传递给模板通常很有用(参见例如这个示例)。这不会为您的控制器代码添加太多样板,因为您可以轻松编写接收隐式请求值的操作:
def index = Action { implicit request =>
Ok(views.html.index()) // The `request` value is implicitly passed by the compiler
}
因此,由于模板通常至少接收此隐式参数,因此您可以将其替换为包含例如您的菜单的更丰富的值。您可以通过使用Play 2的动作组合机制来做到这一点。
为此,您必须定义您的Context
类,包装一个底层请求:
case class Context(menus: Seq[Menu], request: Request[AnyContent])
extends WrappedRequest(request)
然后您可以定义以下ActionWithMenu
方法:
def ActionWithMenu(f: Context => Result) = {
Action { request =>
f(Context(Menu.findAll, request))
}
}
可以这样使用:
def index = ActionWithMenu { implicit context =>
Ok(views.html.index())
}
您可以将上下文作为模板中的隐式参数。例如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>
使用动作组合可以让您将模板所需的所有隐式值聚合为一个值,但另一方面您可能会失去一些灵活性……</p>
使用 Http.Context (Java)
由于 Java 没有 Scala 的隐式机制或类似机制,如果您想避免显式传递模板参数,一种可能的方法是将它们存储在Http.Context
仅在请求期间存在的对象中。该对象包含一个args
类型的值Map<String, Object>
。
因此,您可以从编写拦截器开始,如文档中所述:
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");
}
}
静态方法只是从当前上下文中检索菜单的简写。然后注释您的控制器以与Menus
动作拦截器混合:
@With(Menus.class)
public class Application extends Controller {
// …
}
最后,menus
从模板中检索值,如下所示:
@(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>