本书第六章以一个实例介绍了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的实例,
--> product
boundForm
③ 调用produce.save()添加表单数据到Product的实例boundForm--> product
boundForm
boundForm
boundForm
1.3 JavaForm(Controller)小结
this
① 定义表单类用于接收和发送数据 -->Form<Product> productForm = Form.form(Product.class);
② 把模型中数据写入表单
--> Form<Product> filledForm = productForm.fill(product);
③ 把页面表单的数据写入模型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();
--> Form<Product> boundForm = productForm.bindFromRequest();Product product = boundForm.get();
product.save();
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
action传入的参数
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); } } }
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类
④ 在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复杂参数的格式化
@Formats.DateTime(pattern = "yyyy-MM-dd") public Date date;
4、表单验证—— Validation
Play的验证仅和领域模型绑定,使用JSR-303和Hibernate的验证,经过为对象模型定义添加注解(annotation)来实现。
4.1 内置验证(build-in)
在领域模型中使用注解(annotation)
在Controller中使用Form对象的hasError方法来处理验证的错误意外
4.2 局部验证(partial)
(之后补充)
4.3 自定义验证(custom)
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)); }
参考: