Tapestry 和 Wicket 被宣扬为流行的基于组件的现代 Web 框架。与 Model 2 架构框架 Struts 或 Spring MVC 不同,Tapestry 和 Wicket 为 Web 开发过程提供了一种全新的方式,即以看待基于 GUI 的独立应用程序相同的方式来考虑 Web 应用程序及其行为和组件交互。
典型的基于组件的应用程序表示一个页面集合,它由一套组件组成。而这些组件可能由更小的组件组装而成。用户交互是一种特定的组件事件。这是与基于 MVC 的应用程序的主要区别,后者的交互入口点是一个 servlet 或一个具有一般属性的操作(例如 URLs 及其参数、表单等)。
使用基于组件的框架的开发人员通过实现事件驱动模型来关注组件和组件交互。 Servles、HTTP 会话和其他来自 servlet API 的实体被移动到更低的级别并且从来不会被直接使用。然而,通过声明组件及其属性,开发人员需要管理服务器端的状态,这种状态可能是会话持久性的。
与 JSF 或使用 JSP 和 Velocity 标记的 WebWorks 不同,Tapestry 和 Wicket 都使用它们自己的模板系统,允许创建完全符合 HTML 标准的 HTML 模板。这使我们可以清晰地实现关注点分离:从事 GUI 的 Web 设计者不用考虑应用程序是在何种平台之上编写;同样,实现组件的应用程序的开发人员不用考虑页面的最终设计是否使用 stub 元素进行调试和测试。 Tapestry 和 Wicket 都支持这种分离。
目前,Tapestry V4.1 是该项目的正式发行版。V5.0 目前正处于积极开发中,从团队开始从头编写 Tapestry 到撰写本文之际,已有 18 个月有余。尽管 V5.0 还没有公开发布,可以肯定地说它与先前的版本完全不同,因为它不是向后兼容的。由于不能确定 V5.0 的状态,我们主要侧重于 V4.1。
Tapestry V4.1 主要基于 HiveMind 微内核,这是控制反转(Inversion-of-Control)容器的一个严格表示。所有的 Tapestry 服务都是使用 HiveMind 注册的。
一个典型的 Tapestry 组件由以下部分组装而成:一个组件说明(一个 XML 描述符)、组件逻辑(Java™ 编程语言的一部分)、UI 布局(一个 HTML 模块)。
Wicket 架构的核心是组件 类,所有组件和标记容器对其进行扩展。组件主要负责处理其模型 — IModel 界面的一个实现。模型表示与组件实例相关的任何类型的数据并定义其行为。IModel 中一个著名的实现是 LoadableDetachableModel
,它允许传递临时数据、在呈现组件之前加载数据、在不需要时将其移除(撤销),从而减少了 Wicket 会话的大小。
Wicket 页面也是组件,如果具有状态,则它们的状态将保存在 Wicket 会话中。页面可以启用版本,因此每当页面状态发生改变(查看页面、修改其组件状态等)时,将连同一个递增的版本编号进行保存。
本节说明在 Tapestry 和 Wicket 中如何完成典型的任务。注意下面的代码片段大多数取自较大的代码源,比如组件和页面。因此,它们可能包含此处没有描述的一些业务方法的调用。
这两个框架都支持 Java 数据类型。Tapestry 采用对象图导航语言(Object-Graph Navigation Language,OGNL)。它是一种获取和设置 Java 对象属性的表达式语言。Wicket 用它独有的机制进行数据绑定。 有 两 种 方便的实现可以动态检索和更新对象属性:PropertyModel
和 CompoundPropertyModel
。
我们来查看一个应用 float
类型的示例。在 Tapestry 中,按照以下方式指定一个具有 float
值的文本字段:
<component id="weight" type="TextField"> <binding name="value" value="ognl:weight"/> <binding name="translator" value="translator:number,pattern=#.#"/> <binding name="displayName" value="literal:Weight"/> </component>
Wicket 中按照以下方式指定这种文本字段:
FormComponent field = new TextField("weight", Float.class);
field.setLabel(new Model("Weight"));
add(field);
因此,可以自然地使用简单的数据类型,比如 string
、integer
、 float
,甚至是 BigDecimal
。
作为一条经验法则,应该针对业务规则验证所有的用户输入。Tapestry 和 Wicket 都能够对表单输入字段执行服务器端或客户机端(使用 DHTML)的验证。思考一下在服务器端如何完成这个任务。在框架中将按照以下方式通过特定的验证程序进行字段输入验证:
<component id="weight" type="TextField"> ... <binding name="validators" value="validators:min=1,max=500"/> </component>
... field.add(NumberValidator.range(1, 500)); |
如果没有符合要求的验证程序,也可以定义一个自定义字段验证程序。如果产生任何验证错误,都应该向用户显示以表明输入无效。
在 Tapestry 中,我们应当在字段之上定义一个循环,跟踪默认的 ValidationDelegate
表单 bean,从而显示所有的表单错误。
清单 5. ValidationDelegate
表单 bean
<property name="currentFieldTracking"/> <component id="errors" type="For"> <binding name="source" value="beans.delegate.fieldTracking"/> <binding name="value" value="currentFieldTracking"/> </component> <component id="isInError" type="If"> <binding name="condition" value="currentFieldTracking.inError"/> </component> <component id="error" type="Delegator"> <binding name="delegate" value="currentFieldTracking.errorRenderer"/> </component>
相应的 HTML 标记如下所示:
<ul jwcid="errors"> <li jwcid="isInError"> <span jwcid="error">Form validation error </span> </li> </ul>
在 Wicket 中,FeedbackPanel
实现了显示多种错误的功能:add(new FeedbackPanel("feedback"));
。HTML 标记如下: <span wicket:id="feedback" /></div>
。
要启用客户端验证,需要将 clientValidationEnabled
参数添加到 Tapestry 的一个表单中,如下所示:
<component id="actionForm" type="Form"> <binding name="delegate" value="beans.delegate"/> <binding name="clientValidationEnabled" value="true"/> </component>
基于相同的目的,Wicket 中采用AjaxFormValidatingBehavior
类。
AjaxFormValidatingBehavior.addToAllFormComponents(form, "onkeydown"); // Add the button to submit the form using AJAX form.add(new AjaxButton("ajax-button", form) { protected void onSubmit(AjaxRequestTarget target, Form form) { target.addComponent(feedback); } protected void onError(AjaxRequestTarget target, Form form) { target.addComponent(feedback); } });
我们假设有一个分配给许多人的任务列表,并希望将它显示到一个 HTML 表中。 Tapestry 采用 For
标准组件来遍历集合,如清单 9、10、11 所示:
<component id="receivedItems" type="For"> <binding name="source" value="ognl:receivedItems"/> <binding name="value" value="ognl:currentItem"/> </component> <component id="itemId" type="Insert"> <binding name="value" value="ognl:currentItem.itemId"/> </component> <component id="subject" type="Insert"> <binding name="value" value="ognl:currentItem.subject"/> </component> <component id="creator" type="Insert"> <binding name="value" value="ognl:currentItem.creator"/> </component> <component id="recipient" type="Insert"> <binding name="value" value="ognl:currentItem.recipient"/> </component>
public abstract List getReceivedItems(); public abstract void setReceivedItems(List items); public abstract ActionItem getCurrentItem();
<tr jwcid="receivedItems"> <td><span jwcid="itemId">ID</span></td> <td><span jwcid="subject">Subject</span></td> <td><span jwcid="creator">Creator</span></td> <td><span jwcid="recipient">Recipient</span></td> </tr>
在 Wicket 中,我们采用 ListView
类并匿名地实现其 populateItem()
方法,如清单 12、13 所示:
add(new ListView("receivedItems", items) {
protected void populateItem(final ListItem item) {
ActionItem todo = (ActionItem) item.getModelObject();
item.add(new Label("itemId", String.valueOf(todo.getItemId()));
item.add(new Label("subject", todo.getSubject()));
item.add(new Label("creator", todo.getCreator()));
item.add(new Label("recipient", todo.getRecipient()));
}
});
<tr wicket:id="receivedItems"> <td><span wicket:id="itemId">ID</span></a></td> <td><span wicket:id="subject">Subject</span></td> <td><span wicket:id="creator">Creator</span></td> <td><span wicket:id="recipient">Recipient</span></td> </tr>
现在让我们实现更复杂的目标并添加一些有条件的文本呈现 — 例如,命名偶数和奇数表行的 CSS 类。因此添加以下内容。关于 Tapestry 页面说明,请参看清单 14、15:
<component id="receivedItems" type="For"> ... <binding name="index" value="ognl:currentIndex"/> <binding name="class" value="ognl:currentStyleClass"/> </component>
... public abstract int getCurrentIndex(); public String getCurrentStyleClass() { return (getCurrentIndex() % 2 == 0) ? "list-row-even" : "list-row-odd"; }
add(new ListView("receivedItems", items) { protected void populateItem(final ListItem item) { ... item.add(new AttributeModifier("class", true, new AbstractReadOnlyModel() { public Object getObject() { return (item.getIndex() % 2 == 0) ? "list-row-even" : "list-row-odd"; } })); } });
目前页面展现了所有的用户任务。然而,随着时间的推移会出现更多的任务,单个表中很快就会出现大量任务。这种情形下非常适合使用多页面表。
Tapestry 提供方便的 Table
组件及其低级别的支持,比如来自 Contrib Library 模块的 TableView
,如清单 17、18 所示:
<component id="receivedItemsView" type="contrib:TableView"> <binding name="source" value="ognl:itemsTableModel"/> <binding name="columns" value="itemId, subject, creator, recipient"/> <binding name="pageSize" value="10"/> </component> <component id="receivedItemsColumns" type="contrib:TableColumns" /> <component id="receivedItemsRows" type="contrib:TableRows" /> <component id="receivedItemsValues" type="contrib:TableValues" /> <component id="receivedItemsPages" type="contrib:TablePages"> <binding name="pagesDisplayed" value="10"/> </component>
public IBasicTableModel getItemsTableModel() { return new IBasicTableModel() { public int getRowCount() { return getActionItemManager().getActionItemsCountByRecipient(uid); } public Iterator getCurrentPageRows(int nFirst, int nPageSize, ITableColumn objSortColumn, boolean bSortOrder) { return getActionItemManager() .getActionItemsListByRecipient(uid, nFirst, nPageSize); } }; }
<span jwcid="table"> <table> <tr><span jwcid="receivedItemsColumns" class="title"/></tr> <tr jwcid="receivedItemsRows"> <td jwcid="receivedItemsValues"/> </tr> </table> <span jwcid="receivedItemsPages"/> </span>
Wicket 一同提供了一个 DataView
类和一个 IDataProvider
实现,如下所示:
public class ItemsDataProvider implements IdataProvider {
public Iterator iterator(int first, int count) {
return getActionItemManager().getActionItemsListByRecipient(uid, first, count);
}
public int size() {
return getActionItemManager().getActionItemsCountByRecipient(uid);
}
public IModel model(Object object) {
return new LoadableDetachableModel(object);
}
}
public ListActionItems extends WebPage {
public ListActionItems() {
DataView dataView = new DataView("receivedItemsView", new ItemsDataProvider()) {
protected void populateItem(final Item item) {
ActionItem todo = (ActionItem) item.getModelObject();
item.add(new Label("itemId", String.valueOf(todo.getItemId()));
item.add(new Label("subject", todo.getSubject()));
item.add(new Label("creator", todo.getCreator()));
item.add(new Label("recipient", todo.getRecipient()));
}
};
dataView.setItemsPerPage(10);
add(dataView);
add(new PagingNavigator("receivedItemsPages", dataView));
}
}
清单 21. Wicket 的 DataView
HTML 标记
<table>
<tr>
<th>ID</th>
<th>Subject</th>
<th>Creator</th>
<th>Recipient</th>
</tr>
<tr wicket:id="receivedItemsView">
<td><span wicket:id="itemId">ID</span></td>
<td><span wicket:id="subject">Subject</span></td>
<td><span wicket:id="creator">Creator</span></td>
<td><span wicket:id="creationDate">Creation Date</span></td>
<td><span wicket:id="deadline">Deadline</span></td>
</tr>
</table>
<span wicket:id="receivedItemsPages">Paging navigator links</span>
开发一个多语言应用程序时,国际化支持在制定决策时起着至关重要的作用。 Tapestry 和 Wicket 能够提供几乎无限的本地化功能。你可以本地化组件文本(标签、消息 — 常见内容)以及像图片这样的静态资源,甚至可以本地化整个标记模板,只要将具有本地标识符的后缀添加到本地化资源名称中,如下所示:
清单 22. Tapestry 和 Wicket 中的本地化
ListActionItems.html ListActionItems.properties ListActionItems_ru_RU.html ListActionItems_ru_RU.properties |
当访问一个来自页面说明或模板的本地化消息时,在 Tapestry 组件参数中采用 message: 前缀。Wicket 中有一种功能强大的协助完成本地化任务的 StringResourceModel
类。
Tapestry 和 Wicket 使用自己的格式生成 URLs,这看起来似乎不能使用书签功能。这导致很难引用页面。但是这个框架也提供了这种情况的解决方案。
在 Tapestry 中,从外部引用的页面应该实现 IExternalPage
接口,如下所示:
清单 23. Tapestry 实现 IExternalPage
public abstract class ViewActionItem extends BasePage implements IExternalPage {
public abstract Integer getItemId();
public abstract void setItemId(Integer itemId);
public void activateExternalPage(Object[] params, IRequestCycle cycle) {
setItemId((Integer) params[0]);
}
public void pageBeginRender(PageEvent pageEvent) {
ActionItem todo = getActionItemManager().getActionItemByItemId(getItemId());
}
}
此外,页面可以通过以下网址引用 http://host/webapp/tapestryapp?page=ViewActionItem&service=external&sp=123
。 然而这不是直接访问 Tapestry 页面的惟一方法。我们可以采用 HiveMind 服务编码器 — 页面服务编码器 — 这个编码器能够把页面名字编码成一些在 URL 中可读的内容。
清单 24. HiveMind 配置:hivemodule.xml
<module id="com.ibm.cit.tvw" version="1.1.0"> <contribution configuration-id="tapestry.url.ServiceEncoders"> <page-service-encoder id="page" extension="html" service="page"/> </contribution> </module> |
不要忘记将扩展名 .html 的 servlet 映射添加到 web.xml 中,如下所示:
清单 25. 在 hivemodule.xml 中添加 servlet 映射
<servlet-mapping> <servlet-name>TodolistApplication</servlet-name> <url-pattern>*.html</url-pattern> </servlet-mapping> |
从现在开始,每个应用程序页面都可以被访问,比如, http://host/webapp/ListActionItems.html。可以对它添加书签。注意,不能以这种方式提供请求参数 — 页面可以在没有参数的情况下调用(或使用默认参数)。
在 Wicket 中,应该只将具有 PageParameters
的页面构造函数定义为参数,如下所示:
清单 26. Wicket 和 PageParameters
public class ListActionItems extends WebPage { public ListActionItems(PageParameters parameters) { int id = parameters.getInt("id"); // do something } } |
稍后,这个页面可以引用为 http://host/webapp/wicketapp/?wicket:bookmarkablePage=%3Acom.ibm.cit.tvw.wicket.page.ViewActionItem&id=123。你也可以将页面挂载到理想的路径,如下所示:
public class ToDoListApplication extends WebApplication { protected void init() { mountBookmarkablePage("/apath/login.html", Login.class); } } |
当操作页面 URL 时,上述方法能实现最大的灵活性。
现在我们开始讨论 breadcrumbs。breadcrumbs 是客户机的操作历史。Wicket 有两个接口针对 breadcrumbs: IBreadCrumbModel
和 IBreadCrumbParticipant
,其中模型使用一个活动的 breadcrumbs 将参与者表示为一个列表。模型接口 BreadCrumbBar
的一个有用的实现包含一个 breadcrumbs 参与者堆栈。如果还没有开始一个新的参与者,则它将被放入堆栈的顶部 — 在本例中,对新参与者关闭了堆栈。
另一方面,Tapestry 并没有为 breadcrumbs 提供任何现成的解决方案。
接下来,我们开始讨论 Back 按钮。Web 浏览器的 Back 按钮一直以来都是 Web 应用程序开发人员想要消除的东西,希望以此避免使用它带来的意想不到的影响。但它仍然存在,因此应用程序应该对任何相关事件正确响应。
在这种上下文中,Wicket 因为使用页面版本管理(page-version management)而大放异彩,如果启用了页面版本化,可以很轻松地将页面状态恢复到以前的版本。页面状态由 VersionManager
管理,状态改变保存在用户的会话中。但是要慎用这些特性。
那么 Tapestry 能提供什么呢?Tapestry 中没有什么简单的方法实现上述功能,应用客户端持久性是一种选择,但这是一个很难处理的问题,需要进行一些特殊的考虑。
在基于组件的框架中,重用自定义组件是最主要、最强大的功能之一。你可以描述自己的组件,这些组件可以是整个页面,也可以是较小的面板,甚至是单独的一个 HTML 元素。通过组件继承,可以很自然地设计组件模板,并且不会产生什么问题。一旦创建了这些模块,你可以根据需要将其多次插入应用程序中的任何位置。Tapestry 和 Wicket 对管理组件的重用没有任何限制,但它们的方式各有不同。
当自定义组件可以应用到各种应用程序时,将变得真正可重用。Wicket 仅仅通过创建一个包含组件类和标记模板的 JAR 归档就可以进行打包。把归档自动放入应用程序的类路径中将使组件变得可用。Tapestry 需要另外一些步骤来定义一个自定义组件库 — JAR 归档应该包含一个带有库规范元素的 XML 描述符。使用这个库的应用程序应该在该应用程序的配置文件中指定这个库。
可重用组件的一个最棒的应用是提供了普通导航面板、页眉和页脚、说明栏等内容的页面模板。接下来的模块示例是一个显示主页链接、当前登录用户的名称和 Logout 按钮的组件。
在 Tapestry 中,模板组件规范如清单 28, 29,和 30 所示:
<component-specification allow-body="yes" allow-informal-parameters="no" class="com.ibm.cit.tvw.tapestry.component.Template"> <!--HTML head --> <asset name="stylesheet" path="/style.css" /> <parameter name="pageTitle" default-value="literal:Todolist Application" /> <component id="shell" type="Shell"> <binding name="stylesheet" value="assets.stylesheet" /> <binding name="title" value="ognl:pageTitle" /> </component> <!-- HTML body --> <component id="body" type="Body" /> <!-- The name of the currently logged user --> <component id="user" type="Insert"> <binding name="value" value="ognl:currentUser"/> </component> <!-- The home page --> <component id="homeLink" type="PageLink"> <binding name="page" value="literal:ListActionItems" /> </component> <!-- User Profile page --> <component id="profileLink" type="DirectLink"> <binding name="listener" value="listener:onEditUserProfile"/> </component> <!-- Logout link --> <component id="logoutLink" type="ServiceLink"> <binding name="service" value="ognl:@org.apache.tapestry.Tapestry@RESTART_SERVICE"/> </component> <!-- Page content --> <component id="pageContent" type="RenderBody"/> </component-specification>
清单 29. Tapestry 的组件模板规范 Java 类
public abstract class Template extends BaseComponent {
public String getCurrentUser() {
return getPage().getRequestCycle().getInfrastructure()
.getRequest().getUserPrincipal();
}
}
清单 30. Tapestry 的组件模板规范 HTML 标记
<html jwcid="shell">
<body jwcid="body">
<div id="top">
<div id="header">
<a jwcid="homeLink" class="homelink">Action Items Sample Application</a>
Current user: <span jwcid="user">Davy Jones</span>
<a jwcid="logoutLink">Logout</a></span>
</div>
<div id="page-content">
<span jwcid="pageContent">This is a page content.</span>
</div>
</div>
</body>
</html>
使用模板的其他页面如清单 31 所示,这是 ViewActionItem
页面的一个例子。
<page-specification> <component id="template" type="Template"> <binding name="pageTitle" value="literal:View Action Item"/> </component> ... </page-specification> |
ViewActionItem
<span jwcid="template"> <!-- Page content --> ... </span> |
在 Wicket 中,模板被表示为一个基本页面,如清单 33 和 34 所示:
public abstract class TemplatePage extends WebPage {
private String title;
public TemplatePage() {
add(new Label("title", new PropertyModel(this, "title")));
add(new BookmarkablePageLink("homeLink", getApplication().getHomePage()));
add(new Label("user", ((WebRequest) getRequest()).getHttpServletRequest()
.getUserPrincipal().getName()));
add(new Link("logoutLink") {
public void onClick() {
getSession().invalidate();
getRequestCycle().setResponsePage(getHomePage());
getRequestCycle().setRedirect(true);
}
});
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns:wicket="http://wicket.apache.org/">
<head>
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<link rel="stylesheet" type="text/css" href="style.css" />
<title wicket:id="title">Sample Page Title</title>
</head>
<body>
<div id="top">
<div id="header">
<a wicket:id="homeLink" class="homelink">Action Items Sample Application</a>
Current user: <span wicket:id="user">Davy Jones</span>
<input type="button" class="button" wicket:id="logoutLink" value="Logout" /></span>
</div>
<div id="page-content">
<wicket:child />
</div>
</div>
</body>
</html>
应用这个模板的子页面应该继承模板页面,如清单 35 和 36 所示:
public class ViewActionItem extends TemplatePage { public ViewActionItem() { setTitle("View Action Item"); ... } } |
<html xmlns:wicket="http://wicket.apache.org/"> <head></head> <body> <wicket:extend> <!-- Page content --> ... </wicket:extend> </body> </html> |
我们开始讨论框架对比中最有趣的地方:Asynchronous JavaScript + XML (Ajax) 的使用。
您还记得您的第一个使用 Ajax 的 Web 应用程序以及在实现时付出的艰辛吗? 没错,为了实现多浏览器的支持,所有 JavaScript 通信和数据解析代码都包含了大量技巧。客户端的实现是最麻烦的工作,与之相比,Servlet 工作更加轻松。
然而,现在随着诸如 Tapestry 和 Wicket 这类框架的使用,在一个应用程序中增加一个 Ajax 操作只需要几行代码和几分钟的时间。这种操作非常简单。以下的示例描述了一种简单的场景:单击 to-dos 列表中的某一项将使用相应的数据更新(以 Ajax 模式)预览面板。
Tapestry 将链接的 async
参数设置为 true,并且,如果必要的话,定义链接应该更新的组件的列表,如清单 37、38 和 39 所示:
<component id="previewReceivedItem" type="DirectLink"> <binding name="listener" value="listener:onPreviewItem"/> <binding name="parameters" value="ognl:currentItem.itemId"/> <binding name="async" value="true"/> <binding name="updateComponents" value="ognl:{'preview'}"/> </component> <component id="preview" type="Any"/> <component id="previewSubject" type="Insert"> <binding name="value" value="ognl:previewSubject"/> <binding name="renderTag" value="true"/> </component> <component id="previewState" type="Insert"> <binding name="value" value="ognl:previewState"/> <binding name="renderTag" value="true"/> </component>
public abstract class ListPreviewActionItems extends BasePage {
public abstract String getPreviewSubject();
public abstract void setPreviewSubject(String previewSubject);
public abstract String getPreviewState();
public abstract void setPreviewState(String PreviewState);
public void onPreviewItem(Integer itemId) {
if (itemId != null) {
ActionItem pitem = getActionItemManager().getActionItemByItemId(itemId);
setPreviewSubject(pitem.getSubject());
setPreviewState(pitem.getCurrentState());
}
}
}
清单 39. Tapestry 页面说明片段 HTML 标记
<h3>Preview Action Item</h3> <div jwcid="preview"> Subject: <span id="previewSubject" jwcid="previewSubject">subject</span><br/> State: <span id="previewState" jwcid="previewState">current state</span> </div> |
Wicket 中只使用具有已实现的 onClick()
方法的 AjaxLink
,如清单 40 和 41 所示:
public class ListPreviewActionItems extends WebPage {
public ListPreviewActionItems() {
final LoadableActionItemModel previewItem = new LoadableActionItemModel(null);
WebMarkupContainer preview = new WebMarkupContainer("preview");
preview.setOutputMarkupPlaceholderTag(true);
preview.add(new Label("previewSubject", new PropertyModel(previewItem, "subject")));
preview.add(new Label("previewState", new PropertyModel(previewItem, "currentState")));
add(preview);
AjaxLink previewReceivedItem = new AjaxLink("previewReceivedItem") {
public void onClick(AjaxRequestTarget target) {
previewItem.setItemId(currentItem.getItemId());
target.addComponent(previewContainer);
}
};
add(previewReceivedItem);
}
}
清单 41. Wicket 的 AjaxLink
HTML 标记
<h3>Preview Action Item</h3> <div wicket:id="preview"> Subject: <span wicket:id="previewSubject">subject</span><br/> State: <span wicket:id="previewState">current state</span> </div> |
就这么简单。框架在幕后执行了通信、数据处理及所有其他棘手的工作。
关于 Tapestry 和 Wicket 框架开发过程之间的区别,没有太多可介绍的。使用支持 Web 应用程序开发的大多数 Java IDE 实现 Tapestry 组件非常简单。Wicket 需要的更少 — 任何 Java IDE 加上 HTML 编辑器都能满足开发需要。
对于 Java 和标记中的组件模型树,偶尔会遇到同步问题,但是每次调用组件时您都会收到通知。这与其他框架相反,比如 Google Web Toolkit,后者可以在编译时处理这些问题。
供 IBM WebSphere Application Server CE 使用
Tapestry 和 Wicket 是与 Java Web 应用程序兼容的框架,所以可以像往常一样构建和打包 Web 应用程序。要把这个应用程序部署到 IBM® WebSphere® Application Server Community Edition,提供了一个嵌入式或单独的应用程序开发计划,该计划也是具有代表性的。
Tapestry 和 Wicket 是基于组件的 Web 框架的典型代表,并且可能是这类框架中最杰出的。借助它们的帮助,可以构建各种复杂的应用程序。当然,CMS 可以更好地管理具有静态信息的页面集合,然而当论及一个应用程序的多用户交互时,框架展示了其优势。
尽管框架架构之间有许多相似之处(二者都面向组件并可以实现清晰的关注点分离),它们的实现仍然有很大的区别。Tapestry 在组件规范中提供一个声明式方法,而 Wicket 提供了没有 XML 和注解的纯粹的 Java 编程。Wicket 在页面版本化和多窗口支持方面具有一些优势。Wicket 还提供一些有帮助的工具,包括 breadcrumbs、反馈面板和一些在动态 HTML 中应用的独特特性,比如选项卡式面板和对话框。
如果想要尽量避免使用 XML,或者只喜欢面向对象编程,日常使用时可以选择 Wicket。 Wicket 也提供了一个更丰富的扩展库。
最后,Tapestry 正处于过渡时期。Tapestry V5 在 V4.1 的基础上承诺了很多增强,但是没有规划向后兼容。因此,用 V4.1 编写的应用程序可能还会继续使用,但那些从使用当前 Tapestry 获益的专业开发人员可能不会长期受益。