Tapestry 字典


Tapestry 字典 2006-9-24 Tapestry 字典 (Enjoy Web Dev With Tapestry 快查文档) Author: hero xiafei114@gmail.com 本文笔者初学 Tapestry 发现关于 tapestry 的资料奇 缺,学习起来很不方便,在自学 Enjoy Web Dev With Tapestry 时将所作的练习记录了下来,为了以后方便查 看。 使用字典时遇到书写不清楚的时候请查阅 Enjoy Web Dev With Tapestry。其中,国际化、下载、数据库操 作、应用 hibernate、strute 在字典中没有做记录。 笔者在 javaSE1.5.0_07、Tapestry4.1、jetty-5.1.6 环境 下程序均运行成功。 第 1 页 共 66 页 Tapestry 字典 2006-9-24 目录: Tapestry字典.....................................................................................................................................1 1. 基础知识(Using Forms)......................................................................................................4 1.1. page文件和class文件联系 ...........................................................................................4 1.2. 页面跳转.......................................................................................................................4 1.3. 初始化...........................................................................................................................4 1.4. 在html页面装配 ...........................................................................................................5 1.5. 使用下拉列表框(combo box).................................................................................5 1.6. 使用日历工具(DatePicker).....................................................................................6 2. 输入校验(Validating Inputs) ...............................................................................................6 2.1. 初始程序(map的使用)............................................................................................6 2.2. 变量自动转换/校验变量类型......................................................................................8 2.3. 消息显示.......................................................................................................................8 2.4. 弹出框校验...................................................................................................................9 2.5. 使用FieldLabel标签 ...................................................................................................10 2.6. 自定义校验规则.........................................................................................................10 2.7. 显示多行错误信息.....................................................................................................11 2.8. 页面加入js代码..........................................................................................................12 2.9. 自定义错误显示.........................................................................................................13 2.10. 使用date和taxtarea并加校验 .....................................................................................13 3. e商店(Creating an e-Shop)................................................................................................14 3.1. EShop练习基础程序..................................................................................................14 3.2. 显示详细资料 - 使用DirectLink组建 ...................................................................16 3.3. 显示详细资料 2 - 在另一页显示信息..................................................................16 3.4. 使用不同包中的类.....................................................................................................18 3.5. 区别按钮点击方法.....................................................................................................18 3.6. 使用Session ................................................................................................................19 3.7. 登录/登出PageLink组建/@InjectStateFlag注释........................................................20 3.8. 先选择商品在登录.....................................................................................................21 3.9. 阻挡恶意用户使用PageValidateListener接口...........................................................21 3.10. 任何也登录都可以回到发起页面 - 使用IExternalPage接口.................................22 3.11. 密码隐藏.....................................................................................................................23 3.12. 登出 – logout .............................................................................................................23 4. 自定义组建(Creating Custom Components) ....................................................................23 4.1. 基本代码.....................................................................................................................23 4.2. 组建嵌套.....................................................................................................................25 4.3. 可替换模板变量.........................................................................................................25 4.4. 组建使用指定包.........................................................................................................26 4.5. 给模板传值.................................................................................................................26 4.6. 建立library..................................................................................................................28 5. 使用Table组建(Using the Table Component) ...................................................................29 5.1. 简单table应用.............................................................................................................29 5.2. 列表交互背景颜色.....................................................................................................32 第 2 页 共 66 页 Tapestry 字典 2006-9-24 5.3. 使用tapestry - Contrib.library ................................................................................33 5.4. 自定义table显示的值/列............................................................................................34 5.5. table多语言显示列名.................................................................................................35 5.6. 在Contrib:Table中使用CSS........................................................................................35 5.7. 在名字上添加连接.....................................................................................................36 5.8. Tapestry使用Table模版 ..............................................................................................36 5.9. 自动分页.....................................................................................................................37 5.10. 程序修改准备.............................................................................................................37 5.11. 部分分页显示(不一次从数据库中提出全部记录).............................................39 5.12. 使用缓存.....................................................................................................................41 5.13. 在table中加入按钮.....................................................................................................42 5.14. 更新缓存.....................................................................................................................43 5.15. 删除排序连接.............................................................................................................43 5.16. 自定义table样式.........................................................................................................43 6. 文件上传下载(Handling File Downloads and Uploads)...................................................47 6.1. 源形 页面显示图片...................................................................................................47 6.2. 上传文件.....................................................................................................................49 7. 使用公共层(Providing a Common Layout)......................................................................51 7.1. 公共层基础程序.........................................................................................................51 7.2. 去掉选中下划线.........................................................................................................52 7.3. 添加标题.....................................................................................................................52 8. 使用Javascript (Using Javascript) ....................................................................................53 8.1. 简单js..........................................................................................................................53 8.2. 可复用js(tapestry4.1 自动添加dojo所以再源例上做了修改).............................53 8.3. 多script自动更名........................................................................................................54 8.4. 组建中使用script........................................................................................................55 9. 动态创建列表(Building Dynamic Forms)........................................................................56 9.1. 基础程序.....................................................................................................................56 9.2. 将主键隐藏存储在form上.........................................................................................59 9.3. 数据校验,不通过不能更新.....................................................................................59 9.4. 加入选择框chexckbox ...............................................................................................60 9.5. 多用户删除问题.........................................................................................................62 9.6. 加入下拉框select........................................................................................................62 9.7. 通过刷新更新信息,刷新后不显示错误信息.........................................................65 9.8. 避免选择数组跃界.....................................................................................................65 第 3 页 共 66 页 Tapestry 字典 2006-9-24 1. 基础知识(Using Forms) 1.1. page 文件和 class 文件联系 在 page 文件中加入,如 1.2. 页面跳转 1) cycle.activate("Result"); //Result 是一个页面 2) return "Result"; //Result 是一个页面,方法返回值需要改成 String 3) Result resultPage = (Result) cycle.getPage("Result"); return resultPage; //需要 import Result 类 4) Result resultPage = getResultPage(); resultPage.setStockValue(stockValue); return cycle.getPage("Result"); //在 page 文件中需要设置 5) 使用 java5.0 注释 在 class 中导入 import org.apache.tapestry.annotations.*;类 @InjectPage("Result") //Result 是要跳转的页面 public abstract Result getResultPage(); 1.3. 初始化 1) 在 class 中加入 protected void initialize() { stockValue = 0; } 2) 在 page 文件中加入 //tapestry 在运行时会自动建立如: public class ResultEnhanced extends Result { private XXX stockValue; 第 4 页 共 66 页 Tapestry 字典 2006-9-24 protected void initialize() { stockValue = ; } public XXX getStockValue() { return stockValue; } public void setStockValue(XXX stockValue) { this.stockValue = stockValue; } } //使用在 page 文件初始化要在 class 中添加相应的代码,并删除 geter/seter。如 public abstract String getStockId(); //页面也可以定义初始值,添加 3) 使用 java5.0 注释 在 class 中导入 import org.apache.tapestry.annotations.*;类 @InitialValue("literal:MSFT") //初始化 id 为 MSFT abstract public String getStockId(); 1.4. 在 html 页面装配 直接在 form 中加入配置信息
或者使用匿名组建 //如果不用需要在 page 文件中定义 //如果不用需要在 page 文件中定义 1.5. 使用下拉列表框(combo box) z page 设置方法 //stockId 是在 html 中 jwcid 引用的 //通过 class 中的 getavailStockIds 方 法附值 第 5 页 共 66 页 Tapestry 字典 2006-9-24 //选中的值 z class 中的定义 public IPropertySelectionModel getAvailStockIds() { return new StringPropertySelectionModel(new String[] { "IBM", "MSFT", "RHAT" }); } //数组按照顺序从 0 开始计数 1.6. 使用日历工具(DatePicker) z html 中加入 May 3, 2005 //在 body 中还需要加入如。否则会报错 //添加,否则无法正常使用 dojo z page 中加入 class 中使用 getQuoteDate()获得 2. 输入校验(Validating Inputs) 2.1. 初始程序(map 的使用) z Home.html 第 6 页 共 66 页 Tapestry 字典 2006-9-24
Weight:
Patron code:
z Home.page z Home.java package com.ttdev.postage; import java.util.HashMap; import java.util.Map; import org.apache.hivemind.util.PropertyUtils; import org.apache.tapestry.IPage; import org.apache.tapestry.annotations.InjectPage; import org.apache.tapestry.html.BasePage; public abstract class Home extends BasePage{ private Map patronCodeToDiscount; @InjectPage("Result") abstract public IPage getResultPage(); abstract public String getWeight(); abstract public String getPatronCode(); public Home(){ patronCodeToDiscount = new HashMap(); patronCodeToDiscount.put("p1", new Integer(95)); patronCodeToDiscount.put("p2", new Integer(90)); } public IPage onSubmit(){ 第 7 页 共 66 页 Tapestry 字典 2006-9-24 int weight = Integer.valueOf(getWeight()); Integer discount = (Integer)patronCodeToDiscount.get(this.getPatronCode()); int postagePerKg = 10; int postage = weight * postagePerKg; if (discount != null){ postage = postage * discount.intValue() / 100; } IPage resultPage = getResultPage(); PropertyUtils.write(resultPage, "postage", new Integer(postage)); return resultPage; } } z Result.html The postage is . z Result.page 2.2. 变量自动转换/校验变量类型 在 page 文件 TextField 类型中添加 或者使用 2.3. 消息显示 1) 方法 1,在 class 中添加 ValidationDelegate z 在 class 中添加 import org.apache.tapestry.valid.ValidationConstraint; import org.apache.tapestry.valid.ValidationDelegate; private ValidationDelegate delegate //需要在构造函数中初始化,否则会报错 第 8 页 共 66 页 Tapestry 字典 2006-9-24 delegate.setFormComponent((IFormComponent) getComponent("weight")); // 名称 “weight” delegate.recordFieldInputValue(Integer.toString(weight)); //错误值,传过来的-20 delegate.record("Weight must be >=0",ValidationConstraint.TOO_SMALL); // 显示文字 “Weight must be >=0” if (delegate.getHasErrors()) { //判断是否有错误,如果有就结束 return null; } z 在 page 中添加 2) 方法 2,在 page 中设置 ValidationDelegate z page 中加入 使用 delegate 的需要加入 beans,如 class 中删除 ValidationDelegate 初始化。使用时 ValidationDelegate delegate = (ValidationDelegate) getBeans().getBean("delegate"); 获得 delegate 实例。 3) 使用 javaSE5.0 注释配置 在 class 中加入 @Bean public abstract ValidationDelegate getDelegate(); ValidationDelegate delegate = getDelegate(); //使用 geter 获得实例 page 文件中不需要配置 ValidationDelegate 的 bean 2.4. 弹出框校验 在 page 文件的设置 //Form 中添加注明使用 delegate //添加校验规则 //不符合校验规则显示的文字, “Weight” required 判断是否为空 required[You must enter {0}!] “ You must enter ”是显示的文字,{0} 自动加入 displayName 中的值 min 最小值是几 max 最大值 第 9 页 共 66 页 Tapestry 字典 2006-9-24 minDate 最小时间,minDate=7/1/2005 maxDate 最大时间,maxDate=7/31/2005 maxLength 最大长度,maxLength=20 email 检查 email pattern 使用正则表达式检查,value="validators:pattern=\w+" 2.5. 使用 FieldLabel 标签 在 html 中加入引用Weight: page 文件中加入配置 2.6. 自定义校验规则 z 建立 KnownPatrons 类 public class KnownPatrons { private Map patronCodeToDiscount; public KnownPatrons() { patronCodeToDiscount = new HashMap(); patronCodeToDiscount.put("p1", new Integer(90)); patronCodeToDiscount.put("p2", new Integer(95)); } public Integer getDiscount(String patronCode) { return (Integer) patronCodeToDiscount.get(patronCode); } public boolean isKnown(String patronCode) { return patronCodeToDiscount.containsKey(patronCode); } } z 添加校验类 public class PatronCodeValidator implements Validator { private KnownPatrons knownPatrons; public void setKnownPatrons(KnownPatrons knownPatrons) { 第 10 页 共 66 页 Tapestry 字典 2006-9-24 this.knownPatrons = knownPatrons; } //Validator 接口方法 public void validate(IFormComponent field, ValidationMessages messages, Object object) throws ValidatorException { String patronId = (String) object; if (!knownPatrons.isKnown(patronId)) { throw new ValidatorException("Patron not found", null); } } //Validator 接口中其他方法,略 } z class 文件中使用 KnownPatrons 类代替 Map private KnownPatrons knownPatrons; //定义 public Home() { //初始化 knownPatrons = new KnownPatrons(); } Integer discount = knownPatrons.getDiscount(getPatronCode()); //使用时只需要一句 z 在 page 文件中定义 2.7. 显示多行错误信息 z 在 html 中加入
z page 文件中 //FieldTrack 格式列表 第 11 页 共 66 页 Tapestry 字典 2006-9-24 ‹ 在 li 中注入判断 z 在 html 中加入
  • z page 文件中 //在 li 中判断时添加本行 ‹ 直接在错误循环中加入修释 z page 文件中 2.8. 页面加入 js 代码 z html 中的 body 中加入 第 12 页 共 66 页 Tapestry 字典 2006-9-24 z page 文件中的 Form 类中加入 2.9. 自定义错误显示 z 在 java 代码中添加 delegate.setFormComponent(null); delegate.record("Can't ship 50kg or more for p1",ValidationConstraint.CONSISTENCY); 2.10. 使用 date 和 taxtarea 并加校验 z html 中加入 z page 中加入 z class 中加入 geter/seter public abstract Date getShippingDate(); public abstract String getDesc(); 第 13 页 共 66 页 Tapestry 字典 2006-9-24 3. e 商店(Creating an e-Shop) 3.1. EShop 练习基础程序 z Product 类 public class Product { private String id; private String name; private String desc; private double price; public Product (String id,String name,String desc,double price){ this.id = id; this.name = name; this.desc = desc; this.price = price; } // ....geter 等,略 } z Catalog 类 //singleness(单一)模式 public class Catalog { private List products; private static Catalog globalCatalog; public Catalog(){ products = new ArrayList(); } public List getProducts() { return products; } public void add(Product products) { this.products.add(products); } public static Catalog getGlobalCatalog(){ if (globalCatalog == null){ globalCatalog = new Catalog(); globalCatalog.add(new Product("p01","Pencil","aaaa",1.20)); 第 14 页 共 66 页 Tapestry 字典 2006-9-24 globalCatalog.add(new Product("p02","Eraser","bbbb",2.00)); globalCatalog.add(new Product("p03","Ball pen","cccc",3.50)); globalCatalog.add(new Product("p04","ruler","dddd",2.30)); } return globalCatalog; } } z Home 类 public class Home extends BasePage { public List getProducts() { return Catalog.getGlobalCatalog().getProducts(); } } z Home.page 文件 z Home.html 文件 第 15 页 共 66 页 Tapestry 字典 2006-9-24 Shop

    Product listing

    p01 Pencil 1.20 aaaa
    3.2. 显示详细资料 - 使用 DirectLink 组建 z html 中添加 p01 Pencil 1.20 z page 文件中添加 z 在 Catalog 类中添加 public Product lookup(String productId) { //使用 javaSE1.5 泛型功能 for (Iterator iter = products.iterator(); iter.hasNext();) { Product product = (Product) iter.next(); if (product.getId().equals(productId)) { return product; } } throw new IllegalArgumentException("Unknown product id: " + productId); } z 添加 ProductDetails 类 public class ProductDetails extends BasePage { private String productId; public void setProductId(String id) { this.productId = id; } public String getName() { return lookup().getName(); } public String getDesc() { return lookup().getDesc(); } private Product lookup() { return Catalog.getGlobalCatalog().lookup(productId); 第 17 页 共 66 页 Tapestry 字典 2006-9-24 } } z Home 加入跳转 @InjectPage("ProductDetails") //使用 javaSE1.5 注释功能,设置跳转页面 public abstract ProductDetails getDetailsPage(); 3.4. 使用不同包中的类 在 WEB-INF/[projectName].application 中添加 设置后 page 文件中的 class="com.ttdev.shop.Home"等就可以删除了 注意 web.xml 中的 servlet 配置的工程名一定是正确的名称,否则无法使用本功能 3.5. 区别按钮点击方法 1) 初级方法 在 ProductDetails.page 文件中添加 z ProductDetails.java 中添加 static public final String ADD_TO_CART_BUTTON="CART"; static public final String CONTINUE_SHOPPING_BUTTON="SHOP"; private String buttonClicked; //存储点击按钮的值 public void setButtonClicked(String buttonClicked) { //获得点击按钮的值 this.buttonClicked = buttonClicked; } 第 18 页 共 66 页 Tapestry 字典 2006-9-24 public String actionOnProduct() { //form 监听的方法 if (buttonClicked.equals(ADD_TO_CART_BUTTON)) { // add product to cart return null; } if (buttonClicked.equals(CONTINUE_SHOPPING_BUTTON)) { return "Home"; //返回到 Home.html } return null; } 2) 直接在按钮上定义监听方法 z page 文件中设置 z class 文件中添加 public void addToCart() { // add product to cart // ... } public String continueShopping() { return "Home"; } 3.6. 使用 Session z 在源文件夹(src 文件夹)建立文件夹 META-INF,创建 hivemodule.xml 文件,如下 第 19 页 共 66 页 Tapestry 字典 2006-9-24 z class 中添加 //@InjectState("cart") //定义读出的名称 public abstract List getCart(); //用 getCart()方法从 hivemodule.xml 文件中读取 session private List cart; public ProductDetails() { cart = new ArrayList(); } public void addToCart() { cart.add(productId); } public String getProductId() { return productId; } z html 页面增加 z page 页面增加 3.7. 登录/登出 PageLink 组建/@InjectStateFlag 注释 1) 刷新,不使用 hidden 隐藏数据 删除关于 hidden 的所以代码,包括 html 和 page 文件 z html 页面加入 Refresh z page 文件加入 第 20 页 共 66 页 Tapestry 字典 2006-9-24 2) 跳转 Login 3) user 对象判断 z 在 class 中添加 @InjectStateFlag("user") //对象名“user”本例是在 hivemodule.xml 中配置的 public abstract boolean getUserExists(); //判断是否存在 user 或者使用 z 在 page 中添加 3.8. 先选择商品在登录 z 调用 login 时 public IPage onCheckout() { if (getUserExists()) { return getConfirmPage(); } else { Login login = getLoginPage(); login.setNextPage("Confirm"); return login; } } z class 文件中添加 @Persist("client") //属性,将数据存储到 html 页面里 public abstract String getNextPage(); public abstract void setNextPage(String nextPage); return getNextPage() != null ? getNextPage() : "Home"; //判断跳转时 3.9. 阻挡恶意用户使用 PageValidateListener 接口 //自动对程序进行保护,在页面生成之前首先调用 pageValidate()方法 public abstract class Confirm extends BasePage implements PageValidateListener { 第 21 页 共 66 页 Tapestry 字典 2006-9-24 //.... @InjectStateFlag("user") //声明对象名 public abstract boolean getUserExists(); //判断是否存在 user 对象 @InjectPage("Login") //声明对象名 public abstract Login getLoginPage(); //跳转页面 public void pageValidate(PageEvent event) { //PageValidateListener 接口方法 if (!getUserExists()) { Login login = getLoginPage(); login.setNextPage("Confirm"); throw new PageRedirectException(login); } } //... } 3.10. 任何也登录都可以回到发起页面 - 使用 IExternalPage 接口 z 在需要的 class 中加入 s //属于 IExternalPage 接口方法,任何页面都要在创建时调用他 public void activateExternalPage(Object[] parameters, IRequestCycle cycle) { //setProductId()业务方法,parameters 页面中值 setProductId((String) parameters[0]); } public Login login() { getLoginPage().setNextPage(new ExternalCallback(this, new String[] { getProductId() })); //外部回调有两个参数,“this”外部页面名称,array 数组 return getLoginPage(); } z Login.java 需要修改加入 ICallback 接口 public abstract ICallback getNextPage(); public abstract void setNextPage(ICallback nextPage); public void onLogin(IRequestCycle cycle) { try { User user = Users.getKnownUsers().getUser(email, password); getUser().copyFrom(user); ICallback callback = getNextPage(); 第 22 页 共 66 页 Tapestry 字典 2006-9-24 if (callback != null) { callback.performCallback(cycle); } else { cycle.activate("Home"); } } catch (AuthenticationException ee) { ValidationDelegate delegate = getDelegate(); delegate.setFormComponent(null); delegate.record("Login failed", null); } } z 如果没有参数可以使用 PageCallback 类 login.setNextPage(new PageCallback("Confirm")); 3.11. 密码隐藏 z 使用 Tapestry 自动创建子类功能 public abstract String getEmail(); public abstract String getPassword(); 3.12. 登出 – logout z html 文件上添加 Logout z page 文件中添加 4. 自定义组建(Creating Custom Components) 4.1. 基本代码 z home.html 文件 第 23 页 共 66 页 Tapestry 字典 2006-9-24

    Page 1

    This is page 1. Copyright notice z home.page 文件 z Copyright.jwc 文件 z Copyright.html 文件
    Copyright 2005. Foo Inc. All rights reserved. z Copyright.java 文件 public class Copyright extends BaseComponent { public int getCurrentYear() { return new GregorianCalendar().get(GregorianCalendar.YEAR); } } 注 Copyright.html 文件和 Copyright.jwc 文件需要在同一个目录下,否则会报错。 第 24 页 共 66 页 Tapestry 字典 2006-9-24 4.2. 组建嵌套 z Box.jwc 文件 z Box.java 文件 public class Box extends AbstractComponent { protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle) { writer.begin("table"); //创建标签头 writer.attribute("border", 1); //加入属性 border = 1 renderInformalParameters(writer, cycle); //在引用中定义的 html 属性 writer.begin("tr"); //创建 writer.begin("td"); //创建 writer.end(); //加入 writer.end(); //加入
    renderBody(writer, cycle); //中间的嵌套组建 Copyright writer.end(); //加入
    } } 4.3. 可替换模板变量 1) 设置实现 z 模板 jwc 文件 z 模板 java 文件 public abstract String getHolder(); z 模板 html 文件 第 25 页 共 66 页 Tapestry 字典 2006-9-24
    Copyright 2005. Foo Inc. All rights reserved. z 调用的 page 文件 2) 通过注释添加 z java 文件中添加 @Parameter(name="holder", required=false, defaultValue="literal:Foo Inc.") public abstract String getHolder(); z 模板 jwc 文件中 删除 4.4. 组建使用指定包 z xxxxx.application 4.5. 给模板传值 z html 文件
    edit color here
    z page 文件 第 26 页 共 66 页 Tapestry 字典 2006-9-24 z java 文件 public abstract int getColor(); public void onOk() { System.out.println(Integer.toHexString(getColor())); } z 模板 html R: G: B: Sample:      z 模板 jwc 文件 z 模板 java 文件 public abstract class RGB extends BaseComponent { public abstract int getRed(); public abstract void setRed(int red); 第 27 页 共 66 页 Tapestry 字典 2006-9-24 public abstract int getGreen(); public abstract void setGreen(int green); public abstract int getBlue(); public abstract void setBlue(int blue); @Parameter(required = true) public abstract int getColor(); public abstract void setColor(int color); protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle) { if (!cycle.isRewinding()) { //isRewinding,判断是否被重画 setRed(getColor() >> 16); setGreen((getColor() >> 8) & 0xff); setBlue(getColor() & 0xff); } super.renderComponent(writer, cycle); //按照 html 绘制 if (cycle.isRewinding()) { setColor((getRed() << 16) | (getGreen() << 8) | getBlue()); } } public String getBackgroundColorAssignment() { return "background-color: rgb(" + getRed() + "," + getGreen() + "," + getBlue() + ")"; } } 4.6. 建立 library z 创建 components.library z XXX.application 文件 第 28 页 共 66 页 Tapestry 字典 2006-9-24 z 引用组建时 5. 使用 Table 组建(Using the Table Component) 5.1. 简单 table 应用 包名 com.ttdev.phonebook z GlobalPhoneBook.java public class GlobalPhoneBook extends PhoneBook { public GlobalPhoneBook() { addEntry(new PhoneBookEntry(0, "Alan", "Turing", "111111")); addEntry(new PhoneBookEntry(1, "Bill", "Gates", "111222")); addEntry(new PhoneBookEntry(2, "Martin", "Fowler", "654321")); addEntry(new PhoneBookEntry(3, "Kent", "Beck", "999001")); addEntry(new PhoneBookEntry(4, "Howard", "Ship", "554433")); addEntry(new PhoneBookEntry(5, "Linus", "Torvalds", "888777")); } } z PhoneBook.java public class PhoneBook { private List entries; public PhoneBook() { entries = new ArrayList(); } public void addEntry(PhoneBookEntry entry) { entries.add(entry); } public List getEntries() { return entries; } } 第 29 页 共 66 页 Tapestry 字典 2006-9-24 z PhoneBookEntry.java public class PhoneBookEntry { private int id; private String firstName; private String lastName; private String telNo; public PhoneBookEntry(int id, String firstName, String lastName, String telNo) { this.id = id; this.firstName = firstName; this.lastName = lastName; this.telNo = telNo; } public String getFirstName() { return firstName; } public int getId() { return id; } public String getLastName() { return lastName; } public String getTelNo() { return telNo; } } z Home.java public abstract class Home extends BasePage { public abstract int getIndex(); @InjectState("phoneBook") public abstract PhoneBook getPhoneBook(); public List getEntries() { 第 30 页 共 66 页 Tapestry 字典 2006-9-24 return getPhoneBook().getEntries(); } } z hivemodule.xml 文件 注框架自动将 hivemodule.xml 中配置的 phoneBook 创建成全局变量,phoneBook 是通过 其子类 GlobalPhoneBook 实例化的。 phoneBook 在 Home.java 的@InjectState("phoneBook")调用 z Home.html 文件 Phone Book
    IDFirst nameLast nameTel #
    1 Britney Spears 376926
    2EltonJohn285984
    3DavidLetterman877357
    z Home.page 文件 第 31 页 共 66 页 Tapestry 字典 2006-9-24 z phonebook.application 文件 5.2. 列表交互背景颜色 z PhoneBook.css 文件,放在 css 目录下 tr.odd {background-color: RoyalBlue} tr.even {background-color: GreenYellow} z Home.html 添加 Phone Book 第 32 页 共 66 页 Tapestry 字典 2006-9-24
    IDFirst nameLast nameTel #
    1 Britney Spears 376926
    2EltonJohn285984
    3DavidLetterma877357
    z Home.page 文件 5.3. 使用 tapestry - Contrib.library z PhoneBook.application 中添加 z Home.html 添加 z Home.page 添加 注使用 Contrib:Table 后定义的其他普通组建将无法使用(不删除也不报错) 第 33 页 共 66 页 Tapestry 字典 2006-9-24 5.4. 自定义 table 显示的值/列 z 新建 Name 类 public class Name { private String firstName; private String lastName; public Name(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public String getFirstName() { return firstName; } public String getLastName() { return lastName; } } z 修改 PhoneBookEntry.java private Name name; public PhoneBookEntry(int id, Name name, String telNo) { this.id = id; this.name = name; this.telNo = telNo; } public Name getName() { return name; } z 修改 GlobalPhoneBook.java 如 addEntry(new PhoneBookEntry(0, new Name("Alan", "Turing"), "111111")); z 修改 page 文件 第 34 页 共 66 页 Tapestry 字典 2006-9-24 z 修改 PhoneBookEntry.java public String getFirstName() { return getName().getFirstName(); } z 修改 page 文件,可以简化 value="literal:id, firstName:First name:firstName, lastName:name.lastName, telNo" 5.5. table 多语言显示列名 z 建立 Home.properties firstName = First Name z 建立 Home_zh_CN.properties 文件 firstName = \u59D3 z 修改 page 文件 注 firstName 之所也可以正常访问因为 getFirstName 被重写了,系统可以直接访问 name.firstName() 5.6. 在 Contrib:Table 中使用 CSS z CSS 中加入 th.title {background-color: Pink} z page 中添加 第 35 页 共 66 页 Tapestry 字典 2006-9-24 5.7. 在名字上添加连接 z Home.html 中添加 z page 文件中添加 注显示时自动调用 getFirstName() components.table 中的 table 是定义的 Table 类型的 Id,所以可以随意定义 5.8. Tapestry 使用 Table 模版 Tapestry 自动比对 table 的 html 代码,所以一下两种写法对 tapestry 都一样。 1) 第一种
    Britney
    第 36 页 共 66 页 Tapestry 字典 2006-9-24
    IDFirst nameLast nameTel #
    1 Britney Spears 376926
    2EltonJohn285984
    3DavidLetterman877357
    2) 第二种
    IDFirst nameLast nameTel #
    1 Britney Spears 376926
    2EltonJohn285984
    3DavidLetterman877357
    Britney 5.9. 自动分页 z page 中添加 5.10. 程序修改准备 z 新建 PhoneBookTable.java public class PhoneBookTable { private List entries; public PhoneBookTable() { entries = new ArrayList(); } public void insert(PhoneBookEntry entry) { entries.add(entry); } public ResultSet selectAll() { System.out.println("Selecting all"); return new ResultSet(entries); 第 37 页 共 66 页 Tapestry 字典 2006-9-24 } } z 新建 ResultSet.java public class ResultSet { private List entriesInResultSet; private int currentIdx; public ResultSet(List entriesInResultSet) { this.entriesInResultSet = entriesInResultSet; this.currentIdx = -1; } public boolean next() { if (currentIdx < entriesInResultSet.size() - 1) { currentIdx++; return true; } else { return false; } } public PhoneBookEntry readEntry() { PhoneBookEntry entry = (PhoneBookEntry) entriesInResultSet .get(currentIdx); System.out.println("Reading entry with id " + entry.getId()); return entry; } } z 修改 PhoneBook.java public class PhoneBook { private PhoneBookTable table; public PhoneBook(PhoneBookTable table) { this.table = table; } public List getEntries() { List result = new ArrayList(); ResultSet rs = table.selectAll(); while (rs.next()) { result.add(rs.readEntry()); 第 38 页 共 66 页 Tapestry 字典 2006-9-24 } return result; } } z 修改 GlobalPhoneBook.java public class GlobalPhoneBook extends PhoneBook { public GlobalPhoneBook() { super(makePhoneBookTable()); } private static PhoneBookTable makePhoneBookTable() { PhoneBookTable phoneBookTable = new PhoneBookTable(); phoneBookTable.insert(new PhoneBookEntry(0, new Name("Alan", "Turing"), "111111")); phoneBookTable.insert(new PhoneBookEntry(1, new Name("Bill", "Gates"), "111222")); //......略 return phoneBookTable; } } 5.11. 部分分页显示(不一次从数据库中提出全部记录) z 修改 Home.page z 修改 Home.java public IBasicTableModel getModel() { return new IBasicTableModel() { //内部类 public int getRowCount() { return getPhoneBook().getNoEntries(); //获得记录数,本例为 list.size() } /** * 获得当前页面显示的记录 * * @param nFirst * 第多少条记录,因为现在定义的是每页 3 条,所以第一页为 0, 2 页为 3,3 页为 6; 第 39 页 共 66 页 Tapestry 字典 2006-9-24 * @param nPageSize * 每页显示几条记录,本例为 3 * @param objSortColumn * 按照什么排序,如果没有排序为 null * @param bSortOrder * 升/降排序方法,默认为 * SORT_DESCENDING = true;SORT_ASCENDING = false; */ public Iterator getCurrentPageRows(int nFirst, int nPageSize, ITableColumn objSortColumn, boolean bSortOrder) { //getColumnName(),获得列 ID String sortColumnId = objSortColumn == null ? null : objSortColumn.getColumnName(); return getPhoneBook().getSomeEntries(nFirst, nPageSize, sortColumnId, bSortOrder == ITableSortingState.SORT_ASCENDING) .iterator(); } }; } z PhoneBook.java 添加 public int getNoEntries() { return table.selectCount(); } public List getSomeEntries(int startIdx, int noEntries, String sortColumnId, boolean isAscending) { //selectLimit()模仿 SQL 语句,select * from table order by firstName limit 3 offset 3 ResultSet rs = table.selectLimit(startIdx, noEntries, sortColumnId, isAscending); List result = new ArrayList(); while (rs.next()) { result.add(rs.readEntry()); } return result; } z PhoneBookTable.java 添加 public int selectCount() { return entries.size(); } public ResultSet selectLimit(int startIdx, int noRecords, 第 40 页 共 66 页 Tapestry 字典 2006-9-24 String columnName, boolean isAscending) { System.out.println("Selecting with limit"); List sortedEntries = new ArrayList(entries); //复制一个集合 if (columnName != null) { //进行排序 Collections.sort(sortedEntries, new PhoneBookEntryComparator( columnName)); if (!isAscending) { //如果是倒序排列 Collections.reverse(sortedEntries); } } List subList = sortedEntries.subList(startIdx, startIdx + noRecords); //获得指定记录 return new ResultSet(subList); } z 添加新类 PhoneBookEntryComparator.java public class PhoneBookEntryComparator implements Comparator { private String columnName; public PhoneBookEntryComparator(String columnName) { this.columnName = columnName; } public int compare(Object obj1, Object obj2) { PhoneBookEntry entry1 = (PhoneBookEntry) obj1; PhoneBookEntry entry2 = (PhoneBookEntry) obj2; if (columnName.equals("id")) { return entry1.getId() - entry2.getId(); } else if (columnName.equals("firstName")) { return entry1.getFirstName().compareTo(entry2.getFirstName()); } else { // Similar for other columns. return 0; } } } 5.12. 使用缓存 z Home.html 文件添加 public List getSource() { return getPhoneBook().getEntries(); } z 修改 Home.page 文件 第 41 页 共 66 页 Tapestry 字典 2006-9-24 在 Name.java 和 PhoneBookEntry.java 中添加可序列化接口,保存 session 使用 implements Serializable 5.13. 在 table 中加入按钮 (由于使用 tapestry4.1,所以 4.0 的程序无法使用,看自定义 table 样式) z 添加修改 Home.page z Home.html 文件添加
    z Home.java 文件添加 @InjectComponent("table") //获得页面中叫"table"的组建 public abstract Table getTable(); //获得页面中叫"table"的组建 public void onDelete() { int id = ((PhoneBookEntry) getTable().getTableRow()).getId(); System.out.println("Deleting " + id); getPhoneBook().deleteEntry(id); } 第 42 页 共 66 页 Tapestry 字典 2006-9-24 z PhoneBook.java 添加 public void deleteEntry(int id) { table.delete(id); } z deleteEntry.java 添加 public void delete(int id) { for (Iterator iter = entries.iterator(); iter.hasNext();) { PhoneBookEntry entry = (PhoneBookEntry) iter.next(); if (entry.getId() == id) { iter.remove(); return; } } } 5.14. 更新缓存 z Home.java 文件添加 public void onDelete() { int id = ((PhoneBookEntry) getTable().getTableRow()).getId(); System.out.println("Deleting " + id); getPhoneBook().deleteEntry(id); SimpleTableModel tableModel = ((SimpleTableModel) getTable().getTableModel()); tableModel.setDataModel(new SimpleListTableDataModel(getSource())); tableModel.tableDataChanged(null); getTable().fireObservedStateChange(); } 5.15. 删除排序连接 5.16. 自定义 table 样式 z Home.html 文件 第 43 页 共 66 页 Tapestry 字典 2006-9-24 Phone Book
    IDFirst nameLast nameTel #Delete
    1 Britney Spears 376926
    2EltonJohn285984Delete
    3DavidLetterman877357Delete
    Britney 第 44 页 共 66 页 Tapestry 字典 2006-9-24 z Home.page 文件 第 45 页 共 66 页 Tapestry 字典 2006-9-24 z Home.java public abstract class Home extends BasePage { @InjectState("phoneBook") public abstract PhoneBook getPhoneBook(); public List getSource() { return getPhoneBook().getEntries(); } @InjectComponent("table") public abstract TableView getTable(); @InjectComponent("tableRows") public abstract TableFormRows getTableRows(); public abstract TableValues getTableValues(); public void onDelete() { int id = ((PhoneBookEntry) getTableRows().getTableRow()).getId(); System.out.println("Deleting " + id); getPhoneBook().deleteEntry(id); //刷新缓存 SimpleTableModel tableModel = ((SimpleTableModel) getTable() .getTableModel()); tableModel.setDataModel(new SimpleListTableDataModel(getSource())); tableModel.tableDataChanged(null); getTable().fireObservedStateChange(); } public IBasicTableModel getModel() { return new IBasicTableModel() { public int getRowCount() { return getPhoneBook().getNoEntries(); } public Iterator getCurrentPageRows(int nFirst, int nPageSize, ITableColumn objSortColumn, boolean bSortOrder) { String sortColumnId = objSortColumn == null ? null : objSortColumn.getColumnName(); return getPhoneBook().getSomeEntries(nFirst, nPageSize, 第 46 页 共 66 页 Tapestry 字典 2006-9-24 sortColumnId, bSortOrder == ITableSortingState.SORT_ASCENDING) .iterator(); } }; } public void onShowDetails(int entryId) { System.out.println("Showing details for " + entryId); } } 6. 文件上传下载(Handling File Downloads and Uploads) 6.1. 源形 页面显示图片 z Home.html Download photo #101 z Home.java public abstract class Home extends BasePage { } z Home.page z Download.java public abstract class Download extends BasePage { private int imageId; @InjectObject("service:tapestry.globals.HttpServletResponse") public abstract HttpServletResponse getResponse(); public void setImageId(int imageId) { this.imageId = imageId; } 第 47 页 共 66 页 Tapestry 字典 2006-9-24 //原先只可以传递 html 代码,重写 renderPage 让他可以传递流文件 public void renderPage(IMarkupWriter writer, IRequestCycle cycle) { byte imageData[] = ImageDB.loadImage(imageId); HttpServletResponse response = getResponse(); // 告诉浏览器返回的是图片文件 response.setContentType("image/jpeg"); // 通知浏览器要按照文件需要保存,文件名为 foo.jpg response.setHeader("Content-disposition", "attachment; filename=foo.jpg"); // 在 HTTP 响应中创建输出流,如果失败抛出 IOException try { OutputStream out = response.getOutputStream(); out.write(imageData); } catch (IOException e) { throw new ApplicationRuntimeException(e); } } } z ImageDB.java public class ImageDB { public static byte[] loadImage(int imageId) { InputStream input = ImageDB.class .getResourceAsStream("/com/ttdev/album/" + imageId + ".jpg"); ByteArrayOutputStream output = new ByteArrayOutputStream(); byte buf[] = new byte[1024]; for (;;) { try { int noBytesRead = input.read(buf); if (noBytesRead == -1) { return output.toByteArray(); } output.write(buf, 0, noBytesRead); } catch (IOException e) { throw new RuntimeException(e); } } } } z ImageService.java public class ImageService implements IEngineService { public String getName() { return "image"; 第 48 页 共 66 页 Tapestry 字典 2006-9-24 } public void service(IRequestCycle cycle) throws IOException { int imageId = Integer.parseInt(cycle.getParameter("imageId")); byte imageData[] = ImageDB.loadImage(imageId); WebResponse response = cycle.getInfrastructure().getResponse(); try { OutputStream out = response.getOutputStream(new ContentType( "image/jpeg")); out.write(imageData); } catch (IOException e) { throw new ApplicationRuntimeException(e); } } public ILink getLink(boolean post, Object parameter) { return null; } } z hivemodule.xml 6.2. 上传文件 z XXXX.application 文件 z Upload.html

    z Upload.page 第 49 页 共 66 页 Tapestry 字典 2006-9-24 z Upload.java public abstract class Upload extends BasePage { @InjectMeta("com.ttdev.album.image-folder") public abstract String getImageFolder(); public abstract IUploadFile getFile(); public void onOk() { if (getFile().getFileName().length() == 0) { return; } byte imageData[] = new byte[(int) getFile().getSize()]; InputStream fileInput = getFile().getStream(); try { fileInput.read(imageData); } catch (IOException e) { throw new RuntimeException(e); } ImageDB.saveImage(101, imageData, getImageFolder()); } } z ImageDB.java 文件添加 public static void saveImage(int imageId, byte[] imageData, String imageFolder) { File imageFile = new File(imageFolder, imageId + ".jpg"); try { FileOutputStream output = new FileOutputStream(imageFile); try { output.write(imageData); } finally { output.close(); } 第 50 页 共 66 页 Tapestry 字典 2006-9-24 } catch (IOException e) { throw new RuntimeException(e); } } 7. 使用公共层(Providing a Common Layout) 7.1. 公共层基础程序 z 创建 Border.jwc 文件 z 创建 Border.html,模板文件 <span jwcid="@Insert" value="ognl:title"/>
    Home
    Products
    Contact
    每个调用模板的文件使用 jwcid="@Border"加入内容
    第 51 页 共 66 页 Tapestry 字典 2006-9-24 z Home.html 文件(调用模板时) Home This is the Home page. 替换的内容 7.2. 去掉选中下划线 z 修改 Border.html Home
    Products
    Contact 7.3. 添加标题 1) 直接使用 z Home.html

    Home header

    z 模板文件中 注通过定义的名字 header 替换 2) 需要配置 z jwc 文件加入 z 模板文件中 第 52 页 共 66 页 Tapestry 字典 2006-9-24 z Home.html 文件

    Home header

    8. 使用 Javascript (Using Javascript) 8.1. 简单 js
    8.2. 可复用 js(tapestry4.1 自动添加 dojo 所以再源例上做 了修改) z 创建 Confirm.script z 修改 Home.html 第 53 页 共 66 页 Tapestry 字典 2006-9-24
    z Home.page z Home.java public class Home extends BasePage { public Map getSymbols() { Map symbols = new HashMap(); symbols.put("msg", "Are you sure?"); return symbols; } } 8.3. 多 script 自动更名 z Home.html 文件
    z Home.page 添加 第 54 页 共 66 页 Tapestry 字典 2006-9-24 z Confirm.script 文件 8.4. 组建中使用 script z ConfirmButton.html 文件 z ConfirmButton.jwc 文件 z Home.html
    第 55 页 共 66 页 Tapestry 字典 2006-9-24
    9. 动态创建列表(Building Dynamic Forms) 9.1. 基础程序 z Home.html
    z Home.page 第 56 页 共 66 页 Tapestry 字典 2006-9-24 z Home.java public abstract class Home extends BasePage { public abstract User getCurrentUser(); @InjectState("users") public abstract Users getGlobalUsers(); public List getUsers() { return getGlobalUsers().selectAll(); } //监听调用方法 public void updateUser(IRequestCycle cycle) { if (cycle.isRewinding()) { System.out.println(getCurrentUser().getName()); getGlobalUsers().update(getCurrentUser()); } } } z User.java public class User implements Serializable{ private static final long serialVersionUID = 4751383452617811451L; private int id; private String name; private String password; public User(int id, String name, String password) { this.id = id; this.name = name; this.password = password; } public User makeCopy() { return new User(id, name, password); } //更新 第 57 页 共 66 页 Tapestry 字典 2006-9-24 public void assignFrom(User user) { this.name = user.name; this.password = user.password; } //geter/seter 略 } z Users.java public class Users { public List users; public Users() { users = new ArrayList(); users.add(new User(0, "Paul", "123")); users.add(new User(4, "John", "456")); users.add(new User(6, "Mary", "789")); } public List selectAll() { List usersFound = new ArrayList(); for (Iterator iter = users.iterator(); iter.hasNext();) { User user = (User) iter.next(); usersFound.add(user.makeCopy()); } return usersFound; } //检索 public void update(User user) { for (int i = 0; i < users.size(); i++) { User currentUser = (User) users.get(i); if (currentUser.getId() == user.getId()) { currentUser.assignFrom(user); return; } } throw new RuntimeException("User not found"); } } 第 58 页 共 66 页 Tapestry 字典 2006-9-24 9.2. 将主键隐藏存储在 form 上 z Home.page 添加 z Home.java 添加 public IPrimaryKeyConverter getConverter() { return new IPrimaryKeyConverter() { public Object getPrimaryKey(Object value) { //获得修改 ID return new Integer(((User) value).getId()); } public Object getValue(Object primaryKey) { //模仿数据库查询 return getGlobalUsers().select( ((Integer) primaryKey).intValue()); } }; } z Users.java 文件添加 public User select(int userId) { for (Iterator iter = users.iterator(); iter.hasNext();) { User user = (User) iter.next(); if (user.getId() == userId) { return user.makeCopy(); } } throw new RuntimeException("User not found"); } 9.3. 数据校验,不通过不能更新 z Home.html 文件添加 User name 第 59 页 共 66 页 Tapestry 字典 2006-9-24 z Home.page 添加 z Home.java @Bean public abstract ValidationDelegate getDelegate(); @InjectComponent("userName") public abstract TextField getUserNameField(); public void updateUser(IRequestCycle cycle) { if (cycle.isRewinding()) { getDelegate().setFormComponent(getUserNameField()); if (!getDelegate().isInError()) { getGlobalUsers().update(getCurrentUser()); } } } 9.4. 加入选择框 chexckbox z Home.html 修改加入 User name 第 60 页 共 66 页 Tapestry 字典 2006-9-24 Delete z Home.page 加入 z Home.java 修改加入 public abstract boolean isToDeleteUser(); public void updateUser(IRequestCycle cycle) { if (cycle.isRewinding()) { //获得校验结果 getDelegate().setFormComponent(getUserNameField()); boolean isInError = getDelegate().isInError(); if (isToDeleteUser()) { //清除校验功能 getDelegate().reset(); //删除 getGlobalUsers().delete(getCurrentUser().getId()); } else { if (!isInError) { //更新 getGlobalUsers().update(getCurrentUser()); } } } } z Users.java 添加 public void delete(int userId) { for (Iterator iter = users.iterator(); iter.hasNext();) { User user = (User) iter.next(); if (user.getId() == userId) { iter.remove(); return; } } throw new RuntimeException("User not found"); } 第 61 页 共 66 页 Tapestry 字典 2006-9-24 9.5. 多用户删除问题 z Home.java 中更新 public IPrimaryKeyConverter getConverter() { return new IPrimaryKeyConverter() { public Object getPrimaryKey(Object value) { return new Integer(((User) value).getId()); } public Object getValue(Object primaryKey) { try { return getGlobalUsers().select( ((Integer) primaryKey).intValue()); } catch (RuntimeException e) { getDelegate().setFormComponent(null); getDelegate().record("User has been deleted", null); throw new PageRedirectException(getPageName()); } } }; } 9.6. 加入下拉框 select z 新建 AddUser.html




    z 新建 AddUser.page 第 62 页 共 66 页 Tapestry 字典 2006-9-24 z 新建 AddUser.java public abstract class AddUser extends BasePage implements PageBeginRenderListener { public abstract int getUserId(); public abstract String getUserName(); public abstract String getPassword(); public abstract String getCountry(); public abstract String getCity(); public abstract void setCountry(String country); @InjectState("users") public abstract Users getGlobalUsers(); 第 63 页 共 66 页 Tapestry 字典 2006-9-24 public void pageBeginRender(PageEvent event) { //当 Country 是空时,初始化 Country,在刷新页面之前下拉框不能为空 if (getCountry() == null) { setCountry("US"); } } @Bean public abstract ValidationDelegate getDelegate(); public String onOk() { //如果有错则不做操作,只是显示 if (getDelegate().getHasErrors()) { return getPageName(); } getGlobalUsers().insert( new User(getUserId(), getUserName(), getPassword())); return "Home"; } /** * @return 返回国家数组 */ public IPropertySelectionModel getCountries() { return new StringPropertySelectionModel(new String[] { "US", "China" }); } /** * @return 返回城市数组 */ public IPropertySelectionModel getCities() { if (getCountry().equals("US")) { return new StringPropertySelectionModel(new String[] { "New York", "Boston" }); } else { return new StringPropertySelectionModel(new String[] { "Beijing", "Shanghai" }); } } } z Users.java 添加 public void insert(User user) { users.add(user); 第 64 页 共 66 页 Tapestry 字典 2006-9-24 } 9.7. 通过刷新更新信息,刷新后不显示错误信息 z 修改 AddUser.html(由于使用 tapestry4.1 所以修改了源代码)




    z AddUser.page z AddUser.java 文件 public void onRefresh() { getDelegate().clearErrors(); //刷新页面,抛弃错误 } 9.8. 避免选择数组跃界 z 修改 AddUser.java @Persist("client") //属性,将数据存储到 html 页面里 public abstract String getCountryForRender(); public abstract void setCountryForRender(String country); public void pageBeginRender(PageEvent event) { // 当 Country 是空时,初始化 Country,在刷新页面之前下拉框不能为空 第 65 页 共 66 页 Tapestry 字典 2006-9-24 if (getCountry() == null) { setCountry("US"); } //如果是重绕(rewind,使用 Refresh)重新为读取的 Conuntry 附值 if (!event.getRequestCycle().isRewinding()) { setCountryForRender(getCountry()); } } public IPropertySelectionModel getCities() { //判断传入的 Country 是不是 US if (getCountryForRender().equals("US")) { return new StringPropertySelectionModel(new String[] { "New York", "Boston", "Chicago" }); } else { return new StringPropertySelectionModel(new String[] { "Beijing", "Shanghai" }); } } 第 66 页 共 66 页
    还剩65页未读

    继续阅读

    下载pdf到电脑,查找使用更方便

    pdf的实际排版效果,会与网站的显示效果略有不同!!

    需要 15 金币 [ 分享pdf获得金币 ] 0 人已下载

    下载pdf

    pdf贡献者

    atlas.zjy

    贡献于2012-01-05

    下载需要 15 金币 [金币充值 ]
    亲,您也可以通过 分享原创pdf 来获得金币奖励!
    下载pdf