125 votes

Comment éviter de passer des paramètres partout dans play2 ?

Dans la pièce 1, je récupère généralement toutes les données dans les actions et je les utilise directement dans les vues. Puisque nous n'avons pas besoin de déclarer explicitement les paramètres dans les vues, c'est très facile.

Mais dans play2, j'ai trouvé que nous devons déclarer tous les paramètres (y compris request ) dans l'en-tête des vues, il sera très ennuyeux de récupérer toutes les données dans les actions et de les passer dans les vues.

Par exemple, si j'ai besoin d'afficher des menus qui sont chargés à partir de la base de données dans la page d'accueil, je dois le définir dans le fichier 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>

Ensuite, je dois le déclarer dans chaque sous-page :

@(menus: Seq[Menu])

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

Ensuite, je dois récupérer les menus et les passer à la vue dans chaque action :

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

Pour l'instant, il n'y a qu'un seul paramètre dans main.scala.html et s'il y en a plusieurs ?

Alors finalement, j'ai décidé de tout Menu.findAll() directement en vue :

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

Je ne sais pas si c'est bon ou recommandé, y a-t-il une meilleure solution pour cela ?

0 votes

Peut-être que play2 devrait ajouter quelque chose comme les snippets de lift.

229voto

Julien Richard-Foy Points 6930

À mon avis, le fait que les modèles soient typés de manière statique est en fait une bon une chose : vous avez la garantie que l'appel de votre modèle n'échouera pas s'il compile.

Cependant, cela ajoute effectivement un peu de "boilerplate" sur les sites appelants. Mais vous pouvez le réduire (sans perdre les avantages du typage statique).

En Scala, je vois deux façons d'y parvenir : par la composition d'actions ou par l'utilisation de paramètres implicites. En Java, je suggère d'utiliser la méthode Http.Context.args map pour stocker des valeurs utiles et les récupérer dans les modèles sans avoir à passer explicitement les paramètres des modèles.

Utilisation de paramètres implicites

Placez le menus à la fin de votre main.scala.html paramètres du modèle et le marquer comme "implicite" :

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

Maintenant, si vous avez des modèles qui appellent ce modèle principal, vous pouvez avoir l'attribut menus passé implicitement pour vous à la main par le compilateur Scala s'il est également déclaré comme paramètre implicite dans ces modèles :

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

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

Mais si vous voulez qu'elle soit implicitement transmise par votre contrôleur, vous devez la fournir comme une valeur implicite, disponible dans la portée d'où vous appelez le modèle. Par exemple, vous pouvez déclarer la méthode suivante dans votre contrôleur :

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

Ensuite, dans vos actions, vous pourrez simplement écrire ce qui suit :

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

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

Vous pouvez trouver plus d'informations sur cette approche dans cet article de blog et en cet exemple de code .

Mise à jour : Un bon article de blog démontrant ce modèle a également été écrit. aquí .

Utilisation de la composition des actions

En fait, il est souvent utile de passer l'option RequestHeader aux modèles (voir par exemple cet échantillon ). Cela n'ajoute pas beaucoup de texte passe-partout au code de votre contrôleur, car vous pouvez facilement écrire des actions recevant une valeur de demande implicite :

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

Ainsi, puisque les modèles reçoivent souvent au moins ce paramètre implicite, vous pouvez le remplacer par une valeur plus riche contenant par exemple vos menus. Vous pouvez le faire en utilisant l'option composition des actions mécanisme de jeu 2.

Pour ce faire, vous devez définir votre Context qui englobe une requête sous-jacente :

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

Vous pouvez ensuite définir les éléments suivants ActionWithMenu méthode :

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

Qui peut être utilisé comme ceci :

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

Et vous pouvez prendre le contexte comme un paramètre implicite dans vos modèles. Par exemple, pour 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'utilisation de la composition d'actions vous permet d'agréger toutes les valeurs implicites que vos modèles requièrent en une seule valeur, mais d'un autre côté vous pouvez perdre une certaine flexibilité

Utilisation de Http.Context (Java)

Étant donné que Java ne dispose pas du mécanisme d'implicites de Scala ou d'un mécanisme similaire, si vous souhaitez éviter de passer explicitement les paramètres des modèles, une solution possible consiste à les stocker dans le fichier Http.Context qui ne vit que le temps d'une requête. Cet objet contient un args valeur de type Map<String, Object> .

Ainsi, vous pouvez commencer par écrire un intercepteur, comme expliqué dans la rubrique la documentation :

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

La méthode statique est juste un raccourci pour récupérer les menus du contexte actuel. Ensuite, annotez votre contrôleur pour qu'il soit mélangé avec la méthode Menus intercepteur d'action :

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

Enfin, récupérez le menus de vos modèles comme suit :

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

0 votes

Vouliez-vous dire menus au lieu de menu ? "implicite val menus : Seq[Menu] = Menu.findAll"

1 votes

