Как избежать передачи параметров повсюду в play2?

StackOverflow https://stackoverflow.com//questions/9629250

  •  09-12-2019
  •  | 
  •  

Вопрос

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

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

Это было полезно?

Решение

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

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

В Scala я вижу два способа добиться этого:посредством композиции действий или использования неявных параметров.В Java я предлагаю использовать Http.Context.args map для хранения полезных значений и их извлечения из шаблонов без необходимости явно передавать их в качестве параметров шаблонов.

Использование неявных параметров

Поместите 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
}

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

Другие советы

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

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

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

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

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

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

Передача переменных 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()

Здесь важно отметить, что вы устанавливаете «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}

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

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

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

В ЯВА-КОДЕ

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>
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top