Bagaimana cara menghindari melewati parameter di mana-mana di play2?

125

Di play1, saya biasanya mendapatkan semua data dalam aksi, menggunakannya langsung dalam tampilan. Karena kita tidak perlu mendeklarasikan parameter secara eksplisit, ini sangat mudah.

Namun dalam play2, saya menemukan bahwa kita harus mendeklarasikan semua parameter (termasuk request) di kepala view, akan sangat membosankan untuk mendapatkan semua data dalam aksi dan meneruskannya ke view.

Misalnya, jika saya perlu menampilkan menu yang diambil dari basis data di halaman depan, saya harus mendefinisikannya di 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>

Maka saya harus mendeklarasikannya di setiap halaman:

@(menus: Seq[Menu])

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

Maka saya harus mendapatkan menu dan meneruskannya untuk melihat dalam setiap tindakan:

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

Untuk saat ini hanya ada satu parameter main.scala.html, bagaimana jika ada banyak?

Jadi akhirnya, saya memutuskan untuk Menu.findAll()secara langsung:

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

Saya tidak tahu apakah itu baik atau disarankan, apakah ada solusi yang lebih baik untuk ini?

Jalan bebas
sumber
Mungkin play2 harus menambahkan sesuatu seperti cuplikan lift
Freewind

Jawaban:

229

Menurut pendapat saya, fakta bahwa templat diketik secara statis sebenarnya baik hal yang : Anda dijamin bahwa memanggil templat Anda tidak akan gagal jika dikompilasi.

Namun, itu memang menambahkan beberapa boilerplate di situs panggilan. Tapi Anda bisa menguranginya (tanpa kehilangan keunggulan pengetikan statis).

Di Scala, saya melihat dua cara untuk mencapainya: melalui komposisi aksi atau dengan menggunakan parameter implisit. Di Jawa saya sarankan menggunakanHttp.Context.args peta untuk menyimpan nilai-nilai berguna dan mengambilnya dari template tanpa harus secara eksplisit lulus sebagai parameter template.

Menggunakan parameter implisit

Tempatkan menusparameter di akhir main.scala.htmlparameter template Anda dan tandai sebagai "implisit":

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

Sekarang jika Anda memiliki template yang memanggil template utama ini, Anda dapat memiliki menus parameter yang secara implisit dilimpahkan untuk Anda ke maintemplat oleh kompilator Scala jika dinyatakan sebagai parameter implisit dalam templat ini juga:

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

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

Tetapi jika Anda ingin secara implisit dilewatkan dari pengontrol Anda, Anda harus memberikannya sebagai nilai implisit, tersedia dalam ruang lingkup tempat Anda memanggil templat. Misalnya, Anda dapat mendeklarasikan metode berikut di controller Anda:

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

Maka dalam tindakan Anda, Anda hanya dapat menulis yang berikut ini:

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

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

Anda dapat menemukan informasi lebih lanjut tentang pendekatan ini di posting blog ini dan di contoh kode ini .

Memperbarui : Posting blog yang bagus menunjukkan pola ini juga telah ditulis di sini .

Menggunakan komposisi aksi

Sebenarnya, sering berguna untuk meneruskan RequestHeadernilai ke templat (lihat misalnya sampel ini ). Ini tidak menambah terlalu banyak pelat ke kode pengontrol Anda karena Anda dapat dengan mudah menulis tindakan yang menerima nilai permintaan implisit:

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

Jadi, karena templat sering menerima setidaknya parameter implisit ini, Anda bisa menggantinya dengan nilai yang lebih kaya yang mengandung misalnya menu Anda. Anda dapat melakukannya dengan menggunakan komposisi aksi mekanisme dari Play 2.

Untuk melakukan itu Anda harus mendefinisikan Contextkelas Anda , membungkus permintaan yang mendasarinya:

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

Kemudian Anda dapat menentukan ActionWithMenumetode berikut :

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

Yang bisa digunakan seperti ini:

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

Dan Anda dapat mengambil konteks sebagai parameter implisit dalam template Anda. Misalnya untuk 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>

Menggunakan komposisi tindakan memungkinkan Anda untuk mengumpulkan semua nilai implisit yang diperlukan template Anda menjadi nilai tunggal, tetapi di sisi lain Anda dapat kehilangan beberapa fleksibilitas ...

Menggunakan Http.Context (Java)

Karena Java tidak memiliki mekanisme implisit Scala atau yang serupa, jika Anda ingin menghindari untuk secara eksplisit melewatkan parameter templat, cara yang mungkin adalah dengan menyimpannya di Http.Contextobjek yang hanya hidup selama durasi permintaan. Objek ini mengandung argsnilai tipeMap<String, Object> .

Dengan demikian, Anda bisa mulai dengan menulis interseptor, seperti yang dijelaskan dalam dokumentasi :

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

Metode statis hanyalah singkatan untuk mengambil menu dari konteks saat ini. Lalu beri catatan pengontrol Anda untuk dicampur dengan Menustindakan pencegat:

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

Terakhir, ambil menusnilai dari templat Anda sebagai berikut:

