Come evitare il passaggio di parametri ovunque in play2?


125

In play1, di solito ottengo tutti i dati nelle azioni, li uso direttamente nelle viste. Poiché non è necessario dichiarare esplicitamente i parametri in vista, questo è molto semplice.

Ma in play2, ho scoperto che dobbiamo dichiarare tutti i parametri (incluso request) nella testa delle viste, sarà molto noioso ottenere tutti i dati nelle azioni e passarli nelle viste.

Ad esempio, se devo visualizzare i menu caricati dal database nella prima pagina, devo definirlo in 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>

Quindi devo dichiararlo in ogni sottopagina:

@(menus: Seq[Menu])

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

Quindi devo ottenere i menu e passarli per visualizzarli in ogni azione:

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

Per ora è solo un parametro in main.scala.html, e se ce ne sono molti?

Quindi alla fine, ho deciso di visualizzare tutti Menu.findAll()direttamente:

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

Non so se sia buono o raccomandato, esiste una soluzione migliore per questo?


Forse play2 dovrebbe aggiungere qualcosa di simile ai frammenti di lift
Freewind

Risposte:


229

A mio avviso, il fatto che i modelli siano tipizzati staticamente è in realtà una buona cosa: sei sicuro che la chiamata al tuo modello non fallirà se si compila.

Tuttavia, in effetti aggiunge un po 'di boilerplate sui siti di chiamata. Ma puoi ridurlo (senza perdere i vantaggi della digitazione statica).

In Scala, vedo due modi per raggiungerlo: attraverso la composizione di azioni o usando parametri impliciti. In Java suggerisco di utilizzare la Http.Context.argsmappa per memorizzare valori utili e recuperarli dai modelli senza dover passare esplicitamente come parametri dei modelli.

Utilizzando parametri impliciti

Posiziona il menusparametro alla fine dei main.scala.htmlparametri del modello e contrassegnalo come "implicito":

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

Ora se hai modelli che chiamano questo modello principale, puoi avere il menusparametro implicitamente passato al maincompilatore Scala dal tuo compilatore Scala se è dichiarato come parametro implicito anche in questi modelli:

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

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

Ma se si desidera che venga passato in modo implicito dal controller, è necessario fornirlo come valore implicito, disponibile nell'ambito da cui si chiama il modello. Ad esempio, è possibile dichiarare il seguente metodo nel controller:

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

Quindi nelle tue azioni sarai in grado di scrivere solo quanto segue:

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

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

Puoi trovare ulteriori informazioni su questo approccio in questo post di blog e in questo esempio di codice .

Aggiornamento : qui è stato scritto anche un bel post sul blog che dimostra questo schema .

Usando la composizione delle azioni

In realtà, è spesso utile passare il RequestHeadervalore ai template (vedere ad esempio questo esempio ). Questo non aggiunge così tanto boilerplate al codice del tuo controller perché puoi facilmente scrivere azioni che ricevono un valore di richiesta implicito:

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

Pertanto, poiché i modelli spesso ricevono almeno questo parametro implicito, è possibile sostituirlo con un valore più ricco contenente, ad esempio, i menu. Puoi farlo usando il meccanismo di composizione delle azioni di Play 2.

Per fare ciò devi definire la tua Contextclasse, avvolgendo una richiesta sottostante:

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

Quindi è possibile definire il seguente ActionWithMenumetodo:

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

Quale può essere usato in questo modo:

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

E puoi prendere il contesto come parametro implicito nei tuoi modelli. Ad esempio per 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>

L'uso della composizione delle azioni ti consente di aggregare tutti i valori impliciti richiesti dai tuoi modelli in un singolo valore, ma d'altra parte puoi perdere un po 'di flessibilità ...

Utilizzo di Http.Context (Java)

Poiché Java non ha il meccanismo implicito di Scala o simili, se si desidera evitare di passare esplicitamente i parametri dei modelli, un modo possibile è quello di memorizzarli Http.Contextnell'oggetto che vive solo per la durata di una richiesta. Questo oggetto contiene un argsvalore di tipo Map<String, Object>.

Quindi, puoi iniziare scrivendo un intercettore, come spiegato nella documentazione :

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

Il metodo statico è solo una scorciatoia per recuperare i menu dal contesto corrente. Quindi annota il controller da mescolare con l' Menusinterceptor di azioni:

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

Infine, recupera il menusvalore dai tuoi modelli come segue:

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

Intendevi menu invece di menu? "Valori impliciti: Seq [Menu] = Menu.findAll"
Ben McCann

1
Inoltre, dato che il mio progetto è stato scritto solo in Java in questo momento, sarebbe possibile seguire il percorso di composizione dell'azione e avere solo il mio intercettore scritto in Scala, ma lasciare tutte le mie azioni scritte in Java?
Ben McCann

"menu" o "menu", non importa :), ciò che conta è il tipo: Seq [Menu]. Ho modificato la mia risposta e aggiunto un modello Java per gestire questo problema.
Julien Richard-Foy,

