《Play for Java》學習筆記(五)Form

本書第六章以一個實例介紹了Play Framework中Form的使用,如何綁定數據,如何進行驗證javascript

1、項目結構和actionhtml

2、Play中表單的使用java

1. 在Controller中使用Form——處理提交和驗證ajax

play.data包中包含了處理HTTP表單數據提交和驗證(HTTP form data submission and validation)的一些helpers,通常步驟是先定義一個play.data.Form幷包裹進其所用模型類class,以下所示:bootstrap

Form<User> userForm =Form.form(User.class);
//引入包
import play.data.*;
import static play.data.Form.*;
//
Model—— User Object public class User { public String email; public String password; } //controller—— userForm Form<User> userForm = Form.form(User.class); //1. 定義幷包裹模型User類 //This form can generate a User result value from HashMap<String,String> data Map<String,String> anyData = new HashMap(); //2. 寫入數據到HashMap--mocking data anyData.put("email", "bob@gmail.com"); anyData.put("password", "secret"); User user = userForm.bind(anyData).get(); //3. 寫入數據到表單並綁定給User對象(保存數據) //If have a request available in the scope, bind directly from the request content User user = userForm.bindFromRequest().get();

1.1 在表單中顯示預設好的數據——Displaying a form with preset values瀏覽器

public class Products extends Controller {
    private static final Form<Product> productForm = Form.form(Product.class);
    ...
    public static Result details(String ean) {
        final Product product = Product.findByEan(ean);
        if (product == null) {
            return notFound(String.format("Product %s does not exist.", ean));  //處理錯誤
        }
        Form<Product> filledForm = productForm.fill(product);        //填寫product實例中的數據到頁面表單productForm中
        return ok(details.render(filledForm));                       //返回(跳轉)渲染頁面
    }
    ...
}

route是mvc

GET         /products/:ean               controllers.Products.details(ean: String)ide

1.2 處理表單的輸入ui


① 建立boundForm對象,用於接受從HTTP傳入的數據信息,HTTP --> boundForm
boundForm將接受的表單數據傳給Product的實例,boundForm --> product
③ 調用produce.save()添加表單數據到Product的實例
boundFormboundForm --> productboundFormboundForm

1.3 JavaForm(Controller)小結
this

① 定義表單類用於接收和發送數據 --> Form<Product> productForm = Form.form(Product.class);
② 把模型中數據寫入表單 --> Form<Product> filledForm = productForm.fill(product);
③ 把頁面表單的數據寫入模型 --> Form<Product> boundForm = productForm.bindFromRequest();
Product product = boundForm.get();
 
product.save();
Form<Product> productForm = Form.form(Product.class);模型中數據寫入表單 --> Form<Product> filledForm = productForm.fill(product); --> Form<Product> boundForm = productForm.bindFromRequest();
Product product = boundForm.get();
 
product.save();
--> Form<Product> boundForm = productForm.bindFromRequest();
Product product = boundForm.get();
 
product.save();


 
product.save();

 
product.save();

 
product.save();

 
product.save();

 
product.save();product.save();product.save();

2. 在模板中使用表單——Form template helpers

在模板中咱們可使用Form template的helpers和helper.twitterBootstrap來處理表單各個項,這些helper會自動生成相應的HTML代碼,如:

@helper.form(action = routes.Products.save()) {
    @helper.inputText(productForm("ean"), '_label -> "EAN", '_help -> "Must be exaclty 13 numbers.")
}

會產生以下HTML代碼(helper.twitterBootstrap)

<div class="clearfix  " id="ean_field">
    <label for="ean">EAN</label>
    <div class="input">        
        <input type="text" id="ean" name="ean" value="" >
        <span class="help-inline"></span>
        <span class="help-block">Must be exaclty 13 numbers.</span> 
    </div>
</div>

說明: 該helper可用的可選參數以下

'_label -> "Custom label"
'_id ->"idForTheTopDlElement"
'_help -> "Custom help" '_showConstraints ->false
'_error -> "Force an error" '_showErrors ->false

項目中details.scala.html的代碼以下:

2.1 引入helper

  • @(productForm: Form[Product]) —— action傳入的參數
  • @import helper._ —— Form helpers
  • @import helper.twitterBootstrap._ —— bootstrap helpers

2.2 生成<form> tag

@helper.form(action = routes.Products.save()) { ... }

可在生成的時候加入參數

@helper.form(action = routes.Products.save(),''id -> "form") { ... }

2.3 生成 <input> element

還能夠自定義HTML輸入

@helper.input(myForm("email")) { (id, name, value, args) =>
    <inputtype="date"name="@name"id="@id" @toHtmlArgs(args)>
}

 2.4 自定義的helper——生成自定義的myFieldConstructorTemplate

@(elements: helper.FieldElements)
<div class="@if(elements.hasErrors) {error}">
<label for="@elements.id">@elements.label</label>
<div class="input">
    @elements.input
    <span class="errors">@elements.errors.mkString(", ")</span>
    <span class="help">@elements.infos.mkString(", ")</span>