De plus, comme mon projet n'est actuellement écrit qu'en Java, serait-il possible de suivre la voie de la composition d'actions et de n'avoir que mon intercepteur écrit en Scala, mais de laisser toutes mes actions écrites en Java ?

0 votes

"menu" ou "menus", cela n'a pas d'importance :), ce qui compte c'est le type : Seq[Menu]. J'ai édité ma réponse et ajouté un pattern Java pour gérer ce problème.

19voto

Darko Points 171

La façon dont je le fais est de créer un nouveau contrôleur pour ma navigation/menu et de l'appeler depuis la vue.

Vous pouvez donc définir votre 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>
}

Ensuite, dans ma vue principale, je peux l'appeler NavController :

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

0 votes

Comment le NavController est censé se présenter en Java ? Je n'arrive pas à trouver un moyen de faire en sorte que le contrôleur renvoie le html.

0 votes

Et c'est ainsi que vous trouvez la solution juste après avoir demandé de l'aide :) La méthode du contrôleur devrait ressembler à ceci. public static play.api.templates.Html sidebar() { return (play.api.templates.Html) sidebar.render("message") ; }

1 votes

Est-ce une bonne pratique d'appeler le contrôleur depuis une vue ? Je ne veux pas être pointilleux, je demande donc par pure curiosité.

14voto

guy mograbi Points 2418

Je soutiens la réponse de Stian. C'est un moyen très rapide d'obtenir des résultats.

Je viens de migrer de Java+Play1.0 à Java+Play2.0 et les modèles sont la partie la plus difficile jusqu'à présent, et la meilleure façon que j'ai trouvée pour mettre en œuvre un modèle de base (pour title, head etc.) est d'utiliser le Http.Context.

Il existe une très belle syntaxe que vous pouvez réaliser avec les balises.

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

où get.scala.html est :

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

et set.scala.html l'est :

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

signifie que vous pouvez écrire ce qui suit dans n'importe quel modèle

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

Il est donc très lisible et agréable.

C'est la voie que j'ai choisie. stian - bon conseil. Cela prouve qu'il est important de faire défiler la page pour voir toutes les réponses :)

Passage de variables HTML

Je n'ai pas encore trouvé comment passer des variables Html.

@(title:String,content:Html)

cependant, je sais comment les passer en bloc.

@(title:String)(content:Html)

vous pouvez donc remplacer set.scala.html par

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

de cette façon, vous pouvez passer des blocs Html comme suit

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

EDIT : Effet secondaire de l'implémentation de mon "set".

L'héritage des modèles dans Play est un cas d'utilisation courant.

Vous avez un modèle de base.html et ensuite un modèle de page.html qui étend le modèle de base.html.

base_template.html pourrait ressembler à quelque chose comme

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

tandis que le modèle de page pourrait ressembler à quelque chose comme

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

et ensuite vous avez une page (disons login_page.html) qui ressemble à ceci

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

@page_template()

La chose importante à noter ici est que vous définissez "body" deux fois. Une fois dans "login_page.html" et ensuite dans "page_template.html".

Il semble que cela déclenche un effet de bord, pour autant que vous implémentiez set.scala.html comme je l'ai suggéré ci-dessus.

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

car la page afficherait deux fois "login stuff..." parce que put retourne la valeur qui sort la deuxième fois que l'on met la même clé. (voir la signature de put dans la documentation java).

scala fournit un meilleur moyen de modifier la carte

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

qui ne provoque pas cet effet secondaire.

0 votes

Dans le contrôleur scala, j'essaie de faire il n'y a pas de méthode put dans play.mvc.Htt.Context.current(). Est-ce que j'ai raté quelque chose ?

0 votes

Essayez de mettre le args après avoir appelé le contexte actuel.

13voto

stian Points 1731

Si vous utilisez Java et que vous voulez simplement le moyen le plus simple possible sans avoir à écrire un intercepteur et à utiliser l'annotation @With, vous pouvez également accéder au contexte HTTP directement à partir du modèle.

Par exemple, si vous avez besoin d'une variable disponible dans un modèle, vous pouvez l'ajouter au contexte HTTP avec :

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

Vous pouvez ensuite y accéder à partir du modèle avec :

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

Évidemment, si vous émaillez vos méthodes de Http.Context.current().args.put("",""), vous feriez mieux d'utiliser un intercepteur, mais pour les cas simples, cela peut faire l'affaire.

0 votes

Salut stian, s'il vous plaît regardez ma dernière modification dans ma réponse. Je viens de découvrir que si vous utilisez "put" dans args deux fois avec la même clé, vous obtenez un effet secondaire désagréable. Vous devriez utiliser ...args(key)=value à la place.

6voto

angelokh Points 1946

À partir de la réponse de Stian, j'ai essayé une approche différente. Cela fonctionne pour moi.

EN CODE JAVA

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

DANS L'EN-TÊTE DU MODÈLE HTML

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

ET UTILISER LIKE

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X