play2のどこにでもパラメーターを渡さないようにする方法は?


125

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

今のところ、それはの1つのパラメータにすぎ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>

それが良いか、推奨されているかわかりませんが、これに対するより良い解決策はありますか?


多分play2はliftのスニペットのようなものを追加する必要があります
Freewind

回答:


229

私の意見では、テンプレートが静的に型付けされるという事実は実際には良いことですことです。コンパイルしてもテンプレートの呼び出しが失敗しないことが保証されています。

ただし、実際には呼び出し元サイトに定型文が追加されます。しかし、あなたはそれを減らすことができます(静的型付けの利点を失うなく)。

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>

このメインテンプレートを呼び出すテンプレートがある場合、これらのテンプレートでも暗黙的なパラメーターとして宣言されている場合、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())
}

このアプローチの詳細については、このブログの投稿このコードサンプルをご覧ください。

更新:このパターンを示す素晴らしいブログ投稿もここに書かれています

アクション構成の使用

実際、RequestHeaderテンプレートに値を渡すと便利な場合があります(このサンプルを参照)。暗黙のリクエスト値を受け取るアクションを簡単に記述できるため、これはコントローラーコードにそれほど多くの定型文を追加しません。

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

したがって、テンプレートは少なくともこの暗黙的なパラメータを受け取ることが多いため、メニューなどを含むより豊富な値に置き換えることができます。これは、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>

アクションコンポジションを使用すると、テンプレートに必要なすべての暗黙的な値を1つの値に集約できますが、一方で柔軟性が失われる可能性があります…

Http.Contextの使用(Java)

JavaにはScalaの暗黙のメカニズムなどがないため、テンプレートパラメータを明示的に渡さないようにしたい場合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");
    }
}

staticメソッドは、現在のコンテキストからメニューを取得するための省略形です。次に、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>

メニューではなくメニューですか?「暗黙のvalメニュー:Seq [Menu] = Menu.findAll」
ベンマッキャン

1
また、私のプロジェクトは現在Javaでのみ記述されているため、アクション構成ルートに進んで、インターセプターのみをScalaで記述し、すべてのアクションをJavaで記述したままにすることはできますか?
ベンマッキャン

「メニュー」または「メニュー」、それは重要ではありません:)、重要なのはタイプです:Seq [メニュー]。私は自分の回答を編集し、この問題を処理するJavaパターンを追加しました。
Julien Richard-Foy

3
最後のコードブロックでは、呼び出します@for(menu <- Menus.current()) {Menus定義されていません(メニュー(小文字)を配置しますctx.args.put("menus", Menu.find.all());)。理由はありますか?大文字などに変換するPlayのように?
Cyril N.

1
@ cx42net Menusクラスが定義されています(Javaインターセプター)。@adisはい。ただし、キャッシュなど、別の場所に自由に保存できます。
Julien Richard-Foy

19

私のやり方は、ナビゲーション/メニュー用の新しいコントローラーを作成し、ビューから呼び出すことです

だからあなたはあなたを定義することができます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>

NavControllerはJavaでどのように見えると想定されていますか?コントローラーがHTMLを返すようにする方法が見つかりません。
ミカ

そして、何らかの助けを求めた直後に解決策が見つかることがあります:)コントローラメソッドは次のようになります。public static play.api.templates.Html sidebar(){return(play.api.templates.Html)sidebar.render( "message"); }
Mika

1
これはビューからコントローラーを呼び出すのに良い方法ですか?私は執事になりたくないので、真の好奇心を求めます。
2014

また、このようにリクエストに基づいて
何かを

14

stianの回答をサポートします。これは結果を得るための非常に迅速な方法です。

私はJava + Play1.0からJava + Play2.0に移行したばかりで、これまでのところテンプレートが最も難しい部分であり、ベーステンプレート(タイトル、ヘッドなど)を実装するために見つけた最良の方法はHttpを使用することです。環境。

タグを使用して実現できる非常に優れた構文があります。

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

とても読みやすくて素晴らしいです。

これは私が行くことを選んだ方法です。stian-良いアドバイス。すべての回答を表示するには下にスクロールすることが重要であることを証明します。:)

HTML変数を渡す

Html変数を渡す方法はまだわかりません。

@(タイトル:文字列、コンテンツ:HTML)

しかし、私はそれらをブロックとして渡す方法を知っています。

@(タイトル:文字列)(コンテンツ:HTML)

したがって、set.scala.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があり、次にbase_template.htmlを拡張するpage_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」を2回設定することです。"login_page.html"に入ってから、 "page_template.html"に入ります。

上記で提案したようにset.scala.htmlを実装している限り、これは副作用を引き起こすようです。

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

同じキーを2回入力するとputがポップアウトする値を返すため、ページには「login stuff ...」が2回表示されるため。(Java docsに署名を置くを参照してください)。

Scalaはマップを変更するためのより良い方法を提供します

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

これはこの副作用を引き起こしません。


Scalaコントローラーでは、play.mvc.Htt.Context.current()にputメソッドがないようにしています。何か不足していますか?
2014

args呼び出し後のコンテキストを現在に設定してみてください。
モグラビ男14

13

Javaを使用していて、インターセプターを作成せずに@Withアノテーションを使用せずにできるだけ簡単な方法が必要な場合は、テンプレートから直接HTTPコンテキストにアクセスすることもできます。

たとえば、テンプレートから利用可能な変数が必要な場合は、次のようにしてHTTPコンテキストに追加できます。

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

その後、次のようにしてテンプレートからアクセスできます。

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

Http.Context.current()。args.put( ""、 "")でメソッドを散らかす場合は、インターセプターを使用した方がよいことは明らかですが、単純なケースではそれでうまくいく場合があります。


こんにちはstian、私の回答の最後の編集を見てください。同じキーでargsに「put」を2回使用すると、厄介な副作用が発生することがわかりました。代わりに... args(key)= valueを使用してください。
モグラビ

6

スティアンの答えから、私は別のアプローチを試みました。これでうまくいきます。

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>
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.