playframework 2.0 - ¿Cómo evitar pasar parámetros en todas partes en play2?
playframework-2.0 (5)
Apoyo la respuesta de Stian. Esta es una forma muy rápida de obtener resultados.
Acabo de migrar de Java + Play1.0 a Java + Play2.0 y las plantillas son la parte más difícil hasta ahora, y la mejor manera que encontré para implementar una plantilla base (para título, encabezado, etc.) es mediante el uso de Http .Contexto.
Hay una sintaxis muy buena que puedes lograr con las etiquetas.
views
|
/--- tags
|
/------context
|
/-----get.scala.html
/-----set.scala.html
donde get.scala.html es:
@(key:String)
@{play.mvc.Http.Context.current().args.get(key)}
y set.scala.html es:
@(key:String,value:AnyRef)
@{play.mvc.Http.Context.current().args.put(key,value)}
significa que puede escribir lo siguiente en cualquier plantilla
@import tags._
@contest.set("myKey","myValue")
@context.get("myKey")
Entonces es muy legible y agradable.
Esta es la forma en que elegí ir. stian - buen consejo. Demuestra que es importante desplazarse hacia abajo para ver todas las respuestas. :)
Pasando variables HTML
Todavía no he descubierto cómo pasar variables Html.
@ (título: cadena, contenido: Html)
sin embargo, sé cómo pasarlos como bloque.
@ (title: String) (contenido: Html)
por lo que es posible que desee reemplazar set.scala.html con
@(key:String)(value:AnyRef)
@{play.mvc.Http.Context.current().args.put(key,value)}
de esta manera puede pasar bloques Html como tal
@context.set("head"){
<meta description="something here"/>
@callSomeFun(withParameter)
}
EDITAR: Efecto secundario con mi implementación "Establecer"
Un caso de uso común es la herencia de plantilla en Play.
Tienes un base_template.html y luego tienes page_template.html que extiende base_template.html.
base_template.html podría verse algo así como
<html>
<head>
<title> @context.get("title")</title>
</head>
<body>
@context.get("body")
</body>
</html>
mientras que la plantilla de la página puede ser algo así como
@context.set("body){
some page common context here..
@context.get("body")
}
@base_template()
y luego tienes una página (supongamos que login_page.html) que se parece a
@context.set("title"){login}
@context.set("body"){
login stuff..
}
@page_template()
Lo importante a tener en cuenta aquí es que estableces "cuerpo" dos veces. Una vez en "login_page.html" y luego en "page_template.html".
Parece que esto desencadena un efecto secundario, siempre y cuando implemente set.scala.html como sugerí anteriormente.
@{play.mvc.Http.Context.current().put(key,value)}
ya que la página mostraría "cosas de inicio de sesión ..." dos veces porque put devuelve el valor que aparece la segunda vez que ponemos la misma clave. (ver poner firma en documentos java).
Scala proporciona una mejor manera de modificar el mapa
@{play.mvc.Http.Context.current().args(key)=value}
que no causa este efecto secundario.
En play1, generalmente obtengo todos los datos en acciones, los uso directamente en las vistas. Como no necesitamos declarar explícitamente los parámetros, esto es muy fácil.
Pero en play2, descubrí que tenemos que declarar todos los parámetros (incluida la request
) en el encabezado de las vistas, será muy aburrido obtener todos los datos en acciones y pasarlos a las vistas.
Por ejemplo, si necesito mostrar los menús que se cargan desde la base de datos en la página principal, tengo que definirlos en 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>
Entonces tengo que declararlo en cada página secundaria:
@(menus: Seq[Menu])
@main("SubPage", menus) {
...
}
Luego tengo que obtener los menús y pasarlos a ver en cada acción:
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 ahora, es solo un parámetro en main.scala.html
, ¿y si hay muchos?
Entonces, finalmente, decidí que todo Menu.findAll()
directamente a la 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>
No sé si es bueno o recomendable, ¿hay alguna mejor solución para esto?
En mi opinión, el hecho de que las plantillas estén tipadas estáticamente es realmente una buena cosa: se garantiza que la invocación de su plantilla no fallará si se compila.
Sin embargo, de hecho agrega algunos repetitivos en los sitios de llamadas. Pero puede reducirlo (sin perder ventajas estáticas de tipeo).
En Scala, veo dos formas de lograrlo: a través de la composición de acciones o mediante el uso de parámetros implícitos. En Java sugiero usar el mapa Http.Context.args
para almacenar valores útiles y recuperarlos de las plantillas sin tener que pasar explícitamente como parámetros de plantillas.
Usando parámetros implícitos
Coloque el parámetro de menus
al final de sus parámetros de plantilla main.scala.html
y main.scala.html
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>
Ahora, si tiene plantillas llamando a esta plantilla principal, puede hacer que el parámetro de menus
implícitamente pasado a la plantilla main
por el compilador Scala si también se declara como un parámetro implícito en estas plantillas:
@()(implicit menus: Seq[Menu])
@main("SubPage") {
...
}
Pero si desea que pase implícitamente desde su controlador, debe proporcionarlo como un valor implícito, disponible en el alcance desde donde llama a la plantilla. Por ejemplo, puede declarar el siguiente método en su controlador:
implicit val menu: Seq[Menu] = Menu.findAll
Luego, en tus acciones, podrás escribir lo siguiente:
def index = Action {
Ok(views.html.index())
}
def index2 = Action {
Ok(views.html.index2())
}
Puede encontrar más información sobre este enfoque en esta publicación de blog y en este ejemplo de código .
Actualización : una buena publicación de blog que demuestra este patrón también se ha escrito here .
Usando acciones de composición
En realidad, a menudo es útil pasar el valor RequestHeader
a las plantillas (ver, por ejemplo, este ejemplo ). Esto no agrega mucho texto estándar a su código de controlador porque puede escribir fácilmente acciones que reciben un valor de solicitud implícito:
def index = Action { implicit request =>
Ok(views.html.index()) // The `request` value is implicitly passed by the compiler
}
Entonces, dado que las plantillas a menudo reciben al menos este parámetro implícito, puede reemplazarlo con un valor más rico que contenga, por ejemplo, sus menús. Puedes hacer eso usando el mecanismo de composición de acciones de Play 2.
Para hacerlo, debes definir tu clase de Context
, ajustando una solicitud subyacente:
case class Context(menus: Seq[Menu], request: Request[AnyContent])
extends WrappedRequest(request)
Luego puede definir el siguiente método ActionWithMenu
:
def ActionWithMenu(f: Context => Result) = {
Action { request =>
f(Context(Menu.findAll, request))
}
}
Que se puede usar así:
def index = ActionWithMenu { implicit context =>
Ok(views.html.index())
}
Y puede tomar el contexto como un parámetro implícito en sus plantillas. Por ejemplo, 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>
Usar la composición de acciones le permite agregar todos los valores implícitos que sus plantillas requieren en un solo valor, pero por otro lado puede perder algo de flexibilidad ...
Usando Http.Context (Java)
Como Java no tiene el mecanismo de implícita de Scala o similar, si quiere evitar pasar explícitamente los parámetros de las plantillas, una forma posible es almacenarlos en el objeto Http.Context
que solo vive durante el tiempo de una solicitud. Este objeto contiene un valor args
de tipo Map<String, Object>
.
Por lo tanto, puede comenzar escribiendo un interceptor, como se explica en la documentación :
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");
}
}
El método estático es solo una abreviatura para recuperar los menús del contexto actual. Luego anote su controlador para mezclarlo con el interceptor de acción Menus
:
@With(Menus.class)
public class Application extends Controller {
// …
}
Finalmente, recupere el valor del menus
de sus plantillas de la siguiente manera:
@(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>
La forma en que lo hago, es simplemente crear un nuevo controlador para mi navegación / menú y llamarlo desde la vista
Para que pueda definir su 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>
}
Luego, en mi vista principal, puedo llamar a ese NavController
:
@(title: String)(content: Html)
<!DOCTYPE html>
<html>
<head>
<title>@title</title>
</head>
<body>
@NavController.nav
@content
</body>
</html>
Por la respuesta de Stian, probé con un enfoque diferente. Esto funciona para mí
EN EL CÓDIGO DE JAVA
import play.mvc.Http.Context;
Context.current().args.put("isRegisterDone", isRegisterDone);
EN HTML TEMPLATE HEAD
@import Http.Context
@isOk = @{ Option(Context.current().args.get("isOk")).getOrElse(false).asInstanceOf[Boolean] }
Y UTILIZAR COMO
@if(isOk) {
<div>OK</div>
}
Si está utilizando Java y solo quiere la forma más sencilla posible sin tener que escribir un interceptor y usar la anotación @With, también puede acceder al contexto HTTP directamente desde la plantilla.
Por ejemplo, si necesita una variable disponible de una plantilla, puede agregarla al contexto HTTP con:
Http.Context.current().args.put("menus", menus)
A continuación, puede acceder desde la plantilla con:
@Http.Context.current().args.get("menus").asInstanceOf[List<Menu>]
Obviamente, si ensucia sus métodos con Http.Context.current (). Args.put ("", ""), es mejor que use un interceptor, pero para casos simples puede funcionar.