Wie vermeide ich, Parameter überall in play2 zu übergeben?

125

In play1 erhalte ich normalerweise alle Daten in Aktionen und verwende sie direkt in Ansichten. Da wir die Parameter in der Ansicht nicht explizit deklarieren müssen, ist dies sehr einfach.

Aber in play2 habe ich festgestellt, dass wir alle Parameter (einschließlich request) im Kopf der Ansichten deklarieren müssen. Es wird sehr langweilig sein, alle Daten in Aktionen zu bekommen und sie in Ansichten zu übergeben.

Wenn ich beispielsweise Menüs anzeigen möchte, die aus der Datenbank auf der Startseite geladen werden, muss ich sie definieren 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>

Dann muss ich es auf jeder Unterseite deklarieren:

@(menus: Seq[Menu])

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

Dann muss ich die Menüs holen und sie weitergeben, um sie bei jeder Aktion anzuzeigen:

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

Im Moment ist es nur ein Parameter main.scala.html. Was ist, wenn es viele gibt?

Also entschied ich mich endlich für alle Menu.findAll()direkt im Blick:

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

Ich weiß nicht, ob es gut oder empfohlen ist. Gibt es dafür eine bessere Lösung?

Freilauf
quelle
Vielleicht sollte play2 so etwas wie Lift's Snippets hinzufügen
Freewind

Antworten:

229

Meiner Meinung nach ist die Tatsache, dass Vorlagen statisch typisiert sind, eine gute Sache: Sie können sicher sein, dass das Aufrufen Ihrer Vorlage beim Kompilieren nicht fehlschlägt.

Es wird jedoch tatsächlich ein Boilerplate auf den aufrufenden Sites hinzugefügt. Aber man kann es reduzieren (ohne statische Typisierung Vorteile zu verlieren).

In Scala sehe ich zwei Möglichkeiten, dies zu erreichen: durch Zusammenstellung von Aktionen oder durch Verwendung impliziter Parameter. In Java empfehle ich, die Http.Context.argsMap zu verwenden, um nützliche Werte zu speichern und aus den Vorlagen abzurufen, ohne explizit als Vorlagenparameter übergeben zu müssen.

Implizite Parameter verwenden

Platzieren Sie den menusParameter am Ende Ihrer main.scala.htmlVorlagenparameter und markieren Sie ihn als "implizit":

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

Wenn Sie nun Vorlagen haben, die diese Hauptvorlage aufrufen, können Sie den menusParameter mainvom Scala-Compiler implizit an die Vorlage übergeben lassen, wenn er auch in diesen Vorlagen als impliziter Parameter deklariert ist:

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

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

Wenn Sie jedoch möchten, dass es implizit von Ihrem Controller übergeben wird, müssen Sie es als impliziten Wert angeben, der in dem Bereich verfügbar ist, in dem Sie die Vorlage aufrufen. Sie können beispielsweise die folgende Methode in Ihrem Controller deklarieren:

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

Dann können Sie in Ihren Aktionen einfach Folgendes schreiben:

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

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

Weitere Informationen zu diesem Ansatz finden Sie in diesem Blogbeitrag und in diesem Codebeispiel .

Update : Ein schöner Blogeintrag dieses Muster demonstriert hat auch geschrieben worden hier .

Verwenden der Aktionskomposition

Tatsächlich ist es oft nützlich, den RequestHeaderWert an die Vorlagen zu übergeben (siehe z . B. dieses Beispiel ). Dadurch wird Ihrem Controller-Code nicht so viel Boilerplate hinzugefügt, da Sie problemlos Aktionen schreiben können, die einen impliziten Anforderungswert erhalten:

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

Da Vorlagen häufig mindestens diesen impliziten Parameter erhalten, können Sie ihn durch einen umfangreicheren Wert ersetzen, der z. B. Ihre Menüs enthält. Sie können das tun , indem Sie die Verwendung von Aktionen Zusammensetzung Mechanismus der Wiedergabe 2.

