这个分为四部分的 系列文章 的 第 1 部分 提供了关于 GWT 和 XForms 的坚实基础,并研究了如何结合使用这两种技术简化 Web 应用程序的创建过程。在第 2 部分中,您将构建一个简单的 Web 应用程序,即摇滚巨星应用程序,此程序拥有两个页面:一个用于查看艺术家,另一个用于查看这些艺术家录制的专辑。第一个页面将使用 GWT 构建,它将使用 GWT 的小部件和 GWT 的 Ajax 抽象。该页面将链接到第二个页面,后者使用 XForms 构建并使用 XForms 数据模型和 XForms 控件创建自己的 UI。
前提条件
加快学习速度!请阅读本系列的 第 1 部分,介绍 GWT 的 JavaScript Native Interface 。
本文使用 GWT 1.4 和 Mozilla XForms 插件 0.8(相关链接请参阅 参考资料 部分)。这个 Mozilla XForms 插件可用于任何基于 Mozilla 的 Web 浏览器,如 Firefox 和 Seamonkey。使用 GWT 需要了解 Java? 语言的知识和一些 Web 技术如 HTML、CSS。文中还用到了大量 JavaScript 代码。XForms 大量采用模型-视图-控制器范型,因此熟悉 MVC 就足够了。原来接触过 XForms 和 GWT 当然很好,但不是必需的。本文中的代码是使用 Eclipse 3.3 开发的,但并不需要了解 Eclipse。
回页首
使用 GWT 管理艺术家
构建摇滚巨星应用程序所需做的第一件事是获取所有艺术家的列表。当然,您也希望可以向列表中添加新艺术家。列表还将为您提供浏览到第二个页面的方式,在第二个页面中,您将能够管理某个艺术家所录制的专辑。您将为艺术家页面使用 GWT。您将使用其内置的小部件为页面创建漂亮的 UI,并使用其 Ajax 抽象简化数据的加载和保存。
应用程序数据
应用程序中的数据将存储于简单的 XML 文件中。XML 是传输数据的一种常见格式,当然也是 XForms 原生的。您可以轻松地将数据保存到关系数据库中,但是您也可以很好地将其序列化为 XML 格式以用于不同目的。使用 XML(而不使用关系数据库)使数据更加简单,从而可以将精力集中在应用程序的关键部件:GWT 和 XForms 之上。
带 GWT 的艺术家模型
应用程序需要使用一个简单的数据模型表示艺术家。使用 GWT 可使该过程与创建一个简单的 Java bean 一样容易,如清单 1 所示。
清单 1. 艺术家模型:Java Bean
package org.developerworks.rockstar.client;
import com.google.gwt.user.client.rpc.IsSerializable;
public class Artist implements IsSerializable{
private int id;
private String name;
private String genre;
public Artist(){
// needed for GWT's RPC mechanism
}
public Artist(int id, String name, String genre) {
this.id = id;
this.name = name;
this.genre = genre;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGenre() {
return genre;
}
public void setGenre(String genre) {
this.genre = genre;
}
}
如前所述,这只是一个标准的 Java bean:三个字段带有相应的 getters 和 setters。您将对艺术家设置一个数值 ID 以方便引用。如果使用数据库表保存艺术家,那么可能也需要使用此 ID。要注意的是,它位于应用程序的客户机包中。因此这个类将被编译为 JavaScript。但是这对您没什么影响。您仍然可以使用 Java 编程,无需考虑后面的 JavaScript。最后,注意您的类实现了 IsSerializable 标记器接口。任何需要通过网络发送的类(比如,Ajax 调用的请求或响应的一部分)都需要此接口。现在,考察一下如何使用 GWT 为 Artist 对象列表创建 UI。
回页首
为艺术家列表使用 GWT 小部件
GWT 的一个优秀特性是,带有一组小部件可用于创建通用的 UI 结构。您可以使用熟悉的 Java 语法创建 UI 元素。如果您从事过 Swing 或 SWT 编程,则会对此非常熟悉。此时,您将使用 FlexTable。这是一个非常适合应用程序的动态表,因为没有设定艺术家的数目。FlexTable 可以进行扩展以适合艺术家的数目。填充 FlexTable 的代码如清单 2 所示。
清单 2. 创建一个艺术家表
private void populateTable(Artist[] artists){
// clear the table
int rowCount = this.artistTable.getRowCount();
for (int i=0; i
this.artistTable.removeRow(i);
}
// create the header
this.artistTable.getRowFormatter().addStyleName(0, "tableHeader");
this.artistTable.setText(0, 0, "Name");
this.artistTable.setText(0, 1, "Genre");
// now add artists
for (int i=0; i this.artistTable.setText(i+1, 0, artists[i].getName());
this.artistTable.setText(i+1, 1, artists[i].getGenre());
}
this.artistTable.setBorderWidth(4);
}
如果已经存在表,那么 清单 2 中的代码将清空该表。然后创建一个表头。表头的惟一特点是使用了样式。我们可以大致观察一下。代码接下来对艺术家进行迭代,将其添加到表中。我们来观察一下清单 3 中添加了表的页面。
清单 3. 艺术家页面
RockStars
HTML 没有太多工作要做!所有的难题都由 GWT 解决。HTML 所做的惟一工作就是为表头内置了一些 CSS。现在,页面已具备了 UI 元素,因此只需添加一些数据即可。您将使用 GWT 风格的 Ajax 获取数据。
回页首
获取艺术家数据:使用远程过程调用
您所拥有的优秀 GWT 部件可以用来显示艺术家列表。现在,您只需要一个列表。为此,您将创建管理艺术家的服务。您将使用 GWT(如 Ajax)异步调用此服务。这是一种典型的 GWT 远程过程调用(RPC)。首先声明服务的接口,如清单 4 所示。
清单 4. Artist Service Interface
package org.developerworks.rockstar.client;
import com.google.gwt.user.client.rpc.RemoteService;
public interface ArtistService extends RemoteService {
public Artist[] getAllArtists();
public void addArtist(Artist newArtist);
}
注意,服务扩展了 GWT 标记器接口 RemoteService。这是一个必需的 GWT 约定。另外还要注意,getAllArtists() 调用返回了一组 Artist 对象。这些是先前定义的与数据模型相同的 Artist 对象。您可能希望让它返回 Collection 或 List,但是不要这样做。GWT 在客户机代码中不支持泛型,例如编译为 JavaScript 的代码。JavaScript 中没有范型,而且运行时泛型信息也不可用。您也可以返回 java.util.List,因为这是允许的,但是数组就可以很好地满足要求并且具有强类型。
GWT 需要使用从客户机调用的所有接口的异步版本。接口相应的异步版本如清单 5 所示。
清单 5. 异步服务接口
package org.developerworks.rockstar.client;
import com.google.gwt.user.client.rpc.AsyncCallback;
public interface ArtistServiceAsync {
public void getAllArtists(AsyncCallback callback);
public void addArtist(Artist newArtist, AsyncCallback callback);
}
这里的关键是服务名的 Async 后缀。这是一种命名约定,可使 GWT 将此接口与 清单 4 中所示的接口匹配。另外还要注意,所有调用实现异步的方式,这说明这些调用拥有 void 返回类型。它们都拥有一个 AsyncCallback,这使 GWT 能够在接口服务器端实现处理完收到的请求后,调用回调函数。让我们观察一下接口的服务器端实现,如清单 6 所示。
清单 6. 接口的服务器端实现
package org.developerworks.rockstar.server;
import java.util.List;
import org.developerworks.rockstar.client.Artist;
import org.developerworks.rockstar.client.ArtistService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
public class ArtistServiceImpl extends RemoteServiceServlet implements ArtistService {
private static final long serialVersionUID = -1801240935065207659L;
private List artists;
private ArtistDao dao;
public ArtistServiceImpl(){
this.dao = new ArtistFileDao();
this.artists = this.dao.getAllArtists();
}
public void addArtist(Artist newArtist) {
newArtist.setId(this.artists.size());
this.artists.add(newArtist);
dao.saveArtists(this.artists);
}
public Artist[] getAllArtists() {
Artist[] array = new Artist[this.artists.size()];
return this.artists.toArray(array);
}
}
关于这个类有几个需要注意的地方。其一是它扩展了 RemoteServiceServlet 并实现了您定义的接口。RemoteServiceServlet 是一个用于处理 Ajax 请求的典型 Java servlet。运行时,客户机代码的请求将调用超类的方法,而超类将使用反射调用客户机请求的实际方法。最后,注意,您将使用 Data Access Object (DAO) 接口 ArtistDao。您将使用基于文件的实现 ArtistFileDao,但是您可以轻松地将其交换为基于数据库的实现。该类还拥有处理读/写文件及解析 XML 的代码。我们来测试一下应用程序。
回页首
查看艺术家:托管模式
到目前为止,所有内容都是纯 GWT 的。GWT 的一个优秀特性就是您可以使用托管模式。如果您在使用 Eclipse,则可以从中启动应用程序并进行查看。但是您首先需要一些数据。清单 7 给出了一个简单的数据文件。
清单 7. 测试数据
0
The Struts Five
Classic Rock
1
Spring Flow
Techno
2
The Holy Grails
Funk
3
The Rails Way
Pop
4
Cake Clone
Pop
5
Obscure Tapestry
Techno
6
Dojo Darling
Classic Rock
7
Cairingorm
Progressive
8
ProtoStripes
Thrash
现在您已拥有测试数据,可以开始启动应用程序。应该出现图 1 所示的界面。
图 1. 以托管模式查看艺术家
这是测试数据中的艺术家列表。现在您只需一个简单的表单输入新艺术家。
回页首
添加新艺术家:使用 GWT 创建表单
您需要一个简单的数据输入表单以输入新艺术家。所幸的是 GWT 也包括了创建表单的小部件。您可以使用 GWT 通过编程创建表单,如清单 8 所示。
清单 8. 使用 GWT 的数据输入表单
public class RockStarMain implements EntryPoint {
// Widgets for the page
final FlexTable artistTable = new FlexTable();
final VerticalPanel outerPanel = new VerticalPanel();
final HorizontalPanel formPanel = new HorizontalPanel();
final Label artistLabel = new Label("Artist Name:");
final TextBox artistInput = new TextBox();
final Label genreLabel = new Label("Genre:");
final TextBox genreInput = new TextBox();
final Button addButton = new Button("Add Artist");
/**
* This is the entry point method.
*/
public void onModuleLoad() {
// add the outer panel, then add to it
RootPanel.get().add(outerPanel);
outerPanel.add(artistTable);
outerPanel.add(formPanel);
// arrange form elements horizontally
formPanel.add(artistLabel);
formPanel.add(artistInput);
formPanel.add(genreLabel);
formPanel.add(genreInput);
formPanel.add(addButton);
// add event listener to our button
ClickListener listener = new ClickListener(){
public void onClick(Widget sender) {
addNewAritst();
}
};
addButton.addClickListener(listener);
// load the artists now all the widgets are ready
this.loadArtists();
}
这段代码同样非常简单。您创建了一组小部件:标签、文本框和按钮(以及您一直使用的 FlexTable)。您还将使用几个面板,充当布局管理器处理小部件的布局。然后将事件侦听程序添加到一个按钮上以添加新艺术家。事件侦听程序将调用后端服务。我们来查看一下清单 9 中的代码。
清单 9. 添加新的艺术家方法
private void addNewAritst(){
Artist artist = new Artist(-1, artistInput.getText(), genreInput.getText());
ArtistServiceAsync artistService = getArtistService();
AsyncCallback callback = new AsyncCallback(){
public void onFailure(Throwable caught) {
// remove last row because of failure
removeLastArtist();
}
public void onSuccess(Object result) {
// nothing to do here since we added optimistically
}
};
artistService.addArtist(artist, callback);
// we'll be optimistic and go ahead and add to the table
int size = this.artistTable.getRowCount();
this.artistTable.setText(size, 0, artist.getName());
this.artistTable.setText(size, 1, artist.getGenre());
}
在这段代码中,您看到了被调用接口的异步版本。创建了一个异步回调方法。在本例中该方法非常简单,并且只在调用失败时使用。因为您可以继续操作,将艺术家添加到表中,而不用等待服务器响应。这就提供了很好的用户体验,因为可以即时添加艺术家,但是这里假定很少出现调用失败。本文最后有完整的 源代码下载。我们来查看一下添加完表单后的 UI,如图 2 所示。
图 2. 查看艺术家并添加新艺术家
填写表单并单击 Add Artist 按钮。UI 应立即被更新,如图 3 所示。
图 3. 添加的艺术家
您已经了解了使用 GWT 创建 UI 元素和使用 Ajax 检索数据及将数据保存到后端服务的方式。我们来看看如何使用 XForms 创建管理专辑的页面。
回页首
使用 XForms 管理专辑
应用程序的第二部分将按艺术家查看专辑列表。自然地,您会希望创建单个的页面为每个艺术家显示专辑。那么您如何知道应该显示哪个专辑呢?很明显,有几种不同的方法可以实现此功能,但是我们将使用命令模式。命令会告诉您创建页面所需的所有信息。这样,您需要了解要显示哪个专辑,换言之,哪个艺术家的专辑。为此,您需要一个 request 参数指定艺术家,因此直接称之为 artistId。您需要一个链接从第一个页面链接到第二个页面,而该链接需要使用 artistId 参数。
回页首
从 GWT 页面到 XForms 页面的链接
我们回到第一个页面,创建一个到第二个页面的链接。这只需要稍微修改一下代码,如清单 10 所示。
清单 10. 添加指向 Artist 列表的链接
private void populateTable(Artist[] artists){
// clear the table
int rowCount = this.artistTable.getRowCount();
for (int i=0; i this.artistTable.removeRow(i);
}
// create the header
this.artistTable.getRowFormatter().addStyleName(0, "tableHeader");
this.artistTable.setText(0, 0, "Name");
this.artistTable.setText(0, 1, "Genre");
// now add artists
for (int i=0; i //this.artistTable.setText(i+1, 0, artists[i].getName());
String html = " "+artists[i].getId()+"\">"+artists[i].getName()+"";
this.artistTable.setHTML(i+1, 0, html);
this.artistTable.setText(i+1, 1, artists[i].getGenre());
}
this.artistTable.setBorderWidth(4);
}
修改的代码是用于设置表中左侧专辑的代码。您没有使用 setText(...),而是使用了 setHTML(...)。这让您能够将 HTML 置于表中,从而创建一个简单的指向新页面 “Albums.jsp” 的链接。您使用了一个 JSP,因此可以构建动态页面。动态部分就是只显示由 artistId 指定的艺术家所录制的专辑。注意,artistId 参数是链接的一部分。现在您只需创建此页面即可。
回页首
使用 GWT 创建 XForms 页面
您可以像创建其他 Web 资源(如 HTML 或 CSS 页面)那样创建 JSP。您也可以在页面上使用 GWT。所需做的惟一操作就是引用 GWT 生成的 JavaScript 文件。现在,您将使用服务器端代码加载指定艺术家录制的专辑。您将把该数据直接写入 XForms 模型实例数据,如清单 11 所示。