Как избежать прохождения параметров везде в 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, что, если их много?

Итак, наконец, я решил все 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>

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

Ответы

Ответ 1

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

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

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

Итак, поскольку шаблоны часто получают хотя бы этот неявный параметр, вы можете заменить его более богатым значением, содержащим, например, ваши меню. Вы можете это сделать, используя механизм action composition для воспроизведения 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 не имеет механизма wiki\w62 > s или аналогичного, если вы хотите, чтобы явным образом не передавал параметры шаблонов, возможно, это сохранить их в объекте 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 action interceptor:

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

Ответ 2

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

Итак, вы можете определить свой 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>

Ответ 3

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

Ответ 4

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

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

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

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

Передача переменных 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"/> 
     @callSomeFun(withParameter)
}

EDIT: побочный эффект с реализацией "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}

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

Ответ 5

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

В JAVA CODE

import play.mvc.Http.Context;
Context.current().args.put("isRegisterDone", isRegisterDone);

В HTML TEMPLATE HEAD

@import Http.Context
@isOk = @{ Option(Context.current().args.get("isOk")).getOrElse(false).asInstanceOf[Boolean] } 

И ИСПОЛЬЗУЙТЕ КАК

@if(isOk) {
   <div>OK</div>
}