จะหลีกเลี่ยงการส่งผ่านพารามิเตอร์ทุกที่ใน play2 ได้อย่างไร?


125

ใน play1 ฉันมักจะได้รับข้อมูลทั้งหมดในการดำเนินการใช้โดยตรงในมุมมอง เนื่องจากเราไม่จำเป็นต้องประกาศพารามิเตอร์อย่างชัดเจนในมุมมองจึงทำได้ง่ายมาก

แต่ใน play2 ฉันพบว่าเราต้องประกาศพารามิเตอร์ทั้งหมด (รวมถึงrequest) ในส่วนหัวของมุมมองมันจะน่าเบื่อมากที่จะรับข้อมูลทั้งหมดในการดำเนินการและส่งต่อไปยังมุมมอง

ตัวอย่างเช่นหากฉันต้องการแสดงเมนูที่โหลดจากฐานข้อมูลในหน้าแรกฉันต้องกำหนดใน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>

จากนั้นฉันต้องประกาศในทุกหน้าย่อย:

@(menus: Seq[Menu])

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

จากนั้นฉันต้องรับเมนูและส่งต่อเพื่อดูในทุกการกระทำ:

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

ตอนนี้เป็นเพียงพารามิเตอร์เดียวเท่านั้นmain.scala.htmlถ้ามีหลายตัวล่ะ?

ในที่สุดฉันตัดสินใจที่จะดูทั้งหมดMenu.findAll()โดยตรง:

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

ไม่รู้ว่าดีหรือแนะนำมีวิธีไหนที่ดีกว่านี้ไหม


บางที play2 ควรเพิ่มบางอย่างเช่นตัวอย่างของลิฟท์
Freewind

คำตอบ:


229

ในความคิดของฉันความจริงที่ว่าเทมเพลตถูกพิมพ์แบบคงที่นั้นเป็นสิ่งที่ดีจริงๆคุณรับประกันได้ว่าการเรียกใช้เทมเพลตของคุณจะไม่ล้มเหลวหากรวบรวม

อย่างไรก็ตามมันเพิ่มต้นแบบบางอย่างในไซต์การโทร แต่คุณสามารถลดได้ (โดยไม่สูญเสียข้อดีของการพิมพ์แบบคงที่)

ใน Scala ฉันเห็นสองวิธีในการบรรลุเป้าหมาย: ผ่านองค์ประกอบการกระทำหรือโดยใช้พารามิเตอร์นัย ใน Java ฉันขอแนะนำให้ใช้Http.Context.argsแผนที่เพื่อเก็บค่าที่เป็นประโยชน์และเรียกคืนจากเทมเพลตโดยไม่ต้องส่งผ่านเป็นพารามิเตอร์เทมเพลตอย่างชัดเจน

การใช้พารามิเตอร์โดยนัย

วางmenusพารามิเตอร์ไว้ที่ส่วนท้ายของmain.scala.htmlพารามิเตอร์เทมเพลตของคุณและทำเครื่องหมายเป็น "นัย":

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

ตอนนี้หากคุณมีเทมเพลตที่เรียกใช้เทมเพลตหลักนี้คุณสามารถส่งmenusพารามิเตอร์โดยปริยายให้คุณไปยังmainเทมเพลตโดยคอมไพลเลอร์ Scala ได้หากมีการประกาศเป็นพารามิเตอร์โดยนัยในเทมเพลตเหล่านี้ด้วย:

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

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

แต่ถ้าคุณต้องการส่งผ่านตัวควบคุมของคุณโดยปริยายคุณจะต้องระบุเป็นค่าโดยปริยายซึ่งมีอยู่ในขอบเขตที่คุณเรียกใช้เทมเพลต ตัวอย่างเช่นคุณสามารถประกาศวิธีการต่อไปนี้ในคอนโทรลเลอร์ของคุณ:

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

จากนั้นในการกระทำของคุณคุณจะสามารถเขียนสิ่งต่อไปนี้:

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

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

คุณสามารถค้นหาข้อมูลเพิ่มเติมเกี่ยวกับแนวทางนี้ได้ในโพสต์บล็อกนี้และในตัวอย่างโค้ดนี้

ปรับปรุง : โพสต์บล็อกที่ดีแสดงให้เห็นถึงรูปแบบนี้ยังได้รับการเขียนที่นี่

การใช้องค์ประกอบการกระทำ

ที่จริงแล้วการส่งผ่านRequestHeaderค่าไปยังเทมเพลตมักจะมีประโยชน์(ดูตัวอย่างเช่นนี้ ) สิ่งนี้ไม่ได้เพิ่มต้นแบบลงในโค้ดคอนโทรลเลอร์ของคุณมากนักเนื่องจากคุณสามารถเขียนการดำเนินการที่ได้รับค่าคำขอโดยปริยาย:

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

ดังนั้นเนื่องจากเทมเพลตมักจะได้รับพารามิเตอร์โดยนัยนี้เป็นอย่างน้อยคุณจึงสามารถแทนที่ด้วยค่าที่มากขึ้นซึ่งมีเช่นเมนูของคุณ คุณสามารถทำได้โดยใช้กลไกองค์ประกอบการกระทำของ Play 2

