Làm thế nào để tránh truyền tham số ở mọi nơi trong play2?


125

Trong play1, tôi thường nhận được tất cả dữ liệu trong các hành động, sử dụng chúng trực tiếp trong các khung nhìn. Vì chúng ta không cần phải khai báo rõ ràng các tham số trong chế độ xem, điều này rất dễ dàng.

Nhưng trong play2, tôi thấy chúng ta phải khai báo tất cả các tham số (bao gồm request) trong phần đầu của chế độ xem, sẽ rất nhàm chán khi lấy tất cả dữ liệu trong hành động và chuyển chúng vào dạng xem.

Ví dụ: nếu tôi cần hiển thị các menu được tải từ cơ sở dữ liệu ở trang trước, tôi phải xác định nó trong 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>

Sau đó, tôi phải khai báo nó trong mỗi trang phụ:

@(menus: Seq[Menu])

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

Sau đó, tôi phải lấy các menu và chuyển nó để xem trong mọi hành động:

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

Bây giờ chỉ có một tham số main.scala.html, nếu có nhiều tham số thì sao?

Vì vậy, cuối cùng, tôi quyết định tất cả Menu.findAll()trực tiếp trong xem:

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

Tôi không biết nếu nó là tốt hay được khuyến nghị, có giải pháp nào tốt hơn cho việc này không?


Có lẽ play2 nên thêm một cái gì đó như đoạn trích của thang máy
Freewind

Câu trả lời:


229

Theo tôi, thực tế là các mẫu được gõ tĩnh thực sự là một điều tốt : bạn được đảm bảo rằng việc gọi mẫu của bạn sẽ không thất bại nếu nó được biên dịch.

Tuy nhiên, nó thực sự bổ sung một số mẫu soạn sẵn trên các trang web gọi điện. Nhưng bạn có thể giảm nó (mà không mất lợi thế gõ tĩnh).

Trong Scala, tôi thấy hai cách để đạt được nó: thông qua thành phần hành động hoặc bằng cách sử dụng các tham số ngầm. Trong Java, tôi đề nghị sử dụng Http.Context.argsbản đồ để lưu trữ các giá trị hữu ích và truy xuất chúng từ các mẫu mà không cần phải truyền rõ ràng dưới dạng tham số mẫu.

Sử dụng tham số ngầm

Đặt menustham số ở cuối main.scala.htmltham số mẫu của bạn và đánh dấu tham số đó là ẩn ngầm ẩn:

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

Bây giờ nếu bạn có các mẫu gọi mẫu chính này, bạn có thể có menustham số được chuyển hoàn toàn cho bạn tới mainmẫu bằng trình biên dịch Scala nếu nó cũng được khai báo là tham số ẩn trong các mẫu này:

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

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

Nhưng nếu bạn muốn nó được truyền hoàn toàn từ bộ điều khiển của bạn, bạn cần cung cấp nó dưới dạng một giá trị ngầm định, có sẵn trong phạm vi mà bạn gọi mẫu. Chẳng hạn, bạn có thể khai báo phương thức sau trong bộ điều khiển của mình:

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

Sau đó, trong hành động của bạn, bạn sẽ có thể chỉ cần viết như sau:

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

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

Bạn có thể tìm thêm thông tin về phương pháp này trong bài đăng trên blog này và trong mẫu mã này .

Cập nhật : Một bài đăng blog tốt đẹp thể hiện mô hình này cũng đã được viết ở đây .

Sử dụng thành phần hành động

Trên thực tế, việc truyền RequestHeadergiá trị cho các mẫu thường rất hữu ích (xem ví dụ mẫu này ). Điều này không thêm quá nhiều mẫu mã vào mã điều khiển của bạn bởi vì bạn có thể dễ dàng viết các hành động nhận giá trị yêu cầu ngầm:

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

Vì vậy, vì các mẫu thường nhận được ít nhất tham số ngầm định này, bạn có thể thay thế nó bằng một giá trị phong phú hơn có chứa, ví dụ như các menu của bạn. Bạn có thể làm điều đó bằng cách sử dụng cơ chế cấu thành hành động của Play 2.

