Как избежать передачи параметров везде в play2? - PullRequest
125 голосов
/ 09 марта 2012

В 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>

Не знаю, хорошо это или рекомендуется, есть ли лучшее решение для этого?

Ответы [ 5 ]

229 голосов
/ 09 марта 2012

На мой взгляд, тот факт, что шаблоны статически типизированы, на самом деле хорошая вещь: вы гарантированно не вызовете ваш шаблон, если он скомпилируется.

Тем не менее, это действительно добавляет некоторые шаблоны на вызывающих сайтах. Но вы можете уменьшить его (без потери преимуществ статической печати).

В Scala я вижу два способа добиться этого: с помощью композиции действий или с помощью неявных параметров. В 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)

Поскольку в 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");
    }
}

Статический метод - это просто сокращение для извлечения меню из текущего контекста. Затем прокомментируйте ваш контроллер для смешивания с перехватчиком действия 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>
19 голосов
/ 04 апреля 2012

То, как я это делаю, - это просто создать новый контроллер для моей навигации / меню и вызвать его из вида

Так что вы можете определить свой 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>
14 голосов
/ 17 декабря 2012

Я поддерживаю ответ Стина.Это очень быстрый способ получить результаты.

Я только что перешел с 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._
@context.set("myKey","myValue")
@context.get("myKey")

Так что это очень читабельно и приятно.

Именно так я и выбрал.Стиан - хороший совет.Доказывает, что важно прокрутить вниз, чтобы увидеть все ответы.:)

Передача 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)
}

РЕДАКТИРОВАТЬ: Побочный эффект с моей реализацией "Set"

Обычный вариант использования наследования шаблонов в Play.

У вас есть 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()

Важно отметить, что вы устанавливаете "тело" дважды.Один раз в "login_page.html", а затем в "page_template.html".

Кажется, что это вызывает побочный эффект, если вы реализуете set.scala.html, как я предлагал выше.

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

, так как на странице дважды будет отображаться "вход в систему ...", потому что put возвращает значение, которое появляется во второй раз, когда мы вводим тот же ключ.(см. поставить подпись в документах Java).

scala предоставляет лучший способ изменить карту

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

, которая не вызывает этот побочный эффект.

13 голосов
/ 12 декабря 2012

Если вы используете 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 ("", "") вам лучше использовать перехватчик, но для простых случаев это может сработать.

6 голосов
/ 24 марта 2013

Из ответа Стиана я попробовал другой подход. Это работает для меня.

В коде 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>
}
...