如何避免在 play2中到处传递参数?

在 play1中,我通常在操作中获取所有数据,直接在视图中使用它们。因为我们不需要在 view 中显式声明参数,所以这非常简单。

但是在 play2中,我发现我们必须在视图的头部声明所有的参数(包括 request) ,将所有的数据在操作中获取并传递到视图中是非常无聊的。

例如,如果我需要在首页显示从数据库加载的菜单,我必须在 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中的一个参数,如果有很多参数怎么办?

So at last, I decided to all Menu.findAll() directly in view:

@(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>

我不知道它是好的还是推荐的,有没有更好的解决方案?

41681 次浏览

在我看来,模板是静态类型的这一事实实际上是一个 很好的东西: 您可以保证调用模板在编译时不会失败。

但是,它确实在调用站点上添加了一些样板,但是 你可以减少它(不失去静态类型的优势)。

在 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())
}

您可以在 这篇博文此代码示例中找到关于这种方法的更多信息。

Update: A nice blog post demonstrating this pattern has also been written 给你.

Using actions composition

实际上,将 RequestHeader值传递给模板通常很有用(参见例如 这个样本)。这并不会给控制器代码添加太多的样板,因为您可以轻松地编写接收隐式请求值的操作:

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.

为此,您必须定义 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)

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>.

因此,您可以从编写一个拦截器开始,如 文件中所解释的:

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>

The way I do it, is to just create a new controller for my navigation/menu and call it from the view

所以你可以定义你的 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>
}

那么在我看来,我可以称之为 NavController:

@(title: String)(content: Html)
<!DOCTYPE html>
<html>
<head>
<title>@title</title>
</head>
<body>
@NavController.nav
@content
</body>
</html>

如果您正在使用 Java,并且只想要最简单的方式,而不需要编写拦截器和使用@With 注释,那么您还可以直接从模板访问 HTTP 上下文。

例如,如果你需要一个可用的变量从一个模板,你可以添加到 HTTP 上下文与:

Http.Context.current().args.put("menus", menus)

然后,您可以通过以下方式从模板访问它:

@Http.Context.current().args.get("menus").asInstanceOf[List<Menu>]

显然,如果您使用 Http 来丢弃您的方法。Current ().Put (“”,“”)您最好使用拦截器,但是对于简单的情况,它可能会起作用。

我支持斯蒂安的回答,这是一个快速得到结果的方法。

我刚从 Java + Play1.0迁移到 Java + Play2.0,模板是目前为止最难的部分,也是我找到的实现基本模板(如 title、 head 等)的最佳方法就是使用 Http。背景。

使用标记可以实现一种非常好的语法。

views
|
\--- tags
|
\------context
|
\-----get.scala.html
\-----set.scala.html

其中 get.scala.html 是:

@(key:String)
@{play.mvc.Http.Context.current().args.get(key)}

and set.scala.html is:

@(key:String,value:AnyRef)
@{play.mvc.Http.Context.current().args.put(key,value)}

意味着您可以在任何模板中编写以下内容

@import tags._
@context.set("myKey","myValue")
@context.get("myKey")

So it is very readable and nice.

This is the way I chose to go. stian - good advice. Proves it is important to scroll down to see all answers. :)

传递 HTML 变量

我还没有弄清楚如何传递 Html 变量。

@ (title: String,content: Html)

however, I know how to pass them as block.

@ (title: String)(content: Html)

所以您可能需要将 set.scala.html 替换为

@(key:String)(value:AnyRef)
@{play.mvc.Http.Context.current().args.put(key,value)}

this way you can pass Html blocks like so

@context.set("head"){
<meta description="something here"/>
@callSomeFunction(withParameter)
}

编辑: 我的“集合”实现的副作用

Play 中常见的用例 it 模板继承。

有一个 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>

while page template might look something like

@context.set("body){
some page common context here..
@context.get("body")
}
@base_template()

and then you have a page (lets assume login_page.html) that looks like

@context.set("title"){login}
@context.set("body"){
login stuff..
}


@page_template()

The important thing to note here is that you set "body" twice. Once in "login_page.html" and then in "page_template.html".

似乎这会触发一个副作用,只要像我上面建议的那样实现 set.scala.html。

@{play.mvc.Http.Context.current().put(key,value)}

因为 put 将返回第二次输入相同键时弹出的值,所以页面将显示两次“ login stuff...”。(请参阅 java 文档中的 put 签名)。

Scala 提供了更好的方法来修改映射

@{play.mvc.Http.Context.current().args(key)=value}

不会引起这种副作用。

从斯蒂安的回答来看,我尝试了一种不同的方法,这对我很有效。

用 JAVA 语言

import play.mvc.Http.Context;
Context.current().args.put("isRegisterDone", isRegisterDone);

IN HTML TEMPLATE HEAD

@import Http.Context
@isOk = @{ Option(Context.current().args.get("isOk")).getOrElse(false).asInstanceOf[Boolean] }

然后像这样

@if(isOk) {
<div>OK</div>
}