ในการทำเช่นนั้นคุณต้องกำหนดContextชั้นเรียนของคุณโดยสรุปคำขอพื้นฐาน:

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

จากนั้นคุณสามารถกำหนดActionWithMenuวิธีการต่อไปนี้:

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

ซึ่งสามารถใช้ได้ดังนี้:

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

และคุณสามารถใช้บริบทเป็นพารามิเตอร์โดยนัยในเทมเพลตของคุณ เช่นสำหรับ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>

การใช้องค์ประกอบการดำเนินการช่วยให้คุณสามารถรวมค่าโดยนัยทั้งหมดที่แม่แบบของคุณต้องการให้เป็นค่าเดียว แต่ในทางกลับกันคุณอาจสูญเสียความยืดหยุ่นไปบ้าง ...

การใช้ Http.Context (Java)

เนื่องจาก Java ไม่มีกลไกโดยนัยของ Scala หรือสิ่งที่คล้ายกันหากคุณต้องการหลีกเลี่ยงการส่งผ่านพารามิเตอร์เทมเพลตอย่างชัดเจนวิธีที่เป็นไปได้คือการจัดเก็บไว้ในHttp.Contextวัตถุที่มีชีวิตอยู่ในช่วงเวลาของคำขอเท่านั้น วัตถุนี้มีค่าของชนิดargsMap<String, Object>

ดังนั้นคุณสามารถเริ่มต้นด้วยการเขียนตัวสกัดกั้นตามที่อธิบายไว้ในเอกสารประกอบ :

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

วิธีการคงที่เป็นเพียงการชวเลขเพื่อดึงเมนูจากบริบทปัจจุบัน จากนั้นใส่คำอธิบายประกอบตัวควบคุมของคุณเพื่อผสมกับตัวMenusดักจับการกระทำ:

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

สุดท้ายดึงmenusค่าจากเทมเพลตของคุณดังนี้:

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

คุณหมายถึงเมนูแทนเมนูหรือไม่? "implicit val เมนู: Seq [Menu] = Menu.findAll"
Ben McCann

1
นอกจากนี้เนื่องจากโครงการของฉันเขียนเฉพาะใน Java ในตอนนี้ฉันจะไปเส้นทางองค์ประกอบการกระทำได้ไหมและมีเพียงตัวสกัดกั้นของฉันที่เขียนใน Scala แต่ปล่อยให้การกระทำทั้งหมดของฉันเขียนใน Java
Ben McCann

"เมนู" หรือ "เมนู" ไม่สำคัญ :) สิ่งที่สำคัญคือประเภท: Seq [เมนู] ฉันแก้ไขคำตอบและเพิ่มรูปแบบ Java เพื่อจัดการปัญหานี้
Julien Richard-Foy

3
ในบล็อกโค้ดสุดท้ายคุณเรียก@for(menu <- Menus.current()) {แต่Menusไม่เคยกำหนด (คุณใส่เมนู (ตัวพิมพ์เล็ก) ctx.args.put("menus", Menu.find.all());:) มีเหตุผลหรือไม่? เช่นเดียวกับ Play ที่แปลงเป็นตัวพิมพ์ใหญ่หรืออะไร?
Cyril N.

1
@ cx42net มีMenusคลาสที่กำหนดไว้ (ตัวสกัดกั้น Java) @adis ใช่ แต่คุณมีอิสระที่จะจัดเก็บไว้ในที่อื่นแม้ในแคช
Julien Richard-Foy

19

วิธีที่ฉันทำคือสร้างคอนโทรลเลอร์ใหม่สำหรับการนำทาง / เมนูของฉันและเรียกใช้จากมุมมอง

ดังนั้นคุณสามารถกำหนด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>
}

จากนั้นในมุมมองหลักของฉันฉันสามารถเรียกสิ่งนั้นNavController:

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

NavController ควรมีลักษณะอย่างไรใน Java? ฉันหาวิธีทำให้คอนโทรลเลอร์คืน html ไม่ได้
มิกะ

ดังนั้นจึงเกิดขึ้นเมื่อคุณพบวิธีแก้ปัญหาทันทีหลังจากขอความช่วยเหลือ :) วิธีการควบคุมควรมีลักษณะเช่นนี้ play.api.templates.Html แบบคงที่สาธารณะ () {return (play.api.templates.Html) sidebar.render ("ข้อความ"); }
มิกะ

1
นี่เป็นแนวทางปฏิบัติที่ดีในการเรียกคอนโทรลเลอร์จากมุมมองหรือไม่? ฉันไม่อยากเป็นคนยึดติดดังนั้นถามด้วยความอยากรู้อยากเห็นอย่างแท้จริง
0fnt

นอกจากนี้คุณไม่สามารถทำสิ่งต่างๆตามคำขอด้วยวิธีนี้คุณสามารถ .. ตัวอย่างเช่นการตั้งค่าเฉพาะผู้ใช้ ..
0fnt

