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

125

Dans play1, j'obtiens généralement toutes les données dans les actions, je les utilise directement dans les vues. Comme nous n'avons pas besoin de déclarer explicitement les paramètres dans la vue, c'est très facile.

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

Par exemple, si j'ai besoin d'afficher des menus chargés à partir de la base de données sur la page d'accueil, je dois le définir dans 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 transmettre à voir 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, ce n'est qu'un paramètre main.scala.html, et s'il y en a plusieurs?

Alors enfin, 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?

Freewind
la source
Peut-être que play2 devrait ajouter quelque chose comme des extraits d'ascenseur
Freewind

Réponses:

229

À mon avis, le fait que les modèles soient typés statiquement est en fait une bonne chose: vous êtes assuré que l'appel de votre modèle n'échouera pas s'il se compile.

Cependant, il ajoute en effet un passe-partout sur les sites d'appel. Mais vous pouvez le réduire (sans perdre les avantages de la saisie statique).

Dans Scala, je vois deux façons d'y parvenir: via la composition d'actions ou en utilisant des paramètres implicites. En Java, je suggère d'utiliser la Http.Context.argscarte pour stocker des valeurs utiles et les récupérer à partir des modèles sans avoir à passer explicitement comme paramètres de modèles.

Utilisation de paramètres implicites

Placez le menusparamètre à la fin de vos main.scala.htmlparamètres de modèle et marquez-le 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 appelant ce modèle principal, vous pouvez faire passer le menusparamètre implicitement pour vous au mainmodèle par le compilateur Scala s'il est également déclaré en tant que paramètre implicite dans ces modèles:

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

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

Mais si vous voulez le faire passer implicitement de votre contrôleur, vous devez le fournir en tant que valeur implicite, disponible dans la portée à partir de laquelle 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 dans cet exemple de code .

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

Utilisation de la composition d'actions

En fait, il est souvent utile de transmettre la RequestHeadervaleur aux modèles (voir par exemple cet exemple ). Cela n'ajoute pas tellement de 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, étant donné que 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 le mécanisme de composition d'actions de Play 2.

Pour ce faire, vous devez définir votre Contextclasse en encapsulant une requête sous-jacente:

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

Ensuite, vous pouvez définir la ActionWithMenuméthode suivante :

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 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 dont vos modèles ont besoin en une seule valeur, mais d'un autre côté, vous pouvez perdre une certaine flexibilité ...

Utilisation de Http.Context (Java)

Puisque Java n'a pas de mécanisme d'implication de Scala ou similaire, si vous voulez éviter de passer explicitement les paramètres des modèles, un moyen possible est de les stocker dans l' Http.Contextobjet qui ne vit que pendant la durée d'une requête. Cet objet contient une argsvaleur de type Map<String, Object>.

Ainsi, vous pouvez commencer par écrire un intercepteur, comme expliqué dans 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 n'est qu'un raccourci pour récupérer les menus du contexte actuel. Ensuite, annotez votre contrôleur pour qu'il soit mélangé avec l' Menusintercepteur d'action:

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

Enfin, récupérez la menusvaleur 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>
Julien Richard-Foy
la source
Vouliez-vous dire menus au lieu de menu? "implicit val menus: Seq [Menu] = Menu.findAll"
Ben McCann
1
De plus, étant donné que mon projet est écrit uniquement en Java pour le moment, serait-il possible d'emprunter la voie de composition d'action et de faire écrire uniquement mon intercepteur en Scala, mais de laisser toutes mes actions écrites en Java?
Ben McCann
"menu" ou "menus", peu importe :), ce qui compte c'est le type: Seq [Menu]. J'ai modifié ma réponse et ajouté un modèle Java pour gérer ce problème.
Julien Richard-Foy
3
Dans le dernier bloc de code, vous appelez @for(menu <- Menus.current()) {maisMenus n'est jamais défini (vous mettez des menus (minuscules) :) ctx.args.put("menus", Menu.find.all());. Y a-t-il une raison? Comme Play qui le transforme en majuscules ou quelque chose comme ça?
Cyril N.
1
@ cx42net Une Menusclasse est définie (l'intercepteur Java). @adis Oui mais vous êtes libre de les stocker ailleurs, même dans le cache.
Julien Richard-Foy
19

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

Ainsi, vous pouvez 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 appeler ça NavController :

@(title: String)(content: Html)
<!DOCTYPE html>
<html>
  <head>
    <title>@title</title>
  </head>
  <body>
     @NavController.nav
     @content
  </body>
</html>
Darko
la source
À quoi le NavController est-il censé ressembler en Java? Je ne trouve pas de moyen de faire en sorte que le contrôleur renvoie le code HTML.
Mika
Et il se trouve 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"); }
Mika
1
Est-ce une bonne pratique d'appeler le contrôleur depuis une vue? Je ne veux pas être un maniaque, donc demander par véritable curiosité.
0fnt le
De plus, vous ne pouvez pas faire des choses basées sur des demandes de cette manière, pouvez-vous .., par exemple des paramètres spécifiques à l'utilisateur ..
0fnt
14

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 vers Java + Play2.0 et les modèles sont la partie la plus difficile à ce jour, et le meilleur moyen que j'ai trouvé pour implémenter un modèle de base (pour le titre, la tête, etc.) est d'utiliser le Http .Le contexte.

Il existe une très belle syntaxe que vous pouvez obtenir avec des 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 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")

C'est donc très lisible et agréable.

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

Passer des variables HTML

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

@ (titre: Chaîne, contenu: Html)

cependant, je sais comment les passer en bloc.

@ (titre: String) (contenu: Html)

vous voudrez peut-être 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 ça

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

EDIT: Effet secondaire avec mon implémentation "Set"

Héritage de modèle informatique de cas d'utilisation courant dans Play.

Vous avez un base_template.html, puis page_template.html qui étend base_template.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 peut ressembler à quelque chose comme

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

et puis vous avez une page (supposons login_page.html) qui ressemble à

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

@page_template()

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

Il semble que cela déclenche un effet secondaire, tant que vous implémentez set.scala.html comme je l'ai suggéré ci-dessus.

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

comme la page afficherait deux fois "login stuff ..." parce que put renvoie la valeur qui apparaît la deuxième fois que nous mettons la même clé. (voir mettre la signature dans les documents java).

scala fournit un meilleur moyen de modifier la carte

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

ce qui ne provoque pas cet effet secondaire.

mec mograbi
la source
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 je manque quelque chose?
0fnt le
essayez de mettre le argscontexte après l'appel actuel.
guy mograbi
13

Si vous utilisez Java et que vous voulez simplement la manière la 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 à partir d'un modèle, vous pouvez l'ajouter au contexte HTTP avec:

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

Vous pouvez ensuite y accéder depuis le modèle avec:

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

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

stian
la source
Salut stian, veuillez jeter un œil à ma dernière modification dans ma réponse. Je viens de découvrir que si vous utilisez deux fois "mettre" sur les arguments avec la même touche, vous obtenez un effet secondaire désagréable. Vous devriez utiliser ... args (key) = value à la place.
guy mograbi
6

D'après 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);

EN TÊTE DE MODÈLE HTML

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

ET UTILISER COMME

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