@(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
sumber
Apakah maksud Anda menu alih-alih menu? "menu val implisit: Seq [Menu] = Menu.findAll"
Ben McCann
1
Juga, karena proyek saya hanya ditulis di Jawa sekarang, apakah mungkin untuk menempuh rute komposisi tindakan dan baru saja interceptor saya ditulis dalam Scala, tetapi meninggalkan semua tindakan saya ditulis di Jawa?
Ben McCann
"menu" atau "menu", tidak masalah :), yang penting adalah jenisnya: Seq [Menu]. Saya mengedit jawaban saya dan menambahkan pola Java untuk menangani masalah ini.
Julien Richard-Foy
3
Di blok kode terakhir, Anda menelepon @for(menu <- Menus.current()) {tetapi Menustidak pernah ditentukan (Anda meletakkan menu (huruf kecil) ctx.args.put("menus", Menu.find.all());:). Apakah ada alasan? Seperti Play yang mengubahnya menjadi huruf besar atau semacamnya?
Cyril N.
1
@ cx42net Ada Menuskelas yang ditentukan (pencegat Java). @adis Ya tapi Anda bebas menyimpannya di tempat lain, bahkan di cache.
Julien Richard-Foy
19

Cara saya melakukannya, adalah dengan hanya membuat controller baru untuk menu / navigasi saya dan memanggilnya dari tampilan

Jadi, Anda dapat menentukan 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>
}

Kemudian di tampilan utama saya, saya bisa menyebutnya NavController:

@(title: String)(content: Html)
<!DOCTYPE html>
<html>
  <head>
    <title>@title</title>
  </head>
  <body>
     @NavController.nav
     @content
  </body>
</html>
Darko
sumber
Bagaimana seharusnya NavController terlihat di Jawa? Saya tidak dapat menemukan cara untuk membuat controller untuk mengembalikan html.
Mika
Dan kebetulan Anda menemukan solusinya setelah meminta bantuan :) Metode pengontrol akan terlihat seperti ini. sidebar play.api.templates.Html statis publik () {return (play.api.templates.Html) sidebar.render ("message"); }
Mika
1
Apakah ini praktik yang baik untuk memanggil pengontrol dari tampilan? Saya tidak ingin menjadi orang yang ngotot, jadi bertanya karena penasaran.
0fnt
Juga, Anda tidak dapat melakukan hal-hal berdasarkan permintaan dengan cara ini, bisakah Anda .., misalnya pengaturan spesifik pengguna ..
0fnt
14

Saya mendukung jawaban stian. Ini adalah cara yang sangat cepat untuk mendapatkan hasil.

Saya baru saja bermigrasi dari Java + Play1.0 ke Java + Play2.0 dan templat adalah bagian tersulit sejauh ini, dan cara terbaik yang saya temukan untuk mengimplementasikan templat dasar (untuk judul, head dll.) Adalah dengan menggunakan Http .Konteks.

Ada sintaks yang sangat bagus yang bisa Anda capai dengan tag.

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

di mana get.scala.html adalah:

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

dan set.scala.html adalah:

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

berarti Anda dapat menulis yang berikut dalam templat apa pun

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

Jadi itu sangat enak dibaca dan bagus.

Ini adalah cara saya memilih untuk pergi. stian - saran yang bagus. Buktikan bahwa penting untuk menggulir ke bawah untuk melihat semua jawaban. :)

Melewati variabel HTML

Saya belum menemukan cara untuk melewatkan variabel Html.

@ (judul: String, konten: Html)

Namun, saya tahu cara melewatinya sebagai blok.

@ (judul: String) (konten: Html)

jadi Anda mungkin ingin mengganti set.scala.html dengan

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

dengan cara ini Anda dapat melewati blok Html seperti itu

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

EDIT: Efek Samping Dengan Implementasi "Set" Saya

Kasus penggunaan umum itu templat warisan di Play.

Anda memiliki base_template.html dan kemudian Anda memiliki page_template.html yang meluas base_template.html.

base_template.html mungkin terlihat seperti

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

sementara templat laman mungkin terlihat seperti

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

dan kemudian Anda memiliki halaman (mari kita asumsikan login_page.html) yang terlihat seperti

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

@page_template()

Yang penting untuk diperhatikan di sini adalah Anda mengatur "tubuh" dua kali. Setelah di "login_page.html" dan kemudian di "page_template.html".

Tampaknya ini memicu efek samping, selama Anda menerapkan set.scala.html seperti yang saya sarankan di atas.

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

karena halaman akan menampilkan "barang masuk ..." dua kali karena put mengembalikan nilai yang muncul kedua kalinya kami memasukkan kunci yang sama. (lihat menaruh tanda tangan di java docs).

scala menyediakan cara yang lebih baik untuk memodifikasi peta

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

yang tidak menyebabkan efek samping ini.

orang mograbi
sumber
Dalam scala controller, saya coba lakukan tidak ada metode put di play.mvc.Htt.Context.current (). Apakah saya melewatkan sesuatu?
0fnt
coba letakkan argskonteks panggilan setelah panggilan saat ini.
guy mograbi
13

Jika Anda menggunakan Java dan hanya ingin cara termudah yang mungkin tanpa harus menulis interseptor dan menggunakan penjelasan @Dengan, Anda juga dapat mengakses konteks HTTP langsung dari templat.

Misalnya, jika Anda memerlukan variabel yang tersedia dari templat, Anda dapat menambahkannya ke konteks HTTP dengan:

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

Anda kemudian dapat mengaksesnya dari templat dengan:

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

Tentunya jika Anda mengotori metode Anda dengan Http.Context.current (). Args.put ("", "") Anda lebih baik menggunakan pencegat, tetapi untuk kasus-kasus sederhana itu dapat melakukan trik.

stian
sumber
Hai stian, silakan lihat edit terakhir saya di jawaban saya. Saya baru tahu bahwa jika Anda menggunakan "memasukkan" ke dalam args dua kali dengan kunci yang sama, Anda mendapatkan efek samping yang buruk. Anda harus menggunakan ... args (key) = value sebagai gantinya.
guy mograbi
6

Dari jawaban Stian, saya mencoba pendekatan yang berbeda. Ini bekerja untuk saya.

DALAM KODE JAWA

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

DALAM KEPALA TEMPLATE HTML

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

DAN GUNAKAN SEPERTI

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