</div>
</div>

 保存爲views/myFieldConstructorTemplate.scala.html文件,如今就可使用這個自定義的FieldElements

@implicitField = @{ FieldConstructor(myFieldConstructorTemplate.f) }
@inputText(myForm("email"))

 3、數據綁定

PLay中有三種綁定方式

● 表單綁定(Form binding),見前所述

● URL查詢參數綁定(URL query parameters binding):

GET   /products         Products.edit(id: Long) -->  映射到URL:    http://localhost:9000/products?id=12

● URL路徑綁定(URL path binding)

GET   /products/:id    Products.edit(id: Long)  -->  映射到URL:   http://localhost:9000/products/12

 一、 簡單數據綁定

模型以下:

public class Product {
    public String ean;
    public String name;
    public String description;
}

路由以下:

GET   /products/save   controllers.Products.save()

controller中的部分代碼以下:

//建立一個新的Product實例,用來接受HTTP數據
Form<models.Product> productForm = form(models.Product.class).bindFromRequest();
//將從表單獲得的數據賦給Product實例
Product product = boundForm.get();
//調用Product的save方法,將數據添加到product實例中
product.save();
...
return redirect(routes.Products.list());

在瀏覽器中輸入http://localhost:9000/product/save?ean=1111111111111&name=product&description=a%20description(該URL的參數便是在表單中填入的數據,經過GET方法傳給URL)便可激活Product.save()方法,並轉入Product.list()方法,顯示從HTTP URL傳入的數據。

 二、 複雜數據綁定

假定有兩個類Product和Tag,其關係如圖

//Tag Class
public class Tag {
    public Long id;
    public String name;
    public List<Product> products;
    public Tag(Long id, String name, Collection<Product> products) {
        this.id = id;
        this.name = name;
        this.products = new LinkedList<Product>(products);
        for (Product product : products) {
            product.tags.add(this);
        }
    }
   public static Tag findById(Long id) {
    for (Tag tag : tags) {
      if(tag.id == id) return tag;
    }
    return null;
   }
}
//Product Class
public class Product implements PathBindable<Product>, QueryStringBindable<Product> {
   public String ean;
  public String name;
  public String description;
  public Date date = new Date();
  public Date peremptionDate = new Date();
  public List<Tag> tags = new LinkedList<Tag>();

  public Product() {
  }

  public Product(String ean, String name, String description) {
    this.ean = ean;
    this.name = name;
    this.description = description;
  }
  public static List<Product> findAll() {
    return new ArrayList<Product>(products);
  }

  public static Product findByEan(String ean) {
    for (Product candidate : products) {
      if (candidate.ean.equals(ean)) {
        return candidate;
      }
    }
    return null;
  }

  public static List<Product> findByName(String term) {
    final List<Product> results = new ArrayList<Product>();
    for (Product candidate : products) {
      if (candidate.name.toLowerCase().contains(term.toLowerCase())) {
        results.add(candidate);
      }
    }
}
Tag + Product Class

l瀏覽器中顯示效果如圖:

        

在Products Controller中加入如下代碼

public class Products extends Controller {
   ... public static Result save() {
       ... (binding and error handling)
        Product product = boundForm.get();
        List<Tag> tags = new ArrayList<Tag>();
        for (Tag tag : product.tags) {
          if (tag.id != null) {
            tags.add(Tag.findById(tag.id));
          }
        }
       product.tags = tags;    product.save();
    ... (success message and redirect) }

在Tag模型類中加入模擬數據(mocking data)

static {
   //The lightweight tag is added to product names matching paperclips 1
    tags.add(new Tag(1L, "lightweight", Product.findByName("paperclips 1")));
   //The metal tag is added to all the products (they all match paperclips)
    tags.add(new Tag(2L, "metal", Product.findByName("paperclips")));
    //Theplastic tag is added to all the products (they all match paperclips)
    tags.add(new Tag(3L, "plastic", Product.findByName("paperclips")));
  }

在details.scala.html,加入對應於Tag的相關代碼

<div class="control-group">
  <div class="controls">
    <input name="tags[0].id" value="1" type="checkbox"
    @for(i <- 0 to 2) {
      @if(productForm("tags[" + i + "].id").value=="1"){ checked }   //若是該模型的Tag爲1(paperclips 1),將該選擇項選中,即該產品據有lightweight屬性
    }> lightweight
    <input name="tags[1].id" value="2" type="checkbox"               //若是該模型的Tag爲2(paperclips),將該選擇項選中,即該產品據有metal屬性
    @for(i <- 0 to 2) {
      @if(productForm("tags[" + i + "].id").value=="2"){ checked }   //若是該模型的Tag爲3(paperclips),將該選擇項選中,即該產品據有plastic屬性
    }> metal
    <input name="tags[2].id" value="3" type="checkbox"
    @for(i <- 0 to 2) {
      @if(productForm("tags[" + i + "].id").value=="3"){ checked }
    }> plastic
  </div>
</div>

