Como evitar a passagem de parâmetros em qualquer lugar no play2?

125

No play1, eu costumo obter todos os dados em ações, usá-los diretamente nas visualizações. Como não precisamos declarar explicitamente parâmetros à vista, isso é muito fácil.

Mas no play2, descobri que temos que declarar todos os parâmetros (inclusive request) no cabeçalho das visualizações, será muito chato obter todos os dados em ações e passá-los para as visualizações.

Por exemplo, se eu precisar exibir menus carregados do banco de dados na primeira página, preciso defini-lo em 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>

Então eu tenho que declarar em todas as subpáginas:

@(menus: Seq[Menu])

@main("SubPage", menus) {
   ...
}

Então eu tenho que pegar os menus e passá-lo para ver em todas as ações:

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

Por enquanto main.scala.html, existe apenas um parâmetro , e se houver muitos?

Então, finalmente, eu decidi todos Menu.findAll()diretamente em vista:

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

Não sei se é bom ou recomendado, existe alguma solução melhor para isso?

Vento livre
fonte
Talvez play2 deve adicionar algo como trechos do elevador
Freewind

Respostas:

229

Na minha opinião, o fato de que os modelos são estaticamente digitados é realmente uma coisa boa : você garante que a chamada do seu modelo não falhará se for compilada.

No entanto, ele realmente adiciona alguns clichês nos sites de chamada. Mas você pode reduzi-lo (sem perder as vantagens da digitação estática).

No Scala, vejo duas maneiras de alcançá-lo: através da composição de ações ou usando parâmetros implícitos. Em Java, sugiro usar o Http.Context.argsmapa para armazenar valores úteis e recuperá-los dos modelos sem ter que passar explicitamente como parâmetros de modelos.

Usando parâmetros implícitos

Coloque o menusparâmetro no final dos main.scala.htmlparâmetros do seu modelo e marque-o como "implícito":

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

Agora, se você tiver modelos chamando esse modelo principal, poderá menuspassar o parâmetro implicitamente para o mainmodelo pelo compilador Scala, se ele também for declarado como parâmetro implícito nesses modelos:

@()(implicit menus: Seq[Menu])

@main("SubPage") {
  ...
}

Mas se você deseja que seja passado implicitamente do seu controlador, é necessário fornecê-lo como um valor implícito, disponível no escopo de onde você chama o modelo. Por exemplo, você pode declarar o seguinte método no seu controlador:

implicit val menu: Seq[Menu] = Menu.findAll

Em suas ações, você poderá escrever o seguinte:

def index = Action {
  Ok(views.html.index())
}

def index2 = Action {
  Ok(views.html.index2())
}

Você pode encontrar mais informações sobre essa abordagem nesta postagem do blog e neste exemplo de código .

Atualização : Um bom post de blog demonstrando esse padrão também foi escrito aqui .

Usando composição de ações

Na verdade, geralmente é útil passar o RequestHeadervalor para os modelos (veja, por exemplo, este exemplo ). Isso não adiciona muito clichê ao código do controlador, porque você pode escrever facilmente ações que recebem um valor implícito de solicitação:

def index = Action { implicit request =>
  Ok(views.html.index()) // The `request` value is implicitly passed by the compiler
}

Portanto, como os modelos geralmente recebem pelo menos esse parâmetro implícito, você pode substituí-lo por um valor mais rico, contendo, por exemplo, seus menus. Você pode fazer isso usando o mecanismo de composição de ações do Play 2.

Para fazer isso, você precisa definir sua Contextclasse, agrupando uma solicitação subjacente:

case class Context(menus: Seq[Menu], request: Request[AnyContent])
        extends WrappedRequest(request)

Então você pode definir o seguinte ActionWithMenumétodo:

def ActionWithMenu(f: Context => Result) = {
  Action { request =>
    f(Context(Menu.findAll, request))
  }
}

Que pode ser usado assim:

def index = ActionWithMenu { implicit context =>
  Ok(views.html.index())
}

E você pode considerar o contexto como um parâmetro implícito em seus modelos. Por exemplo, para 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>

O uso da composição de ações permite agregar todos os valores implícitos que seus modelos exigem em um único valor, mas por outro lado, você pode perder alguma flexibilidade…

Usando Http.Context (Java)

Como o Java não possui o mecanismo implícito do Scala ou similar, se você deseja evitar passar explicitamente os parâmetros dos modelos, uma maneira possível é armazená-los no Http.Contextobjeto que vive apenas pela duração de uma solicitação. Este objecto contém um argsvalor de tipo Map<String, Object>.

Assim, você pode começar escrevendo um interceptador, conforme explicado na documentação :

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");
    }
}

O método estático é apenas uma abreviação para recuperar os menus do contexto atual. Em seguida, anote seu controlador para ser misturado com o Menusinterceptor de ação:

@With(Menus.class)
public class Application extends Controller {
    // …
}

Por fim, recupere o menusvalor dos seus modelos da seguinte maneira:

@(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>
Julien Richard-Foy
fonte
Você quis dizer menus em vez de menu? "menus de valores implícitos: Seq [Menu] = Menu.findAll"
Ben McCann
1
Além disso, como meu projeto está escrito apenas em Java agora, seria possível seguir a rota de composição de ações e ter apenas meu interceptador escrito em Scala, mas deixar todas as minhas ações escritas em Java?
22912 Ben McCann
"menu" ou "menus", não importa :), o que importa é o tipo: Seq [Menu]. Editei minha resposta e adicionei um padrão Java para lidar com esse problema.
Julien Richard-Foy
3
No último bloco de código, você chama, @for(menu <- Menus.current()) {mas Menusnunca é definido (você coloca menus (em letras minúsculas) ctx.args.put("menus", Menu.find.all());:). Há uma razão? Como o Play que o transforma em maiúsculas ou algo assim?
Cyril N.
1
@ cx42net Existe uma Menusclasse definida (o interceptor Java). @adis Sim, mas você pode armazená-los em outro local, mesmo no cache.
Julien Richard-Foy
19

A maneira como faço isso é apenas criar um novo controlador para a minha navegação / menu e chamá-lo da exibição

Então você pode definir o seu 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>
}