Dazu müssen Sie Ihre ContextKlasse definieren und eine zugrunde liegende Anforderung umschließen:

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

Dann können Sie die folgende ActionWithMenuMethode definieren :

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

Welches kann so verwendet werden:

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

Und Sie können den Kontext als impliziten Parameter in Ihren Vorlagen verwenden. ZB für 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>

Durch die Verwendung der Aktionskomposition können Sie alle impliziten Werte, die Ihre Vorlagen benötigen, zu einem einzigen Wert zusammenfassen. Andererseits können Sie an Flexibilität verlieren.

Verwenden von Http.Context (Java)

Da Java nicht über den impliziten Mechanismus von Scala oder ähnliches verfügt, können Sie Vorlagenparameter möglicherweise in dem Http.ContextObjekt speichern, das nur für die Dauer einer Anforderung gültig ist, wenn Sie vermeiden möchten, Vorlagenparameter explizit zu übergeben . Dieses Objekt enthält einen argsWert vom Typ Map<String, Object>.

Sie können also zunächst einen Interceptor schreiben, wie in der Dokumentation erläutert :

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

Die statische Methode ist nur eine Abkürzung, um die Menüs aus dem aktuellen Kontext abzurufen. Kommentieren Sie dann Ihren Controller mit dem MenusAction Interceptor:

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

Rufen Sie abschließend den menusWert wie folgt aus Ihren Vorlagen ab:

@(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
quelle
Meinten Sie Menüs statt Menü? "implizite Val-Menüs: Seq [Menu] = Menu.findAll"
Ben McCann
1
Da mein Projekt derzeit nur in Java geschrieben ist, wäre es dann möglich, den Weg der Aktionskomposition zu gehen und nur meinen Interceptor in Scala schreiben zu lassen, aber alle meine Aktionen in Java zu lassen?
Ben McCann
"Menü" oder "Menüs", es spielt keine Rolle :), was zählt, ist der Typ: Seq [Menü]. Ich habe meine Antwort bearbeitet und ein Java-Muster hinzugefügt, um dieses Problem zu beheben.
Julien Richard-Foy
3
Im letzten Codeblock rufen Sie auf, sind @for(menu <- Menus.current()) {aber Menusnie definiert (Sie setzen Menüs (Kleinbuchstaben) :) ctx.args.put("menus", Menu.find.all());. Gibt es einen Grund? Wie Play, das es in Großbuchstaben umwandelt oder so?
Cyril N.
1
@ cx42net Es ist eine MenusKlasse definiert (der Java-Interceptor). @adis Ja, aber Sie können sie an einem anderen Ort speichern, sogar im Cache.
Julien Richard-Foy
19

Ich erstelle einfach einen neuen Controller für meine Navigation / mein Menü und rufe ihn aus der Ansicht auf

So können Sie Folgendes definieren 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>
}

Dann kann ich das in meiner Hauptansicht so nennen NavController:

@(title: String)(content: Html)
<!DOCTYPE html>
<html>
  <head>
    <title>@title</title>
  </head>
  <body>
     @NavController.nav
     @content
  </body>
</html>
Darko
quelle
Wie soll der NavController in Java aussehen? Ich kann keinen Weg finden, den Controller dazu zu bringen, das HTML zurückzugeben.
Mika
Und so kommt es, dass Sie die Lösung direkt finden, nachdem Sie um Hilfe gebeten haben :) Die Controller-Methode sollte so aussehen. public static play.api.templates.Html sidebar () {return (play.api.templates.Html) sidebar.render ("message"); }
Mika
1
Ist dies eine gute Vorgehensweise, um den Controller aus einer Sicht aufzurufen? Ich möchte kein Stickler sein, also frage ich aus echter Neugier.
0fnt
Sie können auf diese Weise auch keine
Dinge
14

Ich unterstütze Stians Antwort. Dies ist ein sehr schneller Weg, um Ergebnisse zu erzielen.

