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

Fre*_*ind 125 playframework-2.0

在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>
Run Code Online (Sandbox Code Playgroud)

然后我必须在每个子页面中声明它:

@(menus: Seq[Menu])

@main("SubPage", menus) {
   ...
}
Run Code Online (Sandbox Code Playgroud)

然后我必须得到菜单并传递给每个动作查看:

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))
}
Run Code Online (Sandbox Code Playgroud)

现在它只有一个参数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>
Run Code Online (Sandbox Code Playgroud)

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

Jul*_*Foy 229

在我看来,模板是静态类型的这一事实实际上是一件好事:你可以保证调用你的模板如果编译就不会失败.

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

在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>
Run Code Online (Sandbox Code Playgroud)

现在,如果您有调用此主模板的模板,则可以menus通过mainScala编译器将参数隐式传递给模板,如果它在这些模板中也被声明为隐式参数:

@()(implicit menus: Seq[Menu])

@main("SubPage") {
  ...
}
Run Code Online (Sandbox Code Playgroud)

但是,如果您希望从控制器中隐式传递它,则需要将其作为隐式值提供,可在调用模板的范围内使用.例如,您可以在控制器中声明以下方法:

implicit val menu: Seq[Menu] = Menu.findAll
Run Code Online (Sandbox Code Playgroud)

然后在您的操作中,您将能够编写以下内容:

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

def index2 = Action {
  Ok(views.html.index2())
}
Run Code Online (Sandbox Code Playgroud)

您可以在此博客文章此代码示例中找到有关此方法的更多信息.

更新:这里也写一篇很好的博客文章,展示了这种模式.

使用动作组合

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

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

因此,由于模板通常至少会接收此隐式参数,因此您可以将其替换为包含例如菜单的更丰富的值.您可以使用Play 2 的动作组合机制来实现.

要做到这一点,你必须定义你的Context类,包装一个基础请求:

case class Context(menus: Seq[Menu], request: Request[AnyContent])
        extends WrappedRequest(request)
Run Code Online (Sandbox Code Playgroud)

然后,您可以定义以下ActionWithMenu方法:

def ActionWithMenu(f: Context => Result) = {
  Action { request =>
    f(Context(Menu.findAll, request))
  }
}
Run Code Online (Sandbox Code Playgroud)

哪个可以这样使用:

def index = ActionWithMenu { implicit context =>
  Ok(views.html.index())
}
Run Code Online (Sandbox Code Playgroud)

您可以将上下文作为模板中的隐式参数.例如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>
Run Code Online (Sandbox Code Playgroud)

使用操作组合允许您将模板所需的所有隐式值聚合到单个值中,但另一方面,您可能会失去一些灵活性......

使用Http.Context(Java)

由于Java没有Scala的implicits机制或类似机制,如果您想避免显式传递模板参数,可能的方法是将它们存储在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");
    }
}
Run Code Online (Sandbox Code Playgroud)

静态方法只是从当前上下文中检索菜单的简写.然后注释您的控制器与Menus动作拦截器混合:

@With(Menus.class)
public class Application extends Controller {
    // …
}
Run Code Online (Sandbox Code Playgroud)

最后,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>
Run Code Online (Sandbox Code Playgroud)

  • 在最后一个代码块中,你调用`@for(menu < - Menus.current()){`但是`Menus`从未被定义(你把菜单(小写):`ctx.args.put("menus", Menu.find.all());`).有原因吗?喜欢Play以大写或其他形式转换它? (3认同)

小智 19

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

所以你可以定义你的NavController:

object NavController extends Controller {

  private val navList = "Home" :: "About" :: "Contact" :: Nil

  def nav = views.html.nav(navList)

}
Run Code Online (Sandbox Code Playgroud)

nav.scala.html

@(navLinks: Seq[String])

@for(nav <- navLinks) {
  <a href="#">@nav</a>
}
Run Code Online (Sandbox Code Playgroud)

然后在我的主视图中我可以称之为NavController:

@(title: String)(content: Html)
<!DOCTYPE html>
<html>
  <head>
    <title>@title</title>
  </head>
  <body>
     @NavController.nav
     @content
  </body>
</html>
Run Code Online (Sandbox Code Playgroud)


guy*_*abi 14

我支持stian的答案.这是获得结果的快速方法.

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

您可以使用标签实现非常好的语法.

views
  |
  \--- tags
         |
         \------context
                  |
                  \-----get.scala.html
                  \-----set.scala.html
Run Code Online (Sandbox Code Playgroud)

get.scala.html在哪里:

@(key:String)
@{play.mvc.Http.Context.current().args.get(key)}
Run Code Online (Sandbox Code Playgroud)

和set.scala.html是:

@(key:String,value:AnyRef)
@{play.mvc.Http.Context.current().args.put(key,value)}
Run Code Online (Sandbox Code Playgroud)

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

@import tags._
@context.set("myKey","myValue")
@context.get("myKey")
Run Code Online (Sandbox Code Playgroud)

所以它非常易读且很好.

这是我选择的方式.stian - 好建议.证明向下滚动以查看所有答案非常重要.:)

传递HTML变量

我还没想出如何传递Html变量.

@(标题:字符串,内容:HTML)

但是,我知道如何将它们作为块传递.

@(标题:字符串)(内容:HTML)

所以你可能想用set.scala.html替换

@(key:String)(value:AnyRef)
@{play.mvc.Http.Context.current().args.put(key,value)}
Run Code Online (Sandbox Code Playgroud)

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

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

编辑:我的"设置"实现的副作用

一个常见的用例是它在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>
Run Code Online (Sandbox Code Playgroud)

而页面模板可能看起来像

@context.set("body){
    some page common context here.. 
    @context.get("body")
}
@base_template()
Run Code Online (Sandbox Code Playgroud)

然后你有一个看起来像的页面(让我们假设login_page.html)

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

@page_template()
Run Code Online (Sandbox Code Playgroud)

这里要注意的重要一点是你将"身体"设置了两次.进入"login_page.html"然后进入"page_template.html".

这似乎会触发副作用,只要你像我上面提到的那样实现set.scala.html.

@{play.mvc.Http.Context.current().put(key,value)}
Run Code Online (Sandbox Code Playgroud)

因为页面会显示"登录的东西......"两次,因为put会返回第二次弹出相同键时弹出的值.(请参阅java docs中的签名).

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

@{play.mvc.Http.Context.current().args(key)=value}
Run Code Online (Sandbox Code Playgroud)

这不会引起这种副作用.


sti*_*ian 13

如果您使用Java并且只需要最简单的方法而不必编写拦截器并使用@With注释,您也可以直接从模板访问HTTP上下文.

例如,如果您需要模板中的变量,则可以将其添加到HTTP上下文中:

Http.Context.current().args.put("menus", menus)
Run Code Online (Sandbox Code Playgroud)

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

@Http.Context.current().args.get("menus").asInstanceOf[List<Menu>]
Run Code Online (Sandbox Code Playgroud)

显然,如果你使用Http.Context.current().args.put("","")丢弃你的方法,你最好使用一个拦截器,但对于简单的情况,它可以做到这一点.


ang*_*okh 6

从Stian的回答中,我尝试了另一种方法.这适合我.

在JAVA代码中

import play.mvc.Http.Context;
Context.current().args.put("isRegisterDone", isRegisterDone);
Run Code Online (Sandbox Code Playgroud)

在HTML模板头

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

和我一样

@if(isOk) {
   <div>OK</div>
}
Run Code Online (Sandbox Code Playgroud)