web-dev-qa-db-ja.com

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_のパラメーターは1つだけですが、多数ある場合はどうなりますか?

ようやく、すべての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>
_

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

125
Freewind

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

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

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

このアプローチの詳細については、 このブログ投稿 および このコードサンプル を参照してください。

Update:このパターンを示す素敵なブログ投稿も書かれています here

アクション構成を使用する

実際には、RequestHeader値をテンプレートに渡すと便利なことがよくあります(例 このサンプル を参照)。暗黙的な要求値を受け取るアクションを簡単に記述できるため、コントローラーコードにそれほどボイラープレートは追加されません。

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

そのため、テンプレートは少なくともこの暗黙的なパラメータを受け取ることが多いため、たとえば次のようなより豊かな値に置き換えることができます。あなたのメニュー。 Play 2の actions composition メカニズムを使用して、これを行うことができます。

そのためには、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_オブジェクトに格納することです要求。このオブジェクトには、タイプ_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>
_
229

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

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>
19
Darko

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変数を渡す方法はまだわかりません。

@(title:String、content:Html)

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

@(title:String)(content: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での一般的なユースケースのテンプレート継承。

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

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

scalaはマップを変更するより良い方法を提供します

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

この副作用は発生しません。

14
guy mograbi

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( ""、 "")を使用してメソッドをポイ捨てする場合は、インターセプターを使用する方が良いことは明らかですが、単純な場合にはトリックを実行できます。

13
stian

Stianの答えから、私は別のアプローチを試みました。これは私のために動作します。

IN Java CODE

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>
}
6
angelokh