Để làm điều đó, bạn phải xác định Contextlớp của mình , gói một yêu cầu cơ bản:

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

Sau đó, bạn có thể xác định ActionWithMenuphương pháp sau :

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

Mà có thể được sử dụng như thế này:

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

Và bạn có thể lấy bối cảnh làm tham số ẩn trong các mẫu của bạn. Ví dụ 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>

Sử dụng thành phần hành động cho phép bạn tổng hợp tất cả các giá trị ngầm định mà các mẫu của bạn yêu cầu thành một giá trị duy nhất, nhưng mặt khác, bạn có thể mất một số tính linh hoạt.

Sử dụng http.Context (Java)

Do Java không có cơ chế ẩn ý của Scala hoặc tương tự, nếu bạn muốn tránh truyền các tham số mẫu một cách rõ ràng, một cách có thể là lưu trữ chúng trong Http.Contextđối tượng chỉ tồn tại trong thời gian yêu cầu. Đối tượng này chứa một argsgiá trị của loại Map<String, Object>.

Do đó, bạn có thể bắt đầu bằng cách viết một thiết bị chặn, như được giải thích trong tài liệu :

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

Phương thức tĩnh chỉ là một tốc ký để lấy các menu từ ngữ cảnh hiện tại. Sau đó chú thích bộ điều khiển của bạn để trộn với bộ Menuschặn hành động:

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

Cuối cùng, lấy menusgiá trị từ các mẫu của bạn như sau:

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

Ý bạn là thực đơn thay vì thực đơn? "Các menu val ẩn: Seq [Menu] = Menu.find ALL"
Ben McCann

1
Ngoài ra, vì dự án của tôi chỉ được viết bằng Java ngay bây giờ, nên có thể đi theo lộ trình sáng tác hành động và chỉ có phần chặn của tôi được viết bằng Scala, nhưng để lại tất cả các hành động của tôi được viết bằng Java?
Ben McCann

"Menu" hoặc "menu", không thành vấn đề :), vấn đề quan trọng là loại: Seq [Menu]. Tôi đã chỉnh sửa câu trả lời của mình và thêm một mẫu Java để xử lý vấn đề này.
Julien Richard-Foy

3
Trong khối mã cuối cùng, bạn gọi @for(menu <- Menus.current()) {nhưng Menuskhông bao giờ được xác định (bạn đặt menu (chữ thường) ctx.args.put("menus", Menu.find.all());:). Có một lý do? Giống như Play mà biến đổi nó bằng chữ hoa hoặc cái gì đó?
Cyril N.

1
@ cx42net Có một Menuslớp được định nghĩa (bộ chặn Java). @adis Có nhưng bạn có thể lưu trữ chúng ở một nơi khác, ngay cả trong bộ đệm.
Julien Richard-Foy

19

Cách tôi làm là chỉ cần tạo một bộ điều khiển mới cho menu / điều hướng của tôi và gọi nó từ chế độ xem

Vì vậy, bạn có thể xác định 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>
}

Sau đó, trong quan điểm chính của tôi, tôi có thể gọi đó là NavController:

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

Làm thế nào các NavContoder được cho là nhìn trong Java? Tôi không thể tìm cách làm cho bộ điều khiển trả về html.
Mika

Và do đó, điều xảy ra là bạn tìm thấy giải pháp ngay sau khi yêu cầu một số trợ giúp :) Phương thức điều khiển sẽ giống như thế này. công khai play.api.temsheet.Html sidebar () {return (play.api.temsheet.Html) sidebar.render ("message"); }
Mika

1
Đây có phải là một thực hành tốt để gọi bộ điều khiển từ một góc nhìn? Tôi không muốn trở thành một stickler, vì vậy hãy hỏi về sự tò mò thực sự.
0fnt

Ngoài ra, bạn không thể thực hiện công cụ dựa trên các yêu cầu theo cách này, bạn có thể .., ví dụ: cài đặt cụ thể của người dùng ..
0fnt

14

Tôi ủng hộ câu trả lời của stian. Đây là một cách rất nhanh để có được kết quả.

