Як уникнути передачі параметрів скрізь у 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 повинен додати щось на кшталт фрагментів ліфта
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>

Ви мали на увазі меню замість меню? "неявні меню меню: Seq [Menu] = Menu.findAll"
Бен Макканн

1
Крім того, оскільки мій проект написаний тільки на Java зараз, чи можна було б пройти маршрут композиції дій і тільки мій перехоплювач записаний у Scala, але залишити всі мої дії написаними на Java?
Бен Макканн

"меню" або "меню", не важливо :), що має значення тип: Seq [Меню]. Я відредагував свою відповідь і додав шаблон 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 ("повідомлення"); }
Міка

1
Це хороша практика викликати контролер з виду? Я не хочу бути прихильником, тому прошу від щирої цікавості.
0fnt

Крім того, ви не можете робити речі, які базуються на запитах таким чином. Чи, наприклад, спеціальні налаштування користувача ..
0fnt

14

Я підтримую відповідь Стіана. Це дуже швидкий спосіб отримати результати.

Я щойно перейшов з 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.

@ (назва: Рядок, вміст: Html)

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

@ (назва: Рядок) (вміст: 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)
}

EDIT: Побічний ефект від моєї реалізації "Набір"

Загальний варіант успадкування шаблонів у 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)}

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

scala забезпечує кращий спосіб зміни карти

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

що не викликає цього побічного ефекту.


У контролері scala я намагаюся зробити, щоб у play.mvc.Htt.Context.current () немає методу put. Я щось пропускаю?
0fnt

спробуйте встановити argsконтекст після виклику поточним.
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 ("", ""), вам краще використовувати перехоплювач, але для простих випадків це може зробити трюк.


Привіт Стянь, будь ласка, подивіться на мою останню редакцію у моїй відповіді. Я щойно дізнався, що якщо два рази використати аргумент "аргументи" одним ключем, ви отримаєте неприємний побічний ефект. Ви повинні використовувати ... args (key) = значення замість цього.
хлопець mograbi

6

З відповіді Стіана я спробував інший підхід. Це працює для мене.

В КОДІ ДЖАВА

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>
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.