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

在play1中,我通常会获取所有数据,直接在视图中使用它们。 由于我们不需要显式地声明参数,所以这非常简单。

但是在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只有一个参数,如果有很多参数呢?

最后,我决定直接在视图中显示所有的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> 

我不知道这是好还是build议,有没有更好的解决scheme呢?

在我看来,模板是静态types的事实实际上是一件好事:你保证调用你的模板不会失败,如果它编译。

但是,它确实在呼叫站点增加了一些样板。 但是你可以减less它 (而不会失去静态打字的优势)。

在斯卡拉,我看到了两种实现方法:通过动作组合或使用隐式参数。 在Java中,我build议使用Http.Context.args映射来存储有用的值,并从模板中检索它们,而不必显式地作为模板parameter passing。

使用隐式参数

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") { ... } 

但是如果你想从你的控制器中隐式地传递它,你需要把它作为一个隐式的值来提供,你可以在你调用模板的地方find它。 例如,您可以在控制器中声明以下方法:

 implicit val menu: Seq[Menu] = Menu.findAll 

然后在你的行动中,你可以只写下面的内容:

 def index = Action { Ok(views.html.index()) } def index2 = Action { Ok(views.html.index2()) } 

您可以在本博客文章和此代码示例中find有关此方法的更多信息。

更新 :一个很好的博客文章展示了这种模式也写在这里 。

使用动作组合

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

 def index = Action { implicit request => Ok(views.html.index()) // The `request` value is implicitly passed by the compiler } 

所以,由于模板通常至less会收到这个隐式参数,所以可以用一个更丰富的值replace它,例如菜单。 您可以使用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对象中,该对象只在请求期间存在。 该对象包含一个types为Map<String, Object>args值。

因此,你可以从编写一个拦截器开始,正如文档中所解释的那样:

 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> 

我这样做的方法是为我的导航/菜单创build一个新的控制器,并从视图中调用它

所以你可以定义你的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上下文。

例如,如果你需要一个模板可用的variables,你可以将它添加到HTTP上下文:

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

然后您可以使用以下模板访问它:

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

很明显,如果你用Http.Context.current()。args.put(“”,“”)抛弃你的方法,你最好使用拦截器,但是对于简单的情况,它可能会有诀窍。

我支持斯坦的回答。 这是获得结果的一个非常快速的方法。

我刚刚从Java + Play1.0迁移到Java + Play2.0,模板是迄今为止最困难的部分,我发现实现基本模板(对于标题,头等)的最佳方式是使用Http .Context。

有一个非常好的语法,你可以实现与标签。

 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._ @contest.set("myKey","myValue") @context.get("myKey") 

所以这是非常可读的,很好。

这是我select去的方式。 stian – 很好的build议。 certificate向下滚动查看所有答案非常重要。 🙂

传递HTMLvariables

我还没有想出如何通过Htmlvariables。

@(标题:string,内容:HTML)

然而,我知道如何通过他们作为块。

@(标题:string)(内容:HTML)

所以你可能想用setreplaceset.scala.html

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

这样你可以像这样传递Html块

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

编辑:我的“设置”实施的副作用

Play中常用的一种模板inheritance。

你有一个base_template.html,然后你有page_template.html扩展base_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”。

看来这触发了一个副作用,只要你像我上面build议的那样实现set.scala.html。

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

因为页面会显示“login的东西…”两次,因为put返回第二次popup的值,我们把相同的密钥。 (请参阅在Java文档中放置签名)。

scala提供了更好的方法来修改地图

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

这不会导致这种副作用。

从Stian的回答中,我尝试了一种不同的方法。 这对我有用。

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