《Play for Java》学习笔记(五)Form

本书第六章以一个实例介绍了Play Framework中Form的使用,如何绑定数据,如何进行验证

一、项目结构和action

二、Play中表单的使用

1. 在Controller中使用Form——处理提交和验证

play.data包中包含了处理HTTP表单数据提交和验证(HTTP form data submission and validation)的一些helpers,一般步骤是先定义一个play.data.Form并包裹进其所用模型类class,如下所示:

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", "[email protected]"); 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是

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

1.2 处理表单的输入


① 创建boundForm对象,用于接受从HTTP传入的数据信息,HTTP --> boundForm
boundForm将接受的表单数据传给Product的实例,boundForm --> product
③ 调用produce.save()添加表单数据到Product的实例

1.3 JavaForm(Controller)小结

① 定义表单类用于接收和发送数据 --> Form<Product> productForm = Form.form(Product.class);
② 把模型中数据写入表单 --> Form<Product> filledForm = productForm.fill(product);
③ 把页面表单的数据写入模型 --> Form<Product> boundForm = productForm.bindFromRequest();
Product product = boundForm.get();
 
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"))

 三、数据绑定

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

 1、 简单数据绑定

模型如下:

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传入的数据。

 2、 复杂数据绑定

假定有两个类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(同上)

4、 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

四、表单验证—— 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)

五、补充

本人比较有印象和感兴趣的是该实例中页面中删除记录的代码,居然直接使用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));

}

 参考:

你可能感兴趣的:(java)