Как избежать передачи параметров везде в 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>

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

Freewind
источник
Может, play2 стоит добавить что-нибудь вроде отрывков лифта
Freewind

Ответы:

229

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

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

В 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>
Жюльен Ришар-Фой
источник
Вы имели в виду меню вместо меню? "implicit val menus: Seq [Menu] = Menu.findAll"
Бен Макканн,
1
Кроме того, поскольку мой проект сейчас написан только на Java, можно ли было бы пойти по маршруту композиции действий и иметь только мой перехватчик, написанный на Scala, но оставить все мои действия написанными на Java?
Бен Макканн,
"меню" или "меню", неважно :), имеет значение тип: Seq [Menu]. Я отредактировал свой ответ и добавил шаблон Java для решения этой проблемы.
Жюльен Ришар-Фой,
3
В последнем блоке кода вы вызываете, @for(menu <- Menus.current()) {но Menusникогда не определяете (вы помещаете меню (нижний регистр) :) ctx.args.put("menus", Menu.find.all());. Есть причина? Например, Play, который переводит его в верхний регистр или что-то в этом роде?
Кирилл Н.
1
@ cx42net Определен Menusкласс (перехватчик Java). @adis Да, но вы можете хранить их в другом месте, даже в кеше.
Жюльен Ришар-Фой
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.
Мика
И так бывает, что вы находите решение сразу после того, как попросите помощи :) Метод контроллера должен выглядеть так. общедоступный статический play.api.templates.Html боковая панель () {return (play.api.templates.Html) sidebar.render ("сообщение"); }
Mika
1
Это хорошая практика - вызывать контроллер из представления? Я не хочу быть ярым сторонником, поэтому спрашиваю из искреннего любопытства.
0fnt
Кроме того, вы не можете делать вещи, основанные на запросах таким образом, не могли бы вы .., например, настройки для конкретного пользователя ..
0fnt
14

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

Я только что перешел с 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.

@ (Название: String, содержание: Html)

однако я знаю, как передать их как блок.

@ (Название: String) (содержание: 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()

Здесь важно отметить, что вы устанавливаете "body" дважды. Один раз в "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}

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

парень мограби
источник
В контроллере scala я пытаюсь сделать так, чтобы в play.mvc.Htt.Context.current () нет метода put. Я что-то упускаю?
0fnt
попробуйте поставить argsконтекст после вызова текущим.
парень мограби
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 (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>
}
angelokh
источник