Ich habe gerade von Java + Play1.0 auf Java + Play2.0 migriert und die Vorlagen sind der bisher schwierigste Teil. Die beste Möglichkeit, eine Basisvorlage (für Titel, Kopf usw.) zu implementieren, ist die Verwendung von HTTP .Kontext.

Es gibt eine sehr schöne Syntax, die Sie mit Tags erreichen können.

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

wo get.scala.html ist:

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

und set.scala.html ist:

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

bedeutet, dass Sie Folgendes in jede Vorlage schreiben können

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

Es ist also sehr lesbar und schön.

Dies ist der Weg, den ich gewählt habe. stian - guter rat. Beweist, dass es wichtig ist, nach unten zu scrollen, um alle Antworten zu sehen. :) :)

HTML-Variablen übergeben

Ich habe noch nicht herausgefunden, wie man HTML-Variablen übergibt.

@ (Titel: String, Inhalt: Html)

Ich weiß jedoch, wie ich sie als Block übergeben kann.

@ (Titel: String) (Inhalt: Html)

Vielleicht möchten Sie set.scala.html durch ersetzen

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

Auf diese Weise können Sie HTML-Blöcke wie folgt übergeben

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

EDIT: Nebeneffekt bei meiner "Set" -Implementierung

Ein häufiger Anwendungsfall für die Vererbung von Vorlagen in Play.

Sie haben eine base_template.html und dann eine page_template.html, die base_template.html erweitert.

base_template.html könnte ungefähr so ​​aussehen

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

während die Seitenvorlage ungefähr so ​​aussieht

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

und dann haben Sie eine Seite (nehmen wir login_page.html an), die aussieht

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

@page_template()

Das Wichtigste dabei ist, dass Sie "Körper" zweimal einstellen. Einmal in "login_page.html" und dann in "page_template.html".

Es scheint, dass dies einen Nebeneffekt auslöst, solange Sie set.scala.html wie oben vorgeschlagen implementieren.

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

da die Seite zweimal "login stuff ..." anzeigen würde, weil put den Wert zurückgibt, der beim zweiten Setzen desselben Schlüssels herausspringt. (Siehe Unterschrift in Java-Dokumenten einfügen).

scala bietet eine bessere Möglichkeit, die Karte zu ändern

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

was diese Nebenwirkung nicht verursacht.

Kerl Mograbi
quelle
In Scala Controller versuche ich, es gibt keine Put-Methode in play.mvc.Htt.Context.current (). Vermisse ich etwas
0fnt
Versuchen Sie, den argsKontext nach dem Aufruf auf den neuesten Stand zu bringen.
Kerl Mograbi
13

Wenn Sie Java verwenden und nur den einfachsten Weg suchen, ohne einen Interceptor schreiben und die Annotation @With verwenden zu müssen, können Sie auch direkt über die Vorlage auf den HTTP-Kontext zugreifen.

Wenn Sie beispielsweise eine Variable benötigen, die aus einer Vorlage verfügbar ist, können Sie sie dem HTTP-Kontext hinzufügen mit:

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

Sie können dann über die Vorlage darauf zugreifen mit:

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

Wenn Sie Ihre Methoden mit Http.Context.current (). Args.put ("", "") verunreinigen, ist es natürlich besser, einen Interceptor zu verwenden, aber in einfachen Fällen kann dies den Trick tun.

Stian
quelle
Hallo Stian, bitte schau dir meine letzte Bearbeitung in meiner Antwort an. Ich habe gerade herausgefunden, dass wenn Sie zweimal mit demselben Schlüssel "put" in args verwenden, Sie einen bösen Nebeneffekt bekommen. Sie sollten stattdessen ... args (Schlüssel) = Wert verwenden.
Kerl Mograbi
6

Aus Stians Antwort habe ich einen anderen Ansatz versucht. Das funktioniert bei mir.

IM JAVA-CODE

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

IN HTML TEMPLATE HEAD

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

UND VERWENDEN WIE

@if(isOk) {
   <div>OK</div>
}
Angelokh
quelle