这是第 3 部分,将进一步完善 第 2 部分 建立的摇滚巨星应用程序。通过该应用程序,唱片公司经理可以管理旗下的歌手和他们的唱片。第 2 部分已经具备的功能仍然保留。但是功能的实现将改为混合使用 GWT 和 XForms。通过这个例子将看到很容易在现有的页面中加入 GWT。我们将使用 GWT 式的 Ajax 调用动态加载数据,然后利用 GWT 的 JSNI 动态创建 XForms 模型。这样可以简化页面,如果使用 GWT JSNI 动态创建 XForms 控件甚至还能进一步简化页面。结合使用 GWT 元素不仅可以简化服务器端代码,而且创建的初始页面更小,下载和呈现的速度更快。
本文使用了 GWT 1.4 和 Mozilla XForms 0.8 插件(下载链接参见 参考资料)。Mozilla XForms 插件可用于任何基于 Mozilla 的 Web 浏览器,如 Firefox 和 Seamonkey。使用 GWT 需要对 Java™ 语言以及 HTML 和 CSS 这样的 Web 技术有所了解。文中还用到了大量 JavaScript 代码。XForms 广泛采用模型-视图-控制器(MVC)范型,因此熟悉 MVC 将很有帮助。以前接触过 XForms 和 GWT 当然很好,但并非必须如此。
到目前为止,看到的 XForms 呈现模型实例数据都是放在页面本身之中的。模型数据是动态的。您使用了 JSP 脚本查询和筛选数据,然后将 XML 写入页面。现在我们将进一步提高 XForms 页面的动态性。不再把实例数据写入页面,而是从服务器异步检索数据然后将其动态增加到页面模型中。我们使用 GWT 进行 Ajax 调用,使用 GWT JSNI 修改 XForms 模型。
我们将在 XForms 唱片页面上使用 GWT。这就带来了一个问题,即如何向已有的网页添加 GWT?非常简单。只需要包括 GWT Java-JavaScript 编译器从代码生成的 JavaScript 库。每个模块一个文件。在 XForms 页面中就变成了清单 1 中所示的代码行。
清单 1. 向唱片页面引入 GWT
<xhtml:script language="text/javascript"
src="org.developerworks.rockstar.RockStarMain.nocache.js"></xhtml:script>
|
在页面中加入 GWT 如此简单,开发人员常常惊讶不已。毫无疑问这正是设计时的初衷。大部分关于 GWT 的例子都是 “green field” 应用程序,换句话说,就是都是使用 GWT 从头开始创建的。但实际上遗留应用程序至少一样常见,这两种情形 GWT 都适用。要记住,所有的 GWT 都只不过是 JavaScript。所以这么简单也就情有可原了。现在在页面中加入了 GWT,可以开始编写 GWT 代码(或者说将被编译成 JavaScript 的 Java 代码)了。
使用 Ajax 载入唱片数据:用 JSNI 操作 XForms 数据
本系列文章的 第 2 部分 中使用 GWT 向页面加载歌手的名单。该操作是异步完成的。换句话说,创建了页面然后发出 Ajax 请求来获取歌手名单。现在我们要对唱片采用同样的技术。因此需要一种服务来使用 GWT Ajax 加载唱片。
首先要声明服务接口,如清单 2 所示。
清单 2. Album Service 接口
package org.developerworks.rockstar.client;
import com.google.gwt.user.client.rpc.RemoteService;
public interface AlbumService extends RemoteService{
public Album[] getAlbumsForArtist(int artistId);
public void addAlbum(Album newAlbum);
}
|
和第 2 部分中创建的服务差不多。类似的,还需要服务的异步版本,如清单 3 所示。
清单 3. 异步服务接口声明
package org.developerworks.rockstar.client;
import com.google.gwt.user.client.rpc.AsyncCallback;
public interface AlbumServiceAsync {
public void getAlbumsForArtist(int artistId, AsyncCallback callback);
public void addAlbum(Album newAlbum, AsyncCallback callback);
}
|
最后还需要服务的服务器端实现,该实现必须扩展 GWT 的 RemoteServiceServlet,以便客户机能够使用 HTTP 调用它。如清单 4 所示。
清单 4. Album 服务实现
package org.developerworks.rockstar.server;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.developerworks.rockstar.client.Album;
import org.developerworks.rockstar.client.AlbumService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
public class AlbumServiceImpl extends RemoteServiceServlet implements AlbumService {
private static final long serialVersionUID = -2706402745094297460L;
private Map<Integer,List<Album>> albumCache;
private AlbumDao dao;
public AlbumServiceImpl(){
this.dao = new AlbumFileDao();
List<Album> allAlbums = this.dao.getAllAlbums();
// initialize cache
int size = allAlbums.size();
this.albumCache = new HashMap<Integer, List<Album>>(size);
for (Album album : allAlbums){
int artistId = album.getArtistId();
List<Album> albums = this.albumCache.get(artistId);
if (albums == null){
albums = new ArrayList<Album>();
this.albumCache.put(artistId, albums);
}
albums.add(album);
}
}
public void addAlbum(Album newAlbum) {
int artistId = newAlbum.getArtistId();
List<Album> albums = this.albumCache.get(artistId);
if (albums == null){
albums = new ArrayList<Album>();
this.albumCache.put(artistId, albums);
}
albums.add(newAlbum);
List<Album> all = this.getAllAlbums();
this.dao.saveAlbums(all);
}
public Album[] getAlbumsForArtist(int artistId) {
List<Album> albums = this.albumCache.get(artistId);
if (albums == null){
return null;
}
Album[] array = new Album[albums.size()];
array = albums.toArray(array);
return array;
}
private List<Album> getAllAlbums(){
List<Album> allAlbums = new ArrayList<Album>();
for (int artistId : this.albumCache.keySet()){
List<Album> albums = this.albumCache.get(artistId);
allAlbums.addAll(albums);
}
return allAlbums;
}
}
|
另外,我们已经使用了 Data Access Object (DAO) 模式来抽象物理数据的管理(从文件系统检索数据、解析 XML 等)。这样很容易就能把过于简单化的基于 XML 文件的实现改成更加标准的数据库驱动实现。现在 GWT 代码可以使用新建立的服务了。
org.developerworks.rockstar.client 包中的所有代码都将被编译成 JavaScript,可以从包含该 JavaScript 库的任何页面中调用,如清单 1 所示。因此我们将创建一个新的 Java 类来处理唱片页面。源代码如清单 5 所示。
清单 5.
AlbumLib 类
package org.developerworks.rockstar.client;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
public class AlbumLib {
public void loadAlbums(int artistId){
AlbumServiceAsync albumService = this.getAlbumService();
AsyncCallback callback = new AsyncCallback(){
public void onFailure(Throwable caught) {
// TODO Auto-generated method stub
}
public void onSuccess(Object result) {
Album[] albums = (Album[]) result;
for (int i=0;i<albums.length;i++){
addAlbumToModel(albums[i]);
}
refreshXformsModel();
}
};
albumService.getAlbumsForArtist(artistId, callback);
}
private AlbumServiceAsync getAlbumService(){
AlbumServiceAsync albumService = (AlbumServiceAsync)
GWT.create(AlbumService.class);
ServiceDefTarget endpoint = (ServiceDefTarget) albumService;
String moduleRelativeUrl = GWT.getModuleBaseURL() + "albumService";
endpoint.setServiceEntryPoint(moduleRelativeUrl);
return albumService;
}
private native void addAlbumToModel(Album album)/*-{
var model = $doc.getElementById("albums");
var instance = model.getInstanceDocument("albumData");
var dataElement = instance.getElementsByTagName("Data")[0];
// create the new album node
var newAlbumElement = instance.createElement("Album");
var titleElement = instance.createElment("Title");
titleElement.appendChild(instance.createTextNode(album.getTitle()));
newAlbumElement.appendChild(titleElement);
var yearElement = instance.createElement("Year");
yearElement.appendChild(instance.createTextNode(album.getYear()));
newAlbumElement.appendChild(yearElement);
dataElement.appendChild(newAlbumElement);
}-*/;
private native void refreshXformsModel()/*-{
var model = $doc.getElementById("albums");
model.rebuild();
model.recalculate();
model.refresh();
}-*/;
}
|
这个 Java 类做了很多工作。首先,它提供了两个 Java 方法远程调用前面建立的 AlbumService。这和 第 2 部分 中调用 ArtistService 很相似。代码中包含服务成功响应异步请求时调用的回调函数。这里,回调函数用到了另外两个方法,addAlbumToModel() 和 refreshXformsModel()。
这两个方法都是 JavaScript 原生方法,和第 1 部分中看到的例子类似。
addAlbumToModel() 方法访问表示 XForms 模型的 JavaScript 对象。从而能够访问 XML 实例数据。您已经看到,第 1 部分 是使用常见的 JavaScript 格式 document.getElementById(...) 实现的。要注意的是,由于是 GWT,必须使用 $doc 来代替 JavaScript 隐含的 document 对象。得到文档引用之后,就可以用熟悉的 DOM 方法向 XML 对象中添加新的唱片了。要反复调用 addAlbumToModel 方法,把服务器返回的唱片全部加进去。遍历服务器响应之后,就用到 refreshXformsModel() 方法了。这也是一个原生方法。它再一次获得 XForms 模型的句柄,然后利用该对象的 API 刷新绑定到模型的控件。
最好,还需要保证在页面加载的时候调用 GWT 方法。为此需要修改原来的 Albums.jsp,如清单 6 所示。
清单 6. 从 JSP 调用 GWT JavaScript
<?xml version="1.0" encoding="UTF-8"?>
<xhtml:html xmlns:ev="http://www.w3.org/2001/xml-events"
xmlns:xforms="http://www.w3.org/2002/xforms"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xhtml:head>
<xhtml:title>Albums</xhtml:title>
<xhtml:script language="text/javascript"
src="org.developerworks.rockstar.RockStarMain.nocache.js">
</xhtml:script>
<xforms:model id="albums" xmlns="http://www.w3.org/1999/xhtml"
xmlns:xforms="http://www.w3.org/2002/xforms">
<xforms:instance id="albumData" xmlns=""/>
</xforms:model>
</xhtml:head>
<xhtml:body onload="loadAlbums(<%=request.getParameter("artistid") %>)">
<xhtml:div id="albumList">
<xforms:repeat id="repeatItem"
nodeset="instance('albumData')/Data/Album"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xforms="http://www.w3.org/2002/xforms">
<xhtml:div>
<xforms:output ref="Title" xmlns="http://ww.w3.org/1999/xhtml"
xmlns:xforms="http://www.w3.org/2002/xforms">
<xforms:label
xmlns:xforms="http://www.w3.org/2002/
xforms">Title:</xforms:label>
</xforms:output>
<xforms:output ref="Year" xmlns="http://www.w3.org/1999/xhtml"
xmlns:xforms="http://www.w3.org/2002/xforms">
<xforms:label
xmlns:xforms="http://www.w3.org/2002/
xforms">Year:</xforms:label>
</xforms:output>
</xhtml:div>
</xforms:repeat>
</xhtml:div>
</xhtml:body>
</xhtml:html>
|
可以看到,仅仅在页面体加载完成之后才调用 GWT 方法。此外,原来的脚本就不再需要了。但页面体声明中仍然有一个非常小的脚本。它仅仅用来传入 artistId 请求参数。现在的大部分代码用于创建 UI 控件。我们看看如何也用 GWT 通过编程来创建控件。
前面介绍了如何使用 GWT 和 JSNI 访问 XForms 模型和实例数据。我们再前进一步,看看如何使用 GWT 和 JSNI 动态创建 XForms 控件。首先用 JavaScript 代码替换重复出现的控件,如清单 7 所示。
清单 7. 用 GWT JSNI 动态创建 XForms 控件
private native void createControls()/*-{
var xfNs = "http://www.w3.org/2002/xforms";
// get the container div
var container = $doc.getElementById("albumList");
var repeater = $doc.createElementNS(xfNs,"xforms:repeat");
repeater.setAttribute("id", "repeatItem");
repeater.setAttribute("nodeset", "instance('albumData')/Data/Album");
var titleOut = $doc.createElementNS(xfNs, "xforms:output");
titleOut.setAttribute("ref", "Title");
var titleLabel = $doc.createElementNS(xfNs, "xforms:label");
titleLabel.appendChild($doc.createTextNode("Title:"));
titleOut.appendChild(titleLabel);
repeater.appendChild(titleOut);
var yearOut = $doc.createElementNS(xfNs, "xforms:output");
yearOut.setAttribute("ref", "Year");
var yearLabel = $doc.createElementNS(xfNs, "xforms:label");
yearLabel.appendChild($doc.createTextNode("Year:"));
yearOut.appendChild(yearLabel);
repeater.appendChild(yearOut);
container.appendChild(repeater);
}-*/;
|
同样也是使用原生的 JavaScript 通过简单的 DOM 编程来创建 XForms 控件。完成上述代码后,剩下的就是将其添加到
loadAlbums() 方法中,加载页面的时候调用该方法。JSNI 代码将创建控件,然后调用远程服务检索唱片信息了。然后使用唱片数据动态创建 XForms 模型实例数据。再刷新 XForms 模型以便绑定的控件显示新的数据。JSP 非常简单,如清单 8 所示。
清单 8. 简化后的 JSP —— 没有了 XForms 控件
<xhtml:body onload="loadAlbums(<%=request.getParameter("artistid") %>)">
<xhtml:div id="albumList">
</xhtml:div>
</xhtml:body>
|
转瞬之间,XForms JSP 看起来就像是显示歌手名单的 GWT HTML 页面了。所有的 UI 都使用 Java 类编译成的 JavaScript 动态创建。数据使用 GWT Ajax 检索。
本文中,我们把一个大量使用 JSP 脚本装载数据的 XForms 页面变成了主要依靠 GWT 的小页面。利用 GWT 的 Ajax 机制从服务器异步加载数据。然后使用 GWT 的 JSNI 工具动态创建 XForms 模型中的实例数据。最后,沿着 JSNI 的路线更进一步,利用它动态创建显示唱片数据所需要的全部 XForms 控件。第 4 部分将介绍如何使用 XForms 控件按需调用 GWT Ajax。
| 描述 | 名字 | 大小 | 下载方法 |
|---|---|---|---|
| 第 3 部分样例代码 | rockstar3_src.zip | 14KB | HTTP |
学习
- 您可以参阅本文在 developerWorks 全球网站上的 英文原文。
- 一定要阅读 本系列文章
第 1 部分,介绍 GWT 的 JavaScript Native Interface,即 将 XForms 与 Google Web Toolkit 相结合。
-
关于 XForms 的入门教程,请参阅分为三部分的系列文章 XForms 简介(Chris Herbroth,developerWorks,2006 年 12 月)。
-
了解如何使用 JavaScript 和 XForms,请参阅 XForms 技巧: 从 XForms 表单调用 JavaScript(Nicholas Chase,developerWorks,2007 年 10 月)。
-
使用 JavaScript 让 XForms 变得更健壮(Michael Galpin,developerWorks,2007 年 8 月)介绍了如何用 JavaScript 增强 XForms 的功能。
-
使用 XForms 和 Ajax 创建自动建议表单字段(Michael Galpin,developerWorks,2007 年 9 月)介绍了如何用 XForms 和 Ajax 实现自动提示功能。
-
深入剖析 GWT 的第一篇教程 面向 Java 开发人员的 Ajax: 探索 Google Web Toolkit(Philip McCarthy,developerWorks,2006 年 7 月)。
-
深入了解如何用 GWT 创建 Web 应用程序,请阅读分为四部分的 developerWorks 系列教程 使用 Google Web Toolkit、Apache Derby 和 Eclipse 构建 Ajax 应用程序,第 1 部分: 梦幻前端(Noel Rappin,developerWorks,2007 年 2 月)。
-
分两部分的系列教程 使用 Google Web Toolkit 和 Apache Geronimo 构建启用 Ajax 的应用程序,第 1 部分:在 Geronimo 上运行经过编译的 Google Web Toolkit 应用程序(Michael Galpin,developerWorks,2007 年 8 月)介绍了 GWT 的 Ajax 功能。
-
请访问 IBM developerWorks Ajax 技术资源中心。
-
访问 IBM developerworks Java 技术专区。
-
访问 W3C 的 XForms 主页。
-
IBM XML 认证:了解如何才能成为一名 IBM 认证的 XML 及相关技术的开发人员。
-
XML 技术文档库:developerWorks XML 专区提供了大量技术文章和技巧、教程、标准以及 IBM 红皮书。
-
developerWorks 技术事件和网络广播:随时关注技术的最新发展。
-
通过 IBM developerWorks XML 专区 进一步学习 XForms。
-
Podcasts:与 IBM 技术专家对话。
获得产品和技术
-
关于 GWT 的最佳信息来源是官方的 Google Web Toolkit 网站。
-
下载 Mozilla、Firefox 或 Seamonkey 的 XForms 扩展。
讨论