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

지금은의 매개 변수가 하나뿐입니다 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 's snippets와 같은 것을 추가해야합니다
Freewind

답변:


229

제 생각에는 템플릿이 정적으로 입력된다는 사실은 실제로 좋은 것입니다. 컴파일하면 템플릿을 호출해도 실패하지 않을 것입니다.

그러나 실제로 호출 사이트에 약간의 상용구를 추가합니다. 그러나 정적 타이핑 이점을 잃지 않고 줄일 수 있습니다 .

스칼라에서는 두 가지 방법으로 작업 구성을 통해 또는 암시 적 매개 변수를 사용합니다. 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
}

따라서 템플릿에는 종종이 암시 적 매개 변수가 수신되기 때문에 메뉴를 포함하는 더 풍부한 값으로 대체 할 수 있습니다. 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에는 스칼라의 암시 적 메커니즘 또는 이와 유사한 기능이 없으므로 템플릿 매개 변수를 명시 적으로 전달하지 않으려면 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>

메뉴 대신 메뉴를 의미 했습니까? "암시 적 val 메뉴 : Seq [메뉴] = Menu.findAll"
Ben McCann

1
또한 내 프로젝트는 현재 Java로만 작성되었으므로 액션 컴포지션 경로로 이동하여 인터셉터 만 Scala로 작성할 수 있지만 모든 액션을 Java로 작성해도됩니까?
벤 McCann

"menu"또는 "menus"는 중요하지 않습니다 :), 중요한 것은 유형 : Seq [Menu]입니다. 내 대답을 편집 하고이 문제를 처리하기 위해 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을 반환하도록 만드는 방법을 찾을 수 없습니다.
Mika

그래서 도움을 요청한 직후에 해결책을 찾으십시오. :) 컨트롤러 방법은 다음과 같아야합니다. 공개 정적 play.api.templates.Html sidebar () {return (play.api.templates.Html) sidebar.render ( "message"); }
Mika

1
보기에서 컨트롤러를 호출하는 것이 좋은 방법입니까? 나는 진정 호기심을 요구하는 까다로운 사람이되고 싶지 않습니다.
0fnt

또한 이러한 방식으로 요청을 기반으로 작업을 수행 할 수 없습니다. 예를 들어 사용자 별 설정 등이 있습니다.
0fnt

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)

그러나 블록으로 전달하는 방법을 알고 있습니다.

@ (제목 : 문자열) (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"를 두 번 설정한다는 것입니다. "login_page.html"과 "page_template.html"에 한 번

위에서 제안한 것처럼 set.scala.html을 구현하는 한 부작용이 발생하는 것으로 보입니다.

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

put은 같은 키를 두번 째 켤 때 튀어 나오는 값을 반환하기 때문에 페이지에 "login stuff ..."가 두 번 표시되므로 (자바 문서에 서명 넣기 참조).

스칼라는지도를 수정하는 더 좋은 방법을 제공합니다

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

이 부작용을 일으키지 않습니다.


스칼라 컨트롤러에서는 play.mvc.Htt.Context.current ()에 put 메소드가 없습니다. 뭔가 빠졌습니까?
0fnt

argsafter call context를 현재로 시도하십시오 .
guy mograbi

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"을 두 번 사용하면 부작용이 발생한다는 것을 알았습니다. 대신 ... args (key) = value를 사용해야합니다.
guy mograbi

6

Stian의 답변에서 다른 접근법을 시도했습니다. 이것은 나를 위해 작동합니다.

자바 코드

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.