この第 3 回では、第 2 回で作成したロック・スターのアプリケーションをリファクタリングを行います。このアプリケーションでは、レコード会社の幹部がアーティストとアーティストのアルバムを管理することができます。今回は第 2 回の機能は何も変更せず、GWT と XForms を混在させることで、その機能の実装を変更します。ここでは、GWT を既存の任意の Web ページに容易に導入できることを説明します。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 は Model-View-Control のパラダイムを頻繁に使うため、このパラダイムに慣れておくと役に立ちます。これまでに XForms と GWT を経験したことがあると、もちろん助けになりますが、必須ではありません。
これまでは、同じページにインライン化されたモデルのインスタンス・データを XForms がどう描画するかを見てきました。このモデル・データは動的でした。JSP スクリプトレットを使ってデータを照会してフィルタリングし、XML をページに書き込みました。今度は、この XForms のページをもっと動的なものにします。ページにインスタンス・データを書き込むのではなく、インスタンス・データをサーバーから非同期で取得し、それをページ上のモデルに動的に追加します。そして Ajax コールには GWT を使い、XForms モデルを変更するためには GWT の JSNI を使います。
ここでは、XForms によるアルバム・ページに対して GWT を使います。すると、既存の Web ページにどうやって GWT を追加するかという疑問が湧いてきます。これは非常に単純です。必要なことは、(GWT の) Java から JavaScript へのコンパイラーによって、コードから生成される JavaScript ライブラリーを含めることだけです。ライブラリーはモジュールごとに 1 つのファイルから成り、XForms のページに含めるには、リスト 1 に示すコード行を追加するだけです。
リスト 1. GWT をアルバムのページに追加する
<xhtml:script language="text/javascript"
src="org.developerworks.rockstar.RockStarMain.nocache.js"></xhtml:script>
|
開発者の人たちは、ページに GWT を追加することがあまりにも容易なことに、よく驚かされます。これは、何といっても設計のおかげです。GWT に見られる例の大部分は「既成のものに頼らない」アプリケーション、つまり GWT を使ってゼロから作成されたアプリケーションです。実際には、レガシー・アプリケーションも最低限のものは使われており、GWT はどちらのアプリケーションにも便利なように設計されています。GWT のコードが、どれも単なる JavaScript であることを思い出してください。そう考えれば、これほど簡単であることも納得できます。これでページの中に GWT を組み込めたので、このページ用の GWT のコード (つまり JavaScript にコンパイルされる Java コード) を書き始めることができます。
Ajax を使ってアルバム・データをロードする: XForms データを操作するための JSNI
このシリーズの第 2 回では、GWT を使ってアーティストのリストをページにロードしましたが、これを非同期で行いました。言い換えると、ページを作成し、アーティストのリストに対して Ajax リクエストを行いました。今度は同じ方法を、アルバムに対して使います。そのため、GWT の Ajax を使ってアルバムをロードするためのサービスが必要になります。
まず、サービスのインターフェースを宣言します (リスト 2)。
リスト 2. アルバム・サービスのインターフェース
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. アルバム・サービスの実装
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;
}
}
|
この場合も、物理データの管理 (ファイルシステムからの取得や XML の構文解析など) を抽象化するために DAO (Data Access Object) パターンを使いました。これによって、XML ファイルをベースとした単純な実装を、もっと標準的なデータベース駆動の実装と容易に交換することができます。これで、新しいサービスを GWT コードから使えるようになりました。
org.developerworks.rockstar.client パッケージの中にあるすべてのコードは JavaScript にコンパイルされ、リスト 1 のような JavaScript ライブラリーを含む、すべてのページで利用できるようになります。そこで、アルバム・ページを扱うための新しい 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 クラスでは多くのことが行われています。第 1 に、先ほど作成した AlbumService のリモート呼び出しを行うために使われる 2 つの Java メソッドがあります。これは、第 2 回での ArtistService の呼び出し方と非常に似ています。このコードには、サービスが非同期のリクエストに適切に応答する際に使われるコールバック関数が含まれています。この場合では、コールバックは他の 2 つのメソッド、addAlbumToModel() と refreshXformsModel() を利用しています。
この 2 つのメソッドは、第 1 回で見た例と同じように、JavaScript のネイティブ・メソッドです。
addAlbumToModel() メソッドは、XForms モデルを表現する JavaScript オブジェクトにアクセスするので、このメソッドを使えば XML のインスタンス・データにアクセスすることができます。第 1 回ではこれを、JavaScript でおなじみのパラダイム、document.getElementById(...) を使って行う方法を説明しました。ここではこれを GWT から行っているため、JavaScript で暗黙的な文書オブジェクトではなく、$doc を使っていることに注意してください。$doc を参照できれば、おなじみの DOM メソッドを使って新しい Album を 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 のメソッドを呼び出します。また、先ほど使用したスクリプトレットはありません。それでも、body 宣言の中に非常に小さなスクリプトレットがまだ残っています。このスクリプトレットは単純にリクエスト・パラメーター artistId を渡します。これで、コードの大部分は UI コントロールを作成するためのものになりました。次に、これを GWT を使ってプログラムでも行えることを説明します。
GWT の JSNI を使って XForms コントロールを作成する
ここまでは、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);
}-*/;
|
これも、簡単な DOM プログラミングを使って、ネイティブの JavaScript を使う XForms コントロールを作成しているにすぎません。このコードが用意できれば、あとは本体がロードされたときに呼び出される loadAlbums() メソッドに、このコードを追加すればよいだけです。コードを追加すると JNSI コードはコントロールを作成し、アルバム・データを取得するリモート・サービスを呼び出します。また、アルバム・データを使ったプログラムにより XForms のモデル・インスタンス・データが作成されます。すると、この XForms モデルは更新され、バインドされたコントロールが新しいデータを表示します。JSP のボディーは、今や非常に単純になりました (リスト 8)。
リスト 8. XForms コントロールを持たない、単純化された JSP
<xhtml:body onload="loadAlbums(<%=request.getParameter("artistid") %>)">
<xhtml:div id="albumList">
</xhtml:div>
</xhtml:body>
|
XForms の JSP は突如として、アーティストのリストを表示するための、GWT の HTML ページに非常に似たものになっています。すべての UI は、Java クラスから生成した JavaScript を使ってプログラムで作成されています。データは GWT の Ajax を使って取得されます。
第 3 回では、JSP スクリプトレットに大きく依存してデータをインライン化していた XForms のページを、GWT に大きく依存する最小限のページに変換しました。GWT の Ajax 機構を使って、サーバーから非同期にデータをロードしました。次に GWT の JSNI 機能を使って、XForms のモデルの中にインスタンス・データを動的に作成しました。そして最後に、さらに JSNI を活用し、アルバム・データを表示するために使用するすべての XForms コントロールを、JSNI を使って動的に作成しました。第 4 回では、XForms コントロールを使って GWT の Ajax をオンデマンドで呼び出す方法について説明する予定です。
| 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|---|---|---|
| Part 3 sample code | rockstar3_src.zip | 14KB | HTTP |
学ぶために
- XForms への優れた入門記事として、3 回シリーズの「XForms 入門」(Chris Herbroth 著、developerWorks、2006年9月) を読んでください。
- JavaScript と XForms とを組み合わせて使う方法を学ぶために、「XForms のヒント: XForms フォームから JavaScript をコールする」(Nicholas Chase 著、developerWorks、2007年1月) を読んでください。
- JavaScript が XForms の機能をどのように改善できるかを学ぶために、「Use JavaScript to make your XForms more robust」(Michael Galpin 著、developerWorks、2007年7月) を読んでください。
- XForms と Ajax を組み合わせて動作させ、autosuggest 機能を作成する方法を学ぶために、「XForms と Ajax を使って autosuggest フォーム・フィールドを作成する」(Michael Galpin 著、developerWorks、2007年7月) を読んでください。
- GWT を詳細に解説した最初のチュートリアルの 1 つとして、「Java開発者のためのAjax: Google Web Toolkitを探る」(Philip McCarthy 著、developerWorks、2006年6月) を読んでください。
- GWT を使った Web アプリケーションの構築を学ぶために、4 回シリーズの developerWorks のチュートリアル「Google Web Toolkit、Apache Derby、Eclipse を使用して Ajax アプリケーションをビルドする」(Noel Rappin 著、developerWorks、2006年12月) を読んでください。
- GWT の Ajax 機能に慣れるために、2 回シリーズのチュートリアル「Build an Ajax-enabled application using the Google Web Toolkit and Apache Geronimo」(Michael Galpin 著、developerWorks、2007年5月) を読んでください。
- IBM developerWorks の Ajax Resource Center をご覧ください。
-
W3C の XForms のホーム・ページを訪れてください。
-
developerWorks technical events and webcasts で最新情報を入手してください。
-
Podcasts を利用して IBM 技術のエキスパートから最新情報を入手してください。
製品や技術を入手するために
- GWT の情報を入手するためには、そのソースである Google Web Toolkit site の正式サイトに直接行くのが一番です。
-
Mozilla や Firefox、あるいは Seamonkey 用の XForms 拡張を入手してください。
議論するために