Play2のどこにでもパラメータを渡すのを避ける方法は?
-
09-12-2019 - |
質問
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>
それが良いのか推奨されているのかわかりませんが、これに対するより良い解決策はありますか?
解決
私の意見では、テンプレートが静的に型付けされているという事実は、実際には 良い もの:テンプレートがコンパイルされても、テンプレートの呼び出しが失敗しないことが保証されています。
しかし、それは確かに呼び出し元のサイトにいくつかの定型文を追加します。しかし、 あなたはそれを減らすことができます (静的型付けの利点を失うことなく)。
Scalaでは、私はそれを達成するための2つの方法を見ています:アクションの構成または暗黙的なパラメータを使用して。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
}
したがって、テンプレートは少なくともこの暗黙的なパラメータを受け取ることが多いため、たとえば次のようなより豊かな値に置き換えることがであなたのメニュー。あなたはそれを使うことによってそれをすることができます アクション構成 遊びのメカニズム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>
Actions compositionを使用すると、テンプレートが必要とするすべての暗黙的な値を単一の値に集約できますが、一方で柔軟性が失われる可能性があります…
Httpを使用します。コンテキスト(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");
}
}
静的メソッドは、現在のコンテキストからメニューを取得するための単なる省略形です。次に、コントローラに注釈を付けて混合します 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)
}
ナビスカラ...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>
私はstianの答えを支持します。これは結果を得るための非常に迅速な方法です。
私はちょうどJava+Play1.0からJava+Play2.0に移行しましたが、テンプレートはこれまでのところ最も難しい部分であり、ベーステンプレート(title、headなど)を実装するため.)はHttpを使用することです。コンテキスト。
あなたがタグで達成できる非常に素晴らしい構文があります。
views
|
\--- tags
|
\------context
|
\-----get.scala.html
\-----set.scala.html
どこで取得します。スカラ...htmlは :
@(key:String)
@{play.mvc.Http.Context.current().args.get(key)}
とセット。スカラ...htmlは:
@(key:String,value:AnyRef)
@{play.mvc.Http.Context.current().args.put(key,value)}
つまり、任意のテンプレートに次のように書くことができます
@import tags._
@context.set("myKey","myValue")
@context.get("myKey")
だから、それは非常に読みやすく、いいです。
これは私が行くことを選んだ方法です。stian-良いアドバイス。すべての回答を表示するには、下にスクロールすることが重要であることを証明します。:)
HTML変数の渡し
私はまだHtml変数を渡す方法を理解していません。
@(タイトル:文字列、コンテンツ:Html)
しかし、私はそれらをブロックとして渡す方法を知っています。
@(タイトル:文字列)(コンテンツ:Html)
したがって、setを置き換えたい場合があります。スカラ...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での一般的なユースケースitテンプレート継承。
あなたはbase_templateを持っています。htmlそして、あなたはpage_templateを持っています。base_templateを拡張するhtml。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」を2回設定することです。一度"login_page"に入ります。html"とし、"page_template"にします。html"。
Setを実装している限り、これは副作用を引き起こすようです。スカラ...私が上で提案したようなhtml。
@{play.mvc.Http.Context.current().put(key,value)}
ページに「ログインのもの」が表示されるように。.."二度putは、我々は同じキーを置く二度目に飛び出す値を返しますので。(javaドキュメントに署名を入れるを参照してください)。
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であなたのメソッドを捨てるならば。コンテキスト。現在の()。args。put(""、"")インターセプターを使用する方が良いですが、単純なケースではトリックを実行するかもしれません。
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>
}