 3. 自定義數據綁定

 3.1 綁定URL Path和模型對象

①將route從 GET    /product/:ean       controllers.Product.details(ean: String)     改成

               GET    /product/:ean       controllers.Product.details(ean: models.Product

②products/list.scala.html中的 <a href="@routes.Products.details(product.ean)">  改成
                   <a href="@routes.Products.details(product)">

③修改Product類

  • 繼承PathBindable接口並重寫bind、unbind和javascriptUnbind方法
  • bind():                URL       --> product
  • unbind():             product -->view
  • javascriptUnbind():    支持Javascript和Ajax call

④ 在controller Products中加入新action

該action用於自動綁定Product對象和URL路徑中的ean

 3.2 綁定URL Path參數(Query string)和模型對象

原理同上,只是繼承的接口是play.mvc.QueryStringBindable

①將route從 GET    /product/:ean       controllers.Product.details(ean: String)     改成

              GET    /product/           controllers.Product.details(ean: models.Product)  這樣咱們就可使用像下面的URL了:  /products?ean=1

②products/list.scala.html中的 <a href="@routes.Products.details(product.ean)">  改成
                    <a href="@routes.Products.details(product)">

③修改Product類

④ 在Products Controller中加入新action(同上)

四、 HTTP URL複雜參數的格式化

  • 若是HTTP URL的參數是String、Integer、Long等簡單類型,不用進行格式化直接使用,但若是是複雜類型(如日期Date類型)就必須將其映射爲array或List對象,才能做爲URL的參數使用。
  • Play提供了內置的格式化方法,均放在play.mvc.data.Formats包中
  • 例如對於Date,必須先對其進行格式化
    @Formats.DateTime(pattern = "yyyy-MM-dd")
    public Date date;
    而後才能做爲URL的參數:   http://localhost:9000/product/save?date=2021-10-02

4、表單驗證—— Validation

Play的驗證僅和領域模型綁定,使用JSR-303和Hibernate的驗證,經過爲對象模型定義添加註解(annotation)來實現。

4.1 內置驗證(build-in)

  在領域模型中使用註解(annotation)

 

  在Controller中使用Form對象的hasError方法來處理驗證的錯誤意外

4.2 局部驗證(partial)

 (之後補充)

4.3 自定義驗證(custom)

  • ad hoc驗證——該方法最簡單、最快捷
  • 使用@ValidateWith並定義本身的Validator類
  • 定義行的JSR 303註解(annotation)和本身的Validator類

4.3.1 ad hoc驗證——爲每一個領域模型類增長validate方法

4.3.2 使用Play的@ValidateWith並定義本身的Validator類

爲每一個領域模型類增長validate方法,以Product類的ean成員變量爲例,爲其添加自定義EanValidator類:

4.3.3 使用JSR-303註解並定義本身的Validator類

以Product類的ean成員變量爲例

① 定義一個註解(annotation) —— 如: 自定義的Product類的成員變量ean的JSR-303類型註解EAN (Custom JSR-303 EAN annotation)


② 定義自定義EanValidator類——Custom JSR-303 validator

③在領域模型類中使用自定義的註解(annotation)

5、補充

本人比較有印象和感興趣的是該實例中頁面中刪除記錄的代碼,竟然直接使用javascript中的$.Ajax方法來實現刪除,其所有代碼以下所示:

list.scala.html

@(products: List[Product])
@main("Products catalogue") {
  <h2>All products</h2>
    <script>
     function del(urlToDelete) { $.ajax({ url: urlToDelete, type: 'DELETE', success: function(results) {location.reload(); // Refresh the page } }); } </script>
   <table class="table table-striped">
    <thead><tr><th>EAN</th><th>Name</th><th>Description</th><th>Date</th><th></th></tr></thead>
    <tbody>
    @for(product <- products) {
      <tr>
        <td><a href="@routes.Products.details(product)">
          @product.ean 
        </a></td>
        <td><a href="@routes.Products.details(product)">@product.name</a></td>
        <td><a href="@routes.Products.details(product)">@product.name</a></td>
        <td>@product.date.format("dd-MM-yyyy")</td>
        <td>
          <a href="@routes.Products.details(product)"><i class="icon icon-pencil"></i></a> 
          <a onclick="javascript:del('@routes.Products.delete(product.ean)')"><i class="icon icon-trash"></i></a> 
        </td>
      </tr>
      }
  
    </tbody>
   </table>    
  <a href="@routes.Products.newProduct()" class="btn">
    <i class="icon-plus"></i> New product</a>
}

delete()方法以下

public static Result delete(String ean) {
    final Product product = Product.findByEan(ean);
    if(product == null) {
        return notFound(String.format("Product %s does not exists.", ean));
    }
    Product.remove(product);
    return redirect(routes.Products.list(1));
}

 參考:

相關文章
相關標籤/搜索