注解: CRUD——Create,Retrieve, Update, Delete
文件结构
一、添加控制器(controller和action)——/app/controllers/Products.java
package controllers; import play.mvc.Controller; import play.mvc.Result; public class Products extends Controller { public static Result list() { //列出所有的产品 return TODO; } public static Result newProduct() { //显示一个空的产品Form return TODO; } public static Result details(String ean) { //显示一个编辑产品的Form return TODO; } public static Result save() { //保存产品资料 return TODO; } }
说明: controllers的action方法必须满足以下三个条件:
- 必须是public
- 必须是static
- 返回类型必须是Result和Result的子类
完整代码为
package controllers; import models.Product; import play.data.Form; import play.mvc.Controller; import play.mvc.Result; import java.util.ArrayList; import java.util.List; import views.html.*; public class Products extends Controller { //constant for Form private static final Form<Product> productForm = Form.form(Product.class); public static Result list(){ List<Product> products = Product.findAll(); return ok(list.render(products)); } public static Result newProduct(){ return ok(details.render(productForm)); } 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); return ok(details.render(filledForm)); } public static Result save(){ Form<Product> boundForm = productForm.bindFromRequest(); if(boundForm.hasErrors()){ flash("error","Please correct the form below."); return badRequest(details.render(boundForm)); } Product product = boundForm.get(); product.save(); flash("success",String.format("Successfully added product %s", product)); return redirect(routes.Products.list()); } public static Result delete(String ean){ final Product product = Product.findByEan(ean); if(product == null){ return notFound(String.format("Product %s does not exist.", ean)); } Product.remove(product); return redirect(routes.Products.list()); } }
二. 修改routes文件
GET /products/ controllers.Products.list() GET /products/new controllers.Products.newProduct() GET /products/:ean controllers.Products.details(ean: String) POST /products/ controllers.Products.save()
DELETE /products/:ean controllers.Products.delete(ean: String)
action和route对应关系如下图
三 . 添加Model Class——/app/models/Product.java
3.1) 数据模型
public class Product { public String ean; public String name; public String description; public Product(){} public Product(String ean, String name, String description){ this.ean = ean; this.name = name; this.description = description; } public String toString(){ return String.format("%s - %s", ean, name, description); } }
说明: 不同于Java,play使用public,不用setter和getter方法
3.2)为了模拟练习,在Product.java中加一些模拟数据
// mocking some data private static List<Product> products; static { products = new ArrayList<Product>(); products.add(new Product("111111111","Paperclips 1","Paperclips description 1")); products.add(new Product("222222222","Paperclips 2","Paperclips description 2")); products.add(new Product("333333333","Paperclips 3","Paperclips description 3")); products.add(new Product("444444444","Paperclips 1","Paperclips description 4")); products.add(new Product("555555555","Paperclips 1","Paperclips description 5")); }
3.3) 数据处理方法——findAll(), findByEan(), findByName(), remove(), save()
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); } } return results; } public static boolean remove(Product product){ return products.remove(product); } public void save(){ products.remove(findByEan(this.ean)); products.add(this); }
完整代码为
四、添加和修改模板文件——list.scala.html, details.scala.html
4.1) list.scala.html
@(products:List[Product]) <!--parameter list: define which parameters this template accepts --> @main("Product catalogue"){ <!-- call main template, the title is "Product catalogue" --> <h2>All Product</h2> <table class="table table-striped"> <thead> <tr> <th>EAN</th> <th>Name</th> <th>Description</th> </tr> </thead> <tbody> @for(product <- products){ <tr> <td><a href="@routes.Products.details(product.ean)">@product.ean</a></td> <td><a href="@routes.Products.details(product.ean)">@product.name</a></td> <td><a href="@routes.Products.details(product.ean)">@product.description</a></td> </tr> } </tbody> </table> }
相应的,修改controller用于显示该模板的HTML内容,即修改/app/controllers/Products.java中的list()方法
... import views.html.*; //引入模板 public class Products extends Controller { public static Result list() { List<Product> products = Product.findAll(); return ok(list.render(products)); } ... }
在浏览器地址栏输入: http://localhost:9000/products
4.2) details.scala.html
@(productForm:Form[Product]) @import helper._ @import helper.twitterBootstrap._ @main("Product Form"){ <h1>Product Form</h1> @helper.form(action = routes.Products.save()){ <fieldset> <legend>Product(@productForm("name").valueOr("New"))</legend> @helper.inputText(productForm("ean"),'_label -> "EAN") @helper.inputText(productForm("name"),'_label -> "Name") @helper.textarea(productForm("description"),'_label -> "description") </fieldset> <input type="submit" class="btn btn-primary" value="Save" /> <a class="btn" href="@routes.Application.index()">Cancel</a> } }
说明: 本模板使用了Play表单Form
- @(productForm:Form[Product]) —>— productForm是play.data.Form.form()的方法,在controller中定义
- @import helper._ —>— 引入生产HTML,@import helper.twitterBootstrap._ 生产的HTML符合Twitter Bootstrap标准
- @helper.form(action =routes.Products.save()){ —>— 生产的 HTML的Form代码, 并将数据提交给controller的save()方法处理,@helper.inputText生产的 HTML的input代码,@helper.textarea生产的 HTML的textarea代码
- Product(@productForm("name").valueOr("New")) —>— productForm.field("name")的缩写,请求表单中名为name的域(field)值,如何该值不存在,则创建一个"New"为默认值。
最后生产的HTML代码如下
<form action="/products" method="GET" > <fieldset> <legend>Product(New)</legend> <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">Required</span> </div> </div> <div class="clearfix " id="name_field"> <label for="name">Name</label> <div class="input"> <input type="text" id="name" name="name" value="" > <span class="help-inline"></span> <span class="help-block">Required</span> </div> </div> <div class="clearfix " id="description_field"> <label for="description">description</label> <div class="input"> <textarea id="description" name="description" ></textarea> <span class="help-inline"></span> <span class="help-block"></span> </div> </div> </fieldset> <input type="submit" class="btn btn-primary" value="Save" /> <a class="btn" href="/products">Cancel</a> </form>
相应的,修改controller用于显示该模板的HTML内容,即修改/app/controllers/Products.java中的newProduct()()方法
public static Result newProduct(){ return ok(details.render(productForm)); }
在浏览器地址栏输入: http://localhost:9000/products/new
五、处理表单提交和验证——binding values from the request
play可以从HTTP request获取一个包含name/value对的映射Map,实现这个功能的是类Form的bindFromRequest()方法,该方法会返回一个Form对象,故我们可以在保存的时候获取页面表单的数据。
5.1) 在/app/models/Products.java中加入
public class Products extends Controller { ... public static Result save() { Form<Product> boundForm = productForm.bindFromRequest(); Product product = boundForm.get(); product.save(); return ok(String.format("Saved product %s", product)); } }
5.2) 在/app/models/Product.java中加入验证
... import play.data.validation.Constraints; public class Product { ... @Constraints.Required public String ean; @Constraints.Required public String name; public String description; ... }
5.3) 对/app/models/Products.java中save()的改进
对模型加入验证数据后,希望能在controller中显示相应的成功、失败信息,这里可以使用play的validation表单功能,修改save()方法如下:
public static Result save() { Form<Product> boundForm = productForm.bindFromRequest(); if(boundForm.hasErrors()) { flash("error", "Please correct the form below."); return badRequest(details.render(boundForm)); } Product product = boundForm.get(); product.save(); flash("success",String.format("Successfully added product %s", product)); return redirect(routes.Products.list()); }
同时还必须在app/views/main.scala.html的<body>标签的开头中加入实现flash scope的代码:
@if(flash.containsKey("success")){ <div class="alert alert-success"> @flash.get("success") </div> } @if(flash.containsKey("error")){ <div class="alert alert-error"> @flash.get("error") </div> }
说明: flash scope是用于在请求(request)的时候存储变量的一个地方,在下一次请求开始之前存储在flash scope的值一直保持着,所以flash 是flash scope用来显示失败和成功是最理想的!
六、在列表中加入删除功能——app/views/products/list.scala.html
6.1) 在routes中加入DELETE路由
DELETE /products/:ean controllers.Products.delete(ean: String)
6.2) 在list.scala.html中加入JS代码,本例用JS处理Ajax请求
<h2>All products</h2> <script> function del(urlToDelete) { $.ajax({ url: urlToDelete, type: 'DELETE', success: function(results) { location.reload(); // Refresh the page } }); } </script> ...... <tbody> @for(product <- products){ <tr> <td><a href="@routes.Products.details(product.ean)">@product.ean</a></td> <td><a href="@routes.Products.details(product.ean)">@product.name</a></td> <td><a href="@routes.Products.details(product.ean)">@product.description</a></td> <td> <a href="@routes.Products.details(product.ean)"><i class="fa fa-pencil"></i></a> <a onclick="del('@routes.Products.delete(product.ean)')"><i class="fa fa-trash-o"></i></a> </td> </tr> } </tbody> </table> <a href="@routes.Products.newProduct()" class="btn btn-primary"><i class="fa fa-plus-circle"></i> New product</a>
说明: 整个案例代码