将 XForms 与 Google Web Toolkit 相结合,第 2 部分: 创建艺术家专辑管理表单

繁简对译:[]  字体设置:[] 2008-11-29  作者:Michael Galpin  来源:IBM Developerworks  阅读  次

这个分为四部分的 系列文章第 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<rowCount;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<artists.length;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. 艺术家页面

<html>
     <head>
          <title>RockStars</title>

          <!--                                           -->
          <!-- This script loads your compiled module.   -->
          <!-- If you add any GWT meta tags, they must   -->
          <!-- be added before this line.                -->
          <!--                                           -->
          <script language='javascript' src='org.developerworks.
		  rockstar.RockStarMain.nocache.js'></script>
          <style type="text/css">
               .tableHeader{
                    background-color:#AAAAAA;
               }
          </style>
     </head>
     <body>
     </body>
</html>

 

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<Artist>List<Artist>,但是不要这样做。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<Artist> 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. 测试数据

<?xml version="1.0" encoding="UTF-8"?>
<Data>
     <Artist>
          <Id>0</Id>
          <Name>The Struts Five</Name>
          <Genre>Classic Rock</Genre>
     </Artist>
     <Artist>
          <Id>1</Id>
          <Name>Spring Flow</Name>
          <Genre>Techno</Genre>
     </Artist>
     <Artist>
          <Id>2</Id>
          <Name>The Holy Grails</Name>
          <Genre>Funk</Genre>
     </Artist>
     <Artist>
          <Id>3</Id>
          <Name>The Rails Way</Name>
          <Genre>Pop</Genre>
     </Artist>
     <Artist>
          <Id>4</Id>
          <Name>Cake Clone</Name>
          <Genre>Pop</Genre>
     </Artist>
     <Artist>
          <Id>5</Id>
          <Name>Obscure Tapestry</Name>
          <Genre>Techno</Genre>
     </Artist>
     <Artist>
          <Id>6</Id>
          <Name>Dojo Darling</Name>
          <Genre>Classic Rock</Genre>
     </Artist>
     <Artist>
          <Id>7</Id>
          <Name>Cairingorm</Name>
          <Genre>Progressive</Genre>
     </Artist>
     <Artist>
          <Id>8</Id>
          <Name>ProtoStripes</Name>
          <Genre>Thrash</Genre>
     </Artist>
</Data>

 

现在您已拥有测试数据,可以开始启动应用程序。应该出现图 1 所示的界面。


图 1. 以托管模式查看艺术家
viewing artists in hosted mode

这是测试数据中的艺术家列表。现在您只需一个简单的表单输入新艺术家。

 



回页首

 

添加新艺术家:使用 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<rowCount;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<artists.length;i++){
           //this.artistTable.setText(i+1, 0, artists[i].getName());
           String html = "<a href=\"Albums.jsp?artistId=
		     "+artists[i].getId()+"\">"+artists[i].getName()+"</a>";
           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 所示。


清单 11. 使用服务器逻辑内联数据

<?xml version="1.0" encoding="UTF-8"?>
<xhtml:html xmlns:xforms="http://www.w3.org/2002/xforms" 
xmlns:xhtml="http://www.w3.org/1999/xhtml">
<%@page import="org.developerworks.rockstar.client.*" %>
<%@page import="org.developerworks.rockstar.server.*" %>
<%@page import="java.util.List" %>
    <xhtml:head>
        <xhtml:title>Albums</xhtml:title>
        <xforms:model id="albums">
            <xforms:instance id="albumData" xmlns="">
                 <Data>
                 <%
                      int artistId = Integer.parseInt(request.getParameter("artistId"));
                      AlbumDao dao = new AlbumFileDao();
                      List<Album> albums = dao.getAllAlbums();
                      for (Album album : albums){
                           if (album.getArtistId() == artistId){
                 %>
                      <Album>
                           <Title><%= album.getTitle() %></Title>
                           <Year><%= album.getYear() %></Year>
                      </Album>
                 <% 
                           }
                      }
                 %>
                 </Data>
            </xforms:instance>
        </xforms:model>
</xhtml:head>
    <xhtml:body>
         <xhtml:div id="albumList">
              <xforms:repeat id="repeatItem" nodeset="/Data/Album">
                   <xhtml:div>
                        <xforms:output ref="Title">
                             <xforms:label>Title:</xforms:label>
                        </xforms:output>
                        <xforms:output ref="Year">
                             <xforms:label>Year:</xforms:label>
                        </xforms:output>
                   </xhtml:div>
              </xforms:repeat>
         </xhtml:div>
    </xhtml:body>
</xhtml:html>

 

Java scriptlet 处理逻辑以查明要显示的专辑。同样,您是从 XML 文件加载数据。过滤逻辑包含在一个外部类中,该类可作为本文 源代码 的一部分下载。同样注意,要在 XML 文件中创建一些测试数据。测试数据也随附在文章的源代码中。

 



回页首

 

以 Web 模式测试 XForms 页面

现在,您已准备好测试 XForms 专辑页面。截止目前,您已使用 GWT 的托管模式测试了 GWT 艺术家页面。您可以再次以托管模式启动,但是您需要再次切换到 GWT 的 Web 模式以测试新页面。为什么要这样做呢?XForms 所需的浏览器插件在 GWT 的托管模式下不可用。图 4 展示了如何切换到 Web 模式。


图 4. 切换到 Web 模式
切换到 Web 模式

单击 Compile/Browse 将应用程序切换到 Web 模式,如图 5 所示。


图 5. Web 模式下的应用程序
Web 模式下的应用程序

您现在应该会看见应用程序在默认的 Web 浏览器中运行。现在,您可以单击某一个艺术家,然后会看见图 6 所示的界面。


图 6. 测试 Artists 页面
测试 Artists 页面

页面显示了艺术家的一列专辑。它使用了一些简单的 XForms 控件从模型的实例数据中迭代 XML 数据。在第 3 部分中,您将看见如何为数据条目添加 XForms 控件,以及如何将其连接到 GWT Ajax 调用以允许唱片公司的工作人员在此页面上添加新专辑。

 



回页首

 

结束语

本文作为摇滚巨星系列的第 2 部分,介绍了如何使用 GWT 构建交互式页面并将其链接到使用 XForms 的页面。在第 3 部分中,您将重构本文所建立的摇滚巨星应用程序。您不会实际更改本文所创建的应用程序的功能,但是您将了解如何使用页面上的 GWT 元素作为 XForms 元素使应用程序更具交互性。

 

打印 收藏 关闭