3
Nell'ultimo blocco di codice, si chiama @for(menu <- Menus.current()) {ma Menusnon viene mai definito (si inseriscono i menu (lettere minuscole):) ctx.args.put("menus", Menu.find.all());. C'è una ragione? Ti piace Play che lo trasforma in maiuscolo o qualcosa del genere?
Cyril N.

1
@ cx42net Esiste una Menusclasse definita (intercettore Java). @adis Sì, ma sei libero di memorizzarli in un altro posto, anche nella cache.
Julien Richard-Foy,

19

Il modo in cui lo faccio è semplicemente creare un nuovo controller per il mio menu / navigazione e chiamarlo dalla vista

Quindi puoi definire il tuo 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>
}

Quindi nella mia vista principale posso chiamarlo NavController:

@(title: String)(content: Html)
<!DOCTYPE html>
<html>
  <head>
    <title>@title</title>
  </head>
  <body>
     @NavController.nav
     @content
  </body>
</html>

Come dovrebbe essere NavController in Java? Non riesco a trovare un modo per fare in modo che il controller restituisca l'html.
Mika,

E così succede che trovi la soluzione subito dopo aver chiesto aiuto :) Il metodo del controller dovrebbe apparire così. public static play.api.templates.Html sidebar () {return (play.api.templates.Html) sidebar.render ("message"); }
Mika,

1
È una buona pratica chiamare il controller da una vista? Non voglio essere un pignolo, quindi chiedendo per vera curiosità.
0

Inoltre, non puoi fare cose in base alle richieste in questo modo, puoi ... ad esempio impostazioni specifiche dell'utente ...
0

14

Sostengo la risposta di Stian. Questo è un modo molto veloce per ottenere risultati.

Ho appena migrato da Java + Play1.0 a Java + Play2.0 e i modelli sono la parte più difficile finora, e il modo migliore che ho trovato per implementare un modello di base (per titolo, testa ecc.) È usando l'Http .Contesto.

C'è una sintassi molto bella che puoi ottenere con i tag.

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

dove 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 che puoi scrivere quanto segue in qualsiasi modello

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

Quindi è molto leggibile e piacevole.

Questo è il modo in cui ho scelto di andare. stian - un buon consiglio. È importante scorrere verso il basso per vedere tutte le risposte. :)

Passando variabili HTML

Non ho ancora capito come passare le variabili HTML.

@ (Titolo: String, il contenuto: Html)

tuttavia, so come passarli come blocco.

@ (Titolo: String) (contenuto: Html)

quindi potresti voler sostituire set.scala.html con

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

in questo modo puoi passare i blocchi HTML in questo modo

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

EDIT: effetto collaterale con la mia implementazione "Set"

Un caso d'uso comune è l'ereditarietà del modello in Play.

Hai base_template.html e poi page_template.html che estende base_template.html.

base_template.html potrebbe assomigliare a qualcosa del genere

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

mentre il modello di pagina potrebbe assomigliare a qualcosa

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

e poi hai una pagina (supponiamo login_page.html) che assomiglia

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

@page_template()

La cosa importante da notare qui è che si imposta "body" due volte. Una volta in "login_page.html" e poi in "page_template.html".

Sembra che questo inneschi un effetto collaterale, purché tu abbia implementato set.scala.html come ho suggerito sopra.

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

poiché la pagina mostrerebbe "login stuff ..." due volte perché put restituisce il valore che viene visualizzato la seconda volta che inseriamo la stessa chiave. (vedi mettere firma nei documenti java).

scala offre un modo migliore per modificare la mappa

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

che non causa questo effetto collaterale.


Nel controller scala, provo a fare non esiste un metodo put in play.mvc.Htt.Context.current (). Mi sto perdendo qualcosa?
0

prova a mettere il argscontesto after call attuale.
guy mograbi,

13

Se stai usando Java e vuoi solo il modo più semplice possibile senza dover scrivere un intercettore e usare l'annotazione @With, puoi anche accedere al contesto HTTP direttamente dal modello.

Ad esempio, se hai bisogno di una variabile disponibile da un modello, puoi aggiungerla al contesto HTTP con:

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

È quindi possibile accedervi dal modello con:

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

Ovviamente se sporchi i tuoi metodi con Http.Context.current (). Args.put ("", "") sei meglio di usare un intercettore, ma per casi semplici può fare il trucco.


Ciao Stian, per favore dai un'occhiata alla mia ultima modifica nella mia risposta. Ho appena scoperto che se usi "put" su args due volte con lo stesso tasto, otterrai un brutto effetto collaterale. Invece, dovresti usare ... args (chiave) = valore.
guy mograbi il

6

Dalla risposta di Stian, ho provato un approccio diverso. Questo funziona per me.

NEL CODICE JAVA

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

IN HTML HEAD MODELLO

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

E UTILIZZARE COME

@if(isOk) {
   <div>OK</div>
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.