web-dev-qa-db-fra.com

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

Dans play1, je récupère généralement toutes les données dans des actions, je les utilise directement dans les vues. Comme il n'est pas nécessaire de déclarer explicitement les paramètres en vue, c'est très simple.

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

Par exemple, si j'ai besoin d'afficher les menus chargés de la base de données dans 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 obtenir les menus et les passer pour voir toutes les actions:

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 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?

125
Freewind

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

Cependant, il ajoute effectivement un passe-partout sur les sites d’appel. Mais vous pouvez le réduire (sans perdre les avantages du typage statique).

Dans Scala, je vois deux manières de le réaliser: par la composition d’actions ou par l’utilisation de paramètres implicites. Dans Java je suggère d'utiliser le Http.Context.args map pour stocker les valeurs utiles et les récupérer à partir des modèles sans avoir à passer explicitement en tant que paramètres de modèles.

Utilisation de paramètres implicites

Placez le paramètre menus à la fin de votre main.scala.html paramè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 implicitement transmettre le paramètre menus au modèle 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") {
  ...
}

Toutefois, si vous souhaitez le transmettre implicitement à partir de votre contrôleur, vous devez le fournir sous forme de valeur implicite, disponible dans l'étendue à 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 article de blog de Nice démontrant que ce modèle a également été écrit ici .

Utiliser la composition d'actions

En fait, il est souvent utile de passer la valeur RequestHeader aux modèles (voir par exemple cet exemple ). Cela n'ajoute pas beaucoup de passe-passe 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 composition des actions de Play 2.

Pour ce faire, vous devez définir votre classe Context 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 méthode ActionWithMenu 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>

La composition d'actions vous permet d'agréger toutes les valeurs implicites requises par vos modèles en une seule valeur, mais vous pouvez toutefois perdre de la flexibilité…

Utiliser Http.Context (Java)

Depuis Java n’a pas de mécanisme d’implication de Scala ou similaire, si vous voulez éviter de transmettre explicitement les paramètres des modèles, vous pouvez les stocker dans le fichier Http.Context objet qui ne vit que pendant la durée d’une requête. Cet objet contient une valeur args 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 extraire les menus du contexte actuel. Puis annotez votre contrôleur pour qu’il soit mélangé avec l’intercepteur d’action Menus:

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

Enfin, récupérez la valeur 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>
229

Pour ce faire, il suffit 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 appeler cela NavController:

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

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 + Play 1.0 vers Java + Play 2.0 et les modèles sont la partie la plus difficile jusqu'à présent, et le meilleur moyen que j'ai trouvé d'implémenter un modèle de base (pour le titre, la tête, etc.) consiste à utiliser le protocole Http. .Le contexte.

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

Donc c'est très lisible et gentil.

C'est la voie que j'ai choisie. stian - bon conseil. Prouve qu'il est important de faire défiler l'écran 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 si

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

EDIT: effet secondaire avec mon implémentation "set"

Un héritage de gabarit us-it courant dans Play.

Vous avez un base_template.html et ensuite vous avez 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 "body" 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 le suggère ci-dessus.

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

comme la page afficherait "login stuff ..." à deux reprises car put renvoie la valeur qui apparaît la deuxième fois que nous mettons la même clé. (voir mettre la signature dans Java docs).

scala fournit un meilleur moyen de modifier la carte

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

qui ne cause pas cet effet secondaire.

14
guy mograbi

Si vous utilisez Java et souhaitez simplement le moyen le plus simple possible sans écrire d'intercepteur ni 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 à partir du modèle avec:

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

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

13
stian

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

IN Java CODE

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

DANS LA 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>
}
6
angelokh