如何避免在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> }