Então, na minha visão principal, posso chamar isso NavController:

@(title: String)(content: Html)
<!DOCTYPE html>
<html>
  <head>
    <title>@title</title>
  </head>
  <body>
     @NavController.nav
     @content
  </body>
</html>
Darko
fonte
Como o NavController deve parecer em Java? Não consigo encontrar uma maneira de fazer com que o controlador retorne o html.
Mika #
E acontece que você encontra a solução logo após pedir ajuda :) O método do controlador deve ficar assim. estática pública play.api.templates.Html sidebar () {return (play.api.templates.Html) sidebar.render ("message"); }
Mika
1
É uma boa prática chamar o controlador de uma exibição? Eu não quero ser um defensor, então pergunte por genuína curiosidade.
0fnt
Além disso, você não pode fazer coisas com base em pedidos, desta forma, não é .., para configurações específicas exemplo usuário ..
0fnt
14

Eu apoio a resposta de Stian. Essa é uma maneira muito rápida de obter resultados.

Acabei de migrar do Java + Play1.0 para o Java + Play2.0 e os modelos são a parte mais difícil até agora, e a melhor maneira que encontrei para implementar um modelo base (para título, cabeçalho etc.) é usando o Http .Contexto.

Há uma sintaxe muito agradável que você pode obter com tags.

views
  |
  \--- tags
         |
         \------context
                  |
                  \-----get.scala.html
                  \-----set.scala.html

onde get.scala.html é:

@(key:String)
@{play.mvc.Http.Context.current().args.get(key)}

e set.scala.html é:

@(key:String,value:AnyRef)
@{play.mvc.Http.Context.current().args.put(key,value)}

significa que você pode escrever o seguinte em qualquer modelo

@import tags._
@context.set("myKey","myValue")
@context.get("myKey")

Portanto, é muito legível e agradável.

Foi assim que eu escolhi seguir. stian - bom conselho. Prova que é importante rolar para baixo para ver todas as respostas. :)

Passando variáveis ​​HTML

Ainda não descobri como passar variáveis ​​Html.

@ (título: String, conteúdo: Html)

no entanto, eu sei como passá-los como bloco.

@ (título: String) (conteúdo: Html)

portanto, convém substituir set.scala.html por

@(key:String)(value:AnyRef)
@{play.mvc.Http.Context.current().args.put(key,value)}

Dessa forma, você pode passar blocos Html dessa maneira

@context.set("head"){ 
     <meta description="something here"/> 
     @callSomeFunction(withParameter)
}

Edição: efeito colateral com minha implementação "Set"

Um caso de uso comum de herança de modelo no Play.

Você tem um base_template.html e, em seguida, tem page_template.html que estende base_template.html.

base_template.html pode parecer algo como

<html> 
    <head>
        <title> @context.get("title")</title>
    </head>
    <body>
       @context.get("body")
    </body>
</html>

enquanto o modelo da página pode parecer algo como

@context.set("body){
    some page common context here.. 
    @context.get("body")
}
@base_template()

e então você tem uma página (vamos assumir login_page.html) que se parece com

@context.set("title"){login}
@context.set("body"){
    login stuff..
}

@page_template()

O importante a ser observado aqui é que você define "corpo" duas vezes. Uma vez em "login_page.html" e depois em "page_template.html".

Parece que isso desencadeia um efeito colateral, desde que você implemente set.scala.html como sugeri acima.

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

pois a página mostraria "coisas de login ..." duas vezes porque put retorna o valor que aparece na segunda vez em que colocamos a mesma chave. (consulte colocar assinatura nos documentos java).

O scala fornece uma maneira melhor de modificar o mapa

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

o que não causa esse efeito colateral.

guy mograbi
fonte
No controlador scala, eu tento fazer, não existe um método put em play.mvc.Htt.Context.current (). Estou esquecendo de algo?
0fnt
tente colocar o argscontexto após a chamada atual.
cara Mograbi
13

Se você estiver usando Java e quiser apenas a maneira mais simples possível, sem precisar escrever um interceptador e usar a anotação @With, também poderá acessar o contexto HTTP diretamente do modelo.

Por exemplo, se você precisar de uma variável disponível em um modelo, poderá adicioná-la ao contexto HTTP com:

Http.Context.current().args.put("menus", menus)

Você pode acessá-lo no modelo com:

@Http.Context.current().args.get("menus").asInstanceOf[List<Menu>]

Obviamente, se você mesclar seus métodos com Http.Context.current (). Args.put ("", ""), é melhor usar um interceptador, mas em casos simples, isso pode ser suficiente.

stian
fonte
Oi stian, por favor, dê uma olhada na minha última edição na minha resposta. Acabei de descobrir que, se você usar "put" em args duas vezes com a mesma chave, terá um efeito colateral desagradável. Você deve usar ... args (key) = value.
cara Mograbi
6

Pela resposta de Stian, tentei uma abordagem diferente. Isso funciona para mim.

CÓDIGO JAVA

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

NA CABEÇA DE MODELO HTML

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

E USAR COMO

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