Tôi mới chuyển từ Java + Play1.0 sang Java + Play2.0 và các mẫu là phần khó nhất cho đến nay và cách tốt nhất tôi tìm thấy để triển khai một mẫu cơ sở (cho tiêu đề, đầu, v.v.) là bằng cách sử dụng http .Bối cảnh.

Có một cú pháp rất hay bạn có thể đạt được với các thẻ.

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

trong đó get.scala.html là:

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

và set.scala.html là:

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

có nghĩa là bạn có thể viết như sau trong bất kỳ mẫu nào

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

Vì vậy, nó rất dễ đọc và tốt đẹp.

Đây là con đường tôi đã chọn để đi. stian - lời khuyên tốt. Chứng tỏ điều quan trọng là cuộn xuống để xem tất cả các câu trả lời. :)

Truyền các biến HTML

Tôi vẫn chưa tìm ra cách chuyển các biến Html.

@ (tiêu đề: Chuỗi, nội dung: Html)

tuy nhiên, tôi biết làm thế nào để vượt qua chúng như một khối.

@ (tiêu đề: Chuỗi) (nội dung: Html)

vì vậy bạn có thể muốn thay thế set.scala.html bằng

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

bằng cách này bạn có thể vượt qua các khối Html như vậy

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

EDIT: Tác dụng phụ với việc thực hiện "Bộ" của tôi

Một trường hợp sử dụng phổ biến nó kế thừa mẫu trong Play.

Bạn có base_template.html và sau đó bạn có page_template.html mở rộng base_template.html.

base_template.html có thể trông giống như

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

trong khi mẫu trang có thể trông giống như

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

và sau đó bạn có một trang (giả sử login_page.html) trông giống như

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

@page_template()

Điều quan trọng cần lưu ý ở đây là bạn đặt "cơ thể" hai lần. Khi đã ở trong "login_page.html" và sau đó trong "page_template.html".

Có vẻ như điều này gây ra hiệu ứng phụ, miễn là bạn triển khai set.scala.html như tôi đã đề xuất ở trên.

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

vì trang sẽ hiển thị "nội dung đăng nhập ..." hai lần vì đặt trả về giá trị bật ra lần thứ hai chúng ta đặt cùng một khóa. (xem đặt chữ ký trong tài liệu java).

scala cung cấp một cách tốt hơn để sửa đổi bản đồ

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

mà không gây ra tác dụng phụ này.


Trong trình điều khiển scala, tôi cố gắng thực hiện không có phương thức put nào trong play.mvc.Htt.Context.cản (). Tui bỏ lỡ điều gì vậy?
0fnt

hãy thử đặt argsbối cảnh sau khi gọi hiện tại.
anh chàng mograbi

13

Nếu bạn đang sử dụng Java và chỉ muốn cách đơn giản nhất có thể mà không phải viết một bộ chặn và sử dụng chú thích @With, bạn cũng có thể truy cập trực tiếp vào bối cảnh HTTP từ mẫu.

Ví dụ: nếu bạn cần một biến có sẵn từ một mẫu, bạn có thể thêm nó vào ngữ cảnh HTTP với:

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

Sau đó, bạn có thể truy cập nó từ mẫu với:

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

Rõ ràng nếu bạn xả rác các phương thức của mình bằng http.Context.cản (). Args.put ("", "") thì tốt hơn bạn nên sử dụng một bộ chặn, nhưng đối với các trường hợp đơn giản, nó có thể thực hiện thủ thuật.


Xin chào stian, xin hãy xem bản chỉnh sửa cuối cùng của tôi trong câu trả lời của tôi. Tôi chỉ phát hiện ra rằng nếu bạn sử dụng "put" trong args hai lần với cùng một khóa, bạn sẽ nhận được một hiệu ứng phụ khó chịu. Bạn nên sử dụng ... args (key) = value thay thế.
anh chàng mograbi

6

Từ câu trả lời của Stian, tôi đã thử một cách tiếp cận khác. Điều này làm việc cho tôi.

TRONG MÃ JAVA

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

TRÊN TẠM THỜI HTML

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

VÀ SỬ DỤNG THÍCH

@if(isOk) {
   <div>OK</div>
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.