题
在play1中,我通常在actions中获取所有数据,直接在视图中使用它们。由于我们不需要在视图中显式声明参数,因此这非常容易。
但是在play2中,我发现我们必须声明所有参数(包括 request
)在视图的头部,获取action中的所有数据并将它们传递到视图中会非常无聊。
例如,如果我需要在首页显示从数据库加载的菜单,我必须在中定义它 main.scala.html
:
@(title: String, menus: Seq[Menu])(content: Html)
<html><head><title>@title</title></head>
<body>
<div>
@for(menu<-menus) {
<a href="#">@menu.name</a>
}
</div>
@content
</body></html>
然后我必须在每个子页面中声明它:
@(menus: Seq[Menu])
@main("SubPage", menus) {
...
}
然后我必须获取菜单并将其传递给每个操作中的查看:
def index = Action {
val menus = Menu.findAll()
Ok(views.html.index(menus))
}
def index2 = Action {
val menus = Menu.findAll()
Ok(views.html.index2(menus))
}
def index3 = Action {
val menus = Menu.findAll()
Ok(views.html.index(menus3))
}
目前它只是一个参数 main.scala.html
, ,如果有很多呢?
所以最后我决定全部 Menu.findAll()
直接查看:
@(title: String)(content: Html)
<html><head><title>@title</title></head>
<body>
<div>
@for(menu<-Menu.findAll()) {
<a href="#">@menu.name</a>
}
</div>
@content
</body></html>
不知道好不好,有什么好的解决办法吗?
解决方案
在我看来,模板是静态类型的这一事实实际上是 好的 事物:如果你的模板编译通过,你可以保证调用它不会失败。
然而,它确实在调用站点上添加了一些样板。但 你可以减少它 (不失去静态类型优势)。
在 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>
现在,如果您有调用此主模板的模板,则可以使用 menus
参数隐式传递给你 main
如果 Scala 编译器也在这些模板中将模板声明为隐式参数:
@()(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>
使用操作组合允许您将模板所需的所有隐式值聚合为单个值,但另一方面您可能会失去一些灵活性......
使用 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>
其他提示
我这样做的方法是为我的导航/菜单创建一个新的控制器并从视图中调用它
所以你可以定义你的 NavController
:
object NavController extends Controller {
private val navList = "Home" :: "About" :: "Contact" :: Nil
def nav = views.html.nav(navList)
}
导航.scala.html
@(navLinks: Seq[String])
@for(nav <- navLinks) {
<a href="#">@nav</a>
}
然后在我的主视图中我可以称之为 NavController
:
@(title: String)(content: Html)
<!DOCTYPE html>
<html>
<head>
<title>@title</title>
</head>
<body>
@NavController.nav
@content
</body>
</html>
我支持斯蒂安的回答。这是获得结果的非常快速的方法。
我刚刚从 Java+Play1.0 迁移到 Java+Play2.0,模板是迄今为止最难的部分,我发现实现基本模板(用于标题、头部等)的最佳方法是使用 Http 。语境。
您可以使用标签实现非常好的语法。
views
|
\--- tags
|
\------context
|
\-----get.scala.html
\-----set.scala.html
其中 get.scala.html 是:
@(key:String)
@{play.mvc.Http.Context.current().args.get(key)}
set.scala.html 是:
@(key:String,value:AnyRef)
@{play.mvc.Http.Context.current().args.put(key,value)}
意味着你可以在任何模板中编写以下内容
@import tags._
@context.set("myKey","myValue")
@context.get("myKey")
所以它非常可读而且很好。
这就是我选择走的路。斯蒂安 - 好建议。事实证明向下滚动查看所有答案很重要。:)
传递 HTML 变量
我还没弄清楚如何传递 Html 变量。
@(标题:字符串,内容:Html)
但是,我知道如何将它们作为块传递。
@(标题:字符串)(内容:Html)
所以你可能想将 set.scala.html 替换为
@(key:String)(value:AnyRef)
@{play.mvc.Http.Context.current().args.put(key,value)}
这样你就可以像这样传递 Html 块
@context.set("head"){
<meta description="something here"/>
@callSomeFunction(withParameter)
}
编辑:我的“设置”实施的副作用
Play 中的一个常见用例是模板继承。
您有一个base_template.html,然后您有一个扩展base_template.html 的page_template.html。
base_template.html 可能看起来像
<html>
<head>
<title> @context.get("title")</title>
</head>
<body>
@context.get("body")
</body>
</html>
而页面模板可能看起来像
@context.set("body){
some page common context here..
@context.get("body")
}
@base_template()
然后你有一个页面(假设login_page.html)看起来像
@context.set("title"){login}
@context.set("body"){
login stuff..
}
@page_template()
这里要注意的重要一点是您将“body”设置了两次。一次进入“login_page.html”,然后进入“page_template.html”。
只要您像我上面建议的那样实现 set.scala.html ,这似乎就会触发副作用。
@{play.mvc.Http.Context.current().put(key,value)}
因为页面会显示“登录内容...”两次,因为 put 返回我们第二次输入相同密钥时弹出的值。(请参阅 java 文档中的 put 签名)。
scala提供了更好的修改地图的方法
@{play.mvc.Http.Context.current().args(key)=value}
这不会导致这种副作用。
如果您使用 Java 并且只想要最简单的方法,而无需编写拦截器并使用 @With 注释,您还可以直接从模板访问 HTTP 上下文。
例如。如果您需要模板中可用的变量,您可以使用以下命令将其添加到 HTTP 上下文中:
Http.Context.current().args.put("menus", menus)
然后您可以通过以下方式从模板访问它:
@Http.Context.current().args.get("menus").asInstanceOf[List<Menu>]
显然,如果您在方法中使用 Http.Context.current().args.put("","") ,那么您最好使用拦截器,但对于简单的情况,它可能会起作用。
从斯蒂安的回答中,我尝试了一种不同的方法。这对我有用。
在Java代码中
import play.mvc.Http.Context;
Context.current().args.put("isRegisterDone", isRegisterDone);
在 HTML 模板头中
@import Http.Context
@isOk = @{ Option(Context.current().args.get("isOk")).getOrElse(false).asInstanceOf[Boolean] }
并使用类似
@if(isOk) {
<div>OK</div>
}