14

ฉันสนับสนุนคำตอบของสเตียน นี่เป็นวิธีที่รวดเร็วมากในการรับผลลัพธ์

ฉันเพิ่งย้ายจาก Java + Play1.0 เป็น Java + Play2.0 และเทมเพลตเป็นส่วนที่ยากที่สุดและวิธีที่ดีที่สุดที่ฉันพบในการใช้เทมเพลตพื้นฐาน (สำหรับชื่อเรื่องหัว ฯลฯ ) คือการใช้ Http .บริบท.

มีไวยากรณ์ที่ดีมากที่คุณสามารถทำได้ด้วยแท็ก

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

โดยที่ get.scala.html คือ:

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

และ set.scala.html คือ:

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

หมายความว่าคุณสามารถเขียนสิ่งต่อไปนี้ในเทมเพลตใดก็ได้

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

ดังนั้นจึงน่าอ่านและดีมาก

นี่คือทางที่ฉันเลือกไป สเตียน - คำแนะนำที่ดี การเลื่อนลงเพื่อดูคำตอบทั้งหมดเป็นสิ่งสำคัญ :)

การส่งผ่านตัวแปร HTML

ฉันยังไม่ทราบวิธีการส่งผ่านตัวแปร Html

@ (ชื่อเรื่อง: String เนื้อหา: html)

อย่างไรก็ตามฉันรู้วิธีส่งผ่านเป็นบล็อก

@ (ชื่อเรื่อง: String) (เนื้อหา: html)

ดังนั้นคุณอาจต้องการแทนที่ set.scala.html ด้วย

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

ด้วยวิธีนี้คุณสามารถผ่านบล็อก Html ได้เช่นนั้น

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

แก้ไข: ผลข้างเคียงด้วยการใช้ "ชุด" ของฉัน

การสืบทอดเทมเพลต it กรณีใช้งานทั่วไปใน Play

คุณมี base_template.html จากนั้นคุณมี page_template.html ที่ขยาย base_template.html

base_template.html อาจมีลักษณะดังนี้

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

ในขณะที่เทมเพลตหน้าอาจมีลักษณะคล้าย

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

จากนั้นคุณจะมีเพจ (สมมติว่า login_page.html) ที่ดูเหมือน

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

@page_template()

สิ่งสำคัญที่ควรทราบก็คือคุณตั้งค่า "ร่างกาย" สองครั้ง เมื่ออยู่ใน "login_page.html" แล้วใน "page_template.html"

ดูเหมือนว่าสิ่งนี้จะก่อให้เกิดผลข้างเคียงตราบใดที่คุณใช้ set.scala.html เหมือนที่ฉันแนะนำข้างต้น

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

เนื่องจากหน้าจะแสดง "สิ่งของเข้าสู่ระบบ ... " สองครั้งเนื่องจาก put ส่งคืนค่าที่ปรากฏในครั้งที่สองที่เราใส่คีย์เดียวกัน (ดูใส่ลายเซ็นในเอกสาร java)

สกาลาเป็นวิธีที่ดีกว่าในการแก้ไขแผนที่

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

ซึ่งไม่ก่อให้เกิดผลข้างเคียงนี้


ในคอนโทรลเลอร์สกาล่าฉันพยายามที่จะไม่มีวิธีการใส่ใน play.mvc.Htt.Context.current () ฉันพลาดอะไรไปรึเปล่า?
0fnt

ลองใส่argsบริบทหลังการโทรปัจจุบัน
guy mograbi

13

หากคุณใช้ Java และต้องการเพียงวิธีที่ง่ายที่สุดโดยไม่ต้องเขียน interceptor และใช้ @ ด้วยคำอธิบายประกอบคุณยังสามารถเข้าถึงบริบท HTTP ได้โดยตรงจากเทมเพลต

เช่นหากคุณต้องการตัวแปรจากเทมเพลตคุณสามารถเพิ่มลงในบริบท HTTP ด้วย:

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

จากนั้นคุณสามารถเข้าถึงได้จากเทมเพลตด้วย:

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

เห็นได้ชัดว่าถ้าคุณทิ้งวิธีการของคุณด้วย Http.Context.current (). args.put ("", "") คุณจะดีกว่าที่จะใช้ interceptor แต่สำหรับกรณีง่ายๆมันอาจทำเคล็ดลับได้


สวัสดี stian โปรดดูการแก้ไขครั้งสุดท้ายของฉันในคำตอบของฉัน ฉันเพิ่งพบว่าหากคุณใช้ "ใส่" ใน args สองครั้งด้วยคีย์เดียวกันคุณจะได้รับผลข้างเคียงที่น่ารังเกียจ คุณควรใช้ ... args (key) = value แทน
guy mograbi

6

จากคำตอบของ Stian ฉันลองใช้วิธีอื่น สิ่งนี้ใช้ได้กับฉัน

ในรหัส Java

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

ใน HTML TEMPLATE HEAD

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

และใช้งานเช่น

@if(isOk) {
   <div>OK</div>
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.