IBM®
本文へジャンプ
    Japan [変更]    ご利用条件
 
 
検索範囲検索:    
    ホーム    製品    サービス & ソリューション    サポート & ダウンロード    マイアカウント    
skip to main content

developerWorks Japan  >  Web development | Open source  >

Grails によるリッチ・インターネット・アプリケーション: 第 2 回 Grails と Google Web Toolkit

developerWorks
ページオプション

JavaScript を要するドキュメントオプションは表示されません

ダウンロード

原文はこちら

原文はこちら


レベル: 中級

Michael Galpin, Software architect, eBay

2009年 3月 10日

2 回連載の第 2 回では、第 1 回で作成した Grails 駆動の Web サービスを拡張していきます。そのために新たに検索ページを作成しますが、今回アプリケーションの作成に使用するのは、GWT (Google Web Toolkit) です。さらに、Ext GWT ライブラリーの一層リッチな UI ウィジェットも追加します。

この連載について

この連載では、Grails フレームワークで実装したバックエンドにおいてサービス指向アーキテクチャー (SOA) を使用するアプリケーション・アーキテクチャーを取り上げ、Grails によって、一般的な Web アプリケーションおよび特定の Web サービスを作成する過程がいかに簡易化されるかを探っていきます。Grails で実装したバックエンドは、純粋なあらゆるクライアント・サイド・アプリケーションに簡単に接続することができます。第 1 回では Adobe Flex を使用して、Flash Player を利用するアプリケーションを作成しました。今回の記事では、GWT (Google Web Toolkit) を使用して、純粋な JavaScript でフロントエンドを作成します。

前提条件

この記事では、Grails と GWT を使用して Web アプリケーションを構築します。Grails フレームワークがベースとしているのは、Java™ プラットフォーム用の動的言語である Groovy プログラミング言語です。この記事を読む上では、Groovy に関する十分な知識があれば理想的ですが、絶対的に必要というわけではありません。代わりに Java プログラミングに関する知識、あるいは Ruby や Python などの他の動的言語の知識があれば十分です。この記事では、Grails 1.0.3 を使用しました (「参考文献」を参照)。Grails はいくつものデータベースやアプリケーション・サーバーと連動することができますが、この記事ではデータベースもアプリケーション・サーバーも用意する必要はありません。Grails にはその両方が備わっているからです。フロントエンドの作成には GWT を使用するので、Java を使い慣れていることが不可欠の条件となります。この記事では、Google Web Toolkit 1.5.3 を使用しました (「参考文献」を参照)。




上に戻る


新規サービス

第 1 回では、Grails で構築した SOA (Service Oriented Architecture) を使用したアプリケーションを作成しました。このアプリケーションのバックエンドには SOA が使用されているため、このバックエンドをベースに新たなアプリケーションを構築することができます。これは、SOA によるバックエンドを純粋なクライアント・サイドのフロントエンドと併せて使用することの大きな利点であり、またこうすることで、サーバー・サイドのコードがユーザー・インターフェースから完全に切り離されます。ただし、今回の記事でも第 1 回に続いて Grails を使うと容易に Web サービスを作成できることを実証するため、既存のバックエンドを変更して検索 API を追加します。

検索 API サービス

既存のアプリケーションには、検索ビジネス・サービスがあります。前回はアプリケーションのすべての記事をリストアップするために、list API を使用しましたが、このサービスにはこれ以外の機能もあります。リスト 1 を見てください。


リスト 1. 検索ビジネス・サービス

class SearchService {

    boolean transactional = false

    def list() {
        Story.list()
    }

    def listCategory(catName){
        Story.findAllWhere(category:catName)
    }
    
    def searchTag(tag){
        Story.findAllByTagsIlike("%"+tag+"%")
    }
}

listCategory メソッドは、ある特定のカテゴリーに含まれるすべての記事を検索します。また searchTag メソッドは、特定のタグが設定されたすべての記事を検索します。検索は完全一致である必要はなく、大小文字を区別しません。前述のとおり、これまでのところは list API しか使用していません。そこで、今回は他の 2 つのメソッドを使用する Web サービスを新しく作成することにします。必要な作業は、新しいメソッドを ApiController に追加するだけのことです (リスト 2 を参照)。


リスト 2. 新規 ApiController

import grails.converters.*

class ApiController {
    // injected services
    def searchService
    def storyService

    def search = {
        def results= null
        def tagResults = null
        if (params.tag){
            tagResults = searchService.searchTag(params.tag)
        }
        def catResults = null
        if (params.category){
            catResults = searchService.listCategory(params.category)
        }
        if (params.tag && params.category){
            def tagMap = [:]
            tagResults.each{ story ->
                tagMap[story.id] = story
            }
            results = catResults.findAll { tagMap[it.id] != null} 
        } else {
            if (params.category){
                results = catResults
            } else {
                results = tagResults
            }
         }
        render results as JSON
    }
}

リスト 2 には、新規の検索メソッドだけを記載しています。このメソッドが要求するリクエスト・パラメーターは、tagcategory の 2 つです。メソッドは、いずれか 1 つのパラメーターを扱うことも、両方のパラメーターを扱うこともできます。これらのパラメーターは、Grails アプリケーションでのすべてのリクエスト・パラメーターと同じく、params オブジェクトによって公開されます。tag パラメーターが存在する場合、検索サービスでは searchTag メソッドが呼び出されます。category パラメーターが存在する場合には、検索サービスで listCategory メソッドが呼び出されます。事態が面白くなってくるのは、両方のパラメーターが存在する場合 (params.tag および params.category の場合) です。

検索結果として表示したいのは、2 つのメソッド呼び出しによって検索サービスに返された両方の記事リストに共通する記事だけです。そこでまずは、[:] 表記を使って空のマップを作成します。これは、Java による HashMap です。次に、特定のタグが設定された記事のリストを繰り返し処理し、記事の ID をキーとして使用して、それぞれの記事をマップに取り込みます。クロージャーには標準の Groovy 構文を使用してください。続いてカテゴリー検索結果の記事のリストから、先に作成したマップ内の ID に該当するすべての記事を見つけます。その際、Groovy によってリストに追加される便利な findAll メソッドを使用してください。また、ここでも同じくクロージャーを使用しますが、この場合のクロージャーには Groovy の簡易表記 (「it」オブジェクト) を使用します。最後に、結果の如何に関わず、Grails の JSON コンバーターを使用して、リストを JSON オブジェクトに変換します。

前回の記事 (「参考文献」を参照) では、Grails ではデータを XML にするのは容易であることを示しましたが、Grails はデータを JSON にするのも同様に容易なのです。ここで、新しい Web サービスをテストしてみましょう。Grails の規約に従えば、この Web サービスの URL は http://<root>/digg/api/search になります。以下のリスト 3 に記載する出力例は、category を “technology” に設定して検索した場合です。


リスト 3. 検索の出力例

[{"id":1,"class":"Story","category":"technology","description":"How to get 
a ternary operator in Scala","link":"http://blog.tmorris.net/does-scala-have-
javas-ternary-operator/","tags":"programming scala","title":"Does Scala have 
Java's ternary operator?","votesAgainst":0,"votesFor":0},{"id":3,"class":"Story",
"category":"technology","description":"New animations available in the Flex 4 'Gumbo'
 release.","link":"http://graphics-geek.blogspot.com/2008/10/flex-specificanimations
-posted.html","tags":"flash flex","title":"Flex Specific Animations Posted",
"votesAgainst":0,"votesFor":0}]

上記の出力は標準の JSON で、まさに期待通りの結果です。取得したオブジェクトの配列では、それぞれの JSON オブジェクトが記事を表します。注目に値する興味深い点は、Grails シリアライザーがそのオブジェクトの Groovy のクラスを class 属性として追加していることです。このアプリケーションではリストの内容が同じ種類なのでこの属性は必要ありませんが、このメタデータが極めて役立つシナリオは想像できるはずです。Web サービスの変更はこれで完了したので、次は、このサービスを利用する新規アプリケーションを GWT で作成します。




上に戻る


GWT を使用した検索アプリケーション

Digg 流のアプリケーションから記事を検索する新しいアプリケーションを作成する準備が整いました。Flex ベースのアプリケーションと同じく、このアプリケーションは Grails 駆動のサービスを呼び出す、完全なクライアント・サイドのアプリケーションとなります。ただし今回使用するのは GWT です。GWT を使用することで、アプリケーションのすべてを Java でプログラミングできる一方、GWT が Java で作成されたアプリケーションを極めて効率的な JavaScript にコンパイルしてクライアント上で実行できるようにします。それではまず、GWT 駆動のアプリケーションへのエントリー・ポイントから検討していきましょう。

検索エントリー・ポイント

GWT アプリケーションへのエントリー・ポイントは Java クラスで、このクラスのコンパイル後の JavaScript が特定のページのスクリプトとなります。エントリー・ポイントの役割は、ユーザー・インターフェースを作成すること、そして対話処理を行うことです。この例で作成する必要があるのは、ユーザーが検索クエリーを入力して指定することができるフォームと、これらのクエリーの結果を表示するパネルです。そのためのコードをリスト 4 に記載します。


リスト 4. DiggApp クラス

public class DiggApp implements EntryPoint {

    private static final String[][] CATEGORIES = {
        { "Technology", "technology" }, { "World & Business", "business" },
        { "Science", "science" }, { "Gaming", "games" },
        { "Lifestyle", "lifestyle" }, { "Entertainment", "entertainment" },
        { "Sports", "sports" }, { "Offbeat", "miscellaneous" } };

    private final HorizontalPanel searchPanel = createSearchForm();
    private final VerticalPanel resultsPanel = new VerticalPanel();

    public void onModuleLoad() {
        RootPanel.get().add(searchPanel);
        RootPanel.get().add(resultsPanel);
    }
    private HorizontalPanel createSearchForm() {
        HorizontalPanel panel = new HorizontalPanel();
        panel.setTitle("Search for Stories");
        Label tagLabel = new Label("Tag:");
        final TextBox tagBox = new TextBox();
        panel.add(tagLabel);
        panel.add(tagBox);
        Label catLabel = new Label("Category:");
        final ListBox catBox = new ListBox();
        catBox.addItem("");
        for (String[] category : CATEGORIES){
            catBox.addItem(category[0],category[1]);
        }
        panel.add(catLabel);
        panel.add(catBox);
        Button searchBtn = new Button("Search");
        searchBtn.addClickListener(new ClickListener(){
            public void onClick(Widget sender) {
                search(tagBox.getText(), catBox.getValue(catBox.getSelectedIndex()));
            }
        });
        panel.add(searchBtn);
        return panel;
    }
}

onModuleLoad メソッドは、ページがロードされるときに呼び出されます。上記では、このメソッドの呼び出しによって、タグを入力するためのテキスト・ボックス、カテゴリーのドロップダウン・リスト、そして検索を要求するための検索ボタンを備えた検索フォームが作成されます。また、検索結果を表示するための空のパネルも作成されます。この検索ボタンがクリックされると、検索メソッドが呼び出されます。検索メソッドのコードは、リスト 5 のとおりです。


リスト 5. DiggApp 検索メソッド

private final void search(String tag, String category){
    resultsPanel.clear();
    Story.search(tag, category, new RequestCallback(){
        public void onError(Request request, Throwable exception) {
            Label label = new Label("Sorry there was an error");
            resultsPanel.add(label);
        }
        public void onResponseReceived(Request request, Response response) {
            List<Story> stories = Story.fromJson(response.getText());
            SearchTable grid = new SearchTable(stories);
            resultsPanel.add(grid);
        }
    });
}    

このコードでは、検索メソッドを Story クラスで使用します。Story クラスは、連載第 1 回の Flex アプリケーションで作成した Story クラスと同様に、システム内でモデルの役割を果たします。このクラスには静的検索メソッドがあり、データ・モデルとして機能します。リスト 6 に、このクラスを記載します。


リスト 6. Story クラス

public class Story {

    private static final String BASE_URL = "/digg/api/search?";
    
    private int id;
    private String link;
    private String title;
    private String description;
    private String tags;
    private String category;
    private int votesFor;
    private int votesAgainst;

    public Story(JSONObject obj){
        id = (int) obj.get("id").isNumber().doubleValue();
        link = obj.get("link").isString().stringValue();
        title = obj.get("title").isString().stringValue();
        description = obj.get("description").isString().stringValue();
        tags = obj.get("tags").isString().stringValue();
        category = obj.get("category").isString().stringValue();
        votesFor = (int) obj.get("votesFor").isNumber().doubleValue();
        votesAgainst = (int) obj.get("votesAgainst").isNumber().doubleValue();
    }
    
    public static void search(final String tag, final String category, final 
RequestCallback callback){
        String url = buildRequest(tag, category);
        RequestBuilder builder = 
            new RequestBuilder(RequestBuilder.GET, url);
        try {
            builder.sendRequest(null, new RequestCallback(){
                public void onError(Request request, Throwable exception) {
                    callback.onError(request, exception);
                }
                public void onResponseReceived(Request request, Response response) {
                    callback.onResponseReceived(request, response);
                }                
            });
        } catch (RequestException e) {
            callback.onError(null, e);
        }
    }
}

上記は、クラスの一部を記載したもので、ヘルパー・メソッドやゲッター、セッターは省略されていることに注意してください。この検索メソッドは tag、category、そして RequestCallback 型のオブジェクトも引数に取り、メソッド内では GWT の RequestBuilder クラスを使用して HTTP リクエストを作成し、このリクエストを Web サービスに送信します。RequestCallback は、非同期 HTTP リクエストを送信するために必要な GWT のインターフェースです。Story.search メソッドでは、渡された RequestCallback が委譲されるに過ぎません。この例の場合に実行するのは、リスト 5 で作成した RequestCallback です。注意する点として、リスト 5 とリスト 6 の両方では RequestCallback インターフェースを実装する匿名内部クラスを作成しました。この方法は、GWT では (および Java UI プログラミングでも概して) 一般的な手法となっていますが、それは匿名内部クラスから、エンクロージング・クラスのフィールドとメソッドにアクセスできるためです。リスト 5 をもう一度見てみると、レスポンス・テキストを取得した後、Story.fromJson メソッドによってテキストを Story オブジェクトのリストに構文解析していることがわかるはずです。リスト 7 に、このメソッドを記載します。


リスト 7. Story.fromJson メソッド

public static List<Story> fromJson(String jsonText){
    JSONValue val = JSONParser.parse(jsonText);
    JSONArray array = val.isArray();
    List<Story> stories = new ArrayList<Story>(array.size());
    for (int i=0;i<array.size();i++){
        Story story = new Story(array.get(i).isObject());
        stories.add(story);
    }
    return stories;
}

このメソッドが使用するのは、GWT の JSONParser クラスです。JSON オブジェクトの配列を作成し、各オブジェクトを Story クラス (リスト 6 に記載) のコンストラクターに渡します。リスト 5 をもう一度見てください。ここでは、記事オブジェクトのリストを取得すると、このリストを使って SearchTable オブジェクトを作成しています。このオブジェクトはカスタム・ウィジェットで、リスト 8 に記載するとおりです。


リスト 8. SearchTable ウィジェット

public class SearchTable extends FlexTable {
    private List<Story> stories;
    public SearchTable(List<Story> stories) {
        super();
        this.stories = stories;
        this.buildTable();
    }
    private void buildTable(){
        this.setBorderWidth(2);
        this.setText(0, 0, "story");
        this.setText(0, 1, "category");
        this.setText(0, 2, "description");
        if (stories.size() == 0){
            showMessage("Sorry there were no results");
        } else {
            for (int i=0;i<stories.size();i++){
                Story story = stories.get(i);
                setWidget(i+1, 0, story.getTitleLink());
                setText(i+1, 1, story.getCategory());
                setText(i+1, 2, story.getDescription());
            }
        }
    }
    private void showMessage(String msg){
        setText(1,0, msg);
        getFlexCellFormatter().setColSpan(1, 0, 3);
    }    
}

このクラスはコアの GWT クラスである FlexTable を継承するクラスで、記事のリストを繰り返し処理して単純なテーブルにデータを取り込みます。記事がなければ、リスト 8 に示したような単純なメッセージを表示します。これで、アプリケーションのすべてのコードは作成できましたが、もう 1 つ必要な作業が残っています。Story クラスは GWT の HTTP ライブラリーと JSON ライブラリーをそれぞれ使用して、リクエストを Web サービスに送信し、サービスからのレスポンスを構文解析します。そのため、これらのライブラリーをコンパイラーが使用できるようにアプリケーションのモジュール定義に追加する必要があります。その方法は、リスト 9 に示すとおりです。


リスト 9. モジュール定義

<module>

      <!-- Inherit the core Web Toolkit stuff.                        -->
      <inherits name='com.google.gwt.user.User'/>
    
      <!-- Inherit the default GWT style sheet.  You can change       -->
      <!-- the theme of your GWT application by uncommenting          -->
      <!-- any one of the following lines.                            -->
      <inherits name='com.google.gwt.user.theme.standard.Standard'/>
      <!-- <inherits name='com.google.gwt.user.theme.chrome.Chrome'/> -->
      <!-- <inherits name='com.google.gwt.user.theme.dark.Dark'/>     -->

      <!-- Other module inherits                                      -->
      <inherits name="com.google.gwt.http.HTTP" />
      <inherits name="com.google.gwt.json.JSON" />

      <!-- Specify the app entry point class.                         -->
      <entry-point class='org.developerworks.digg.client.DiggApp'/>
    
      <!-- Specify the application specific style sheet.              -->
      <stylesheet src='DiggApp.css' />
</module>

リスト 9 は、ほぼ GWT applicationCreator スクリプトによって生成されるファイルの内容そのものですが、2 行だけ追加しています。該当する部分は、「Other module inherits」コメントに続く 2 行で、2 つの GWT ライブラリー、HTTP と JSON を追加しているだけです。これで、アプリケーションを JavaScript にコンパイルしてデプロイする準備が整いました。




上に戻る


GWT デプロイメント

Java Web アプリケーションの一部としてよく見かけられる GWT コードは、WAR ファイルに組み込まれますが、この例では GWT のクライアント・サイドの部分だけを使用します。つまり、HTML、CSS、および JavaScript のみです。これによって、GWT をサーバー技術とは切り離して使用できるようになります。必要な作業は、Java を JavaScript にコンパイルし、結果を取得し、その結果を Web アプリケーションに結合することだけです。

コンパイラーのスクリプトは、GWT アプリケーションを (GWT の applicationCreator スクリプトを使用して) 作成したときに自動的に作成されています。このスクリプトは実行可能ファイルなので、そのまま呼び出すことができます (リスト 10 を参照)。


リスト 10. GWT アプリケーションのコンパイル

$ ./DiggApp-compile
Compiling module org.developerworks.digg.DiggApp
2008-11-08 19:56:14.962 java[1300:c1b] 
      [Java CocoaComponent compatibility mode]: Enabled
2008-11-08 19:56:14.963 java[1300:c1b] [Java CocoaComponent compatibility mode]: 
      Setting timeout for SWT to 0.100000
Compilation succeeded
Linking compilation into ./www/org.developerworks.digg.DiggApp

後は、このフォルダー (org.developerworks.digg.DiggApp という名前のフォルダー) を Web アプリケーションにコピーするだけです。GWT は JavaScrip として実行されるので、GWT アプリケーションがリモート・サーバーに対して行うすべての呼び出しには、同一生成元ポリシーの制約が適用されます。そこで最も簡単な解決策となるのは、GWT アプリケーションを Grails アプリケーションの一部としてデプロイすることです。それには、ただ単純に GWT アプリケーションを Grails アプリケーションの web-app フォルダーにコピーするだけで済みます (図 1 を参照)。


図 1. Grails にデプロイされた GWT アプリケーション

新しい検索アプリケーションを実行するため、このアプリケーションをブラウザーで開いてください。URL は、Grails の規約に従って http://<root>/digg/org.developerworks.digg.DiggApp/DiggApp.html となります。アプリケーションを実行すると、図 2 のような表示になるはずです。


図 2. 検索アプリケーション

図 2 に示すアプリケーションには検索フォームしか表示されていません。Tag または Category フィールド (あるいは両方) に入力して検索 (Search) を実行してください。すると、図 3 のような結果が表示されます。


図 3. 検索アプリケーションによる検索結果の表示

アプリケーションは無事、完成しています!このアプリケーションは GWT のコア・コンポーネントを使った、かなり典型的な GWT アプリケーションです。これらのコンポーネントが標準 HTML を薄いラッパーで覆うことにより、アプリケーションは高速で軽量になりますが、多くの JavaScript ベースのウィジェット・キット (あるいは Flex に付属のもの) と比べると、多少見劣りがします。幸いこれを改善するための選択肢はいくつかあります。その 1 つが、Ext GWT です。




上に戻る


Ext GWT ウィジェットの使用

Ext JS ライブラリーは、アプリケーションを作成する際に非常によく使われている JavaScript ライブラリーです。このライブラリーには多数のリッチなウィジェットがあるだけでなく、共通 JavaScript タスクのためのユーティリティー・メソッドもあります。さらに、Google Web Toolkit で使用できるように Java にも変換されています。それが、Ext GWT です。必要な作業として、まずは Ext GWT を使用するようにアプリケーションを構成してください。

Ext GWT のセットアップ

Ext GWT のセットアップはかなり単純な作業です。まず、Ext GWT JAR (Ext GWT ダウンロードに含まれる gxt.jar) をクラスパスに配置します。このアプリケーションの場合、lib ディレクトリーを作成して、そこに gxt.jar をコピーしました。その上で、この JAR ファイルをコンパイラーのクラスパスに追加し、そのクラスパスに「./lib/gxt.jar」を追加して DiggApp コンパイル・スクリプトを変更します (リスト 11 を参照)。


リスト 11. DiggApp コンパイル・スクリプトの変更

#!/bin/sh
APPDIR=`dirname $0`;
java -XstartOnFirstThread -Xmx256M -cp "$APPDIR/src:$APPDIR/bin:
/Users/michael/lib/gwt-mac-1.5.3/gwt-user.jar:
/Users/michael/lib/gwt-mac-1.5.3/gwt-dev-mac.jar:./lib/gxt.jar" 
com.google.gwt.dev.GWTCompiler -out "$APPDIR/www" "$@" 
org.developerworks.digg.DiggApp;

次に、GWT が生成する基本の HTML ページを少し変更します。このHTML ファイルの配置場所は、/public ディレクトリーです。変更後の HTML ファイルをリスト 12 に記載します。


リスト 12. 変更後の DiggApp.html

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">  

<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <!--                                           -->
    <!-- Any title is fine                         -->
    <!--                                           -->
    <title>DiggApp</title>
    
    <!--                                           -->
    <!-- This script loads your compiled module.   -->
    <!-- If you add any GWT meta tags, they must   -->
    <!-- be added before this line.                -->
    <!--                                           -->
    <script type="text/javascript" language="javascript" 
        src="org.developerworks.digg.DiggApp.nocache.js"></script>
    <link rel="stylesheet" type="text/css" href="css/ext-all.css" />
  </head>

  <!--                                           -->
  <!-- The body can have arbitrary html, or      -->
  <!-- you can leave the body empty if you want  -->
  <!-- to create a completely dynamic UI.        -->
  <!--                                           -->
  <body>

    <!-- OPTIONAL: include this if you want history support -->
    <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' 
        style="position:absolute;width:0;height:0;border:0"></iframe>

  </body>
</html>

上記では、2 つの変更が行われています。まず、DOCTYPE を変更して、Ext が期待する HTML 3.2にしました。もう 1 つの変更は、Ext に必要な CSS スタイルシート、「css/ext-all.css」を追加したことです。実際には、このファイルをどこかしらにコピーする必要はありません。コンパイラーは、アプリケーションが Ext GWT モジュールを使用していることを認識すると、このファイルを自動的に作成します。このことから必要になるのが、最後の変更です。つまり、Ext GWT モジュールをモジュール定義に追加する必要があります。リスト 13 に、このモジュール定義を記載します。


リスト 13. GXT をサポートするモジュール定義

<module>

      <!-- Inherit the core Web Toolkit stuff.                        -->
      <inherits name='com.google.gwt.user.User'/>
    
      <!-- Inherit the default GWT style sheet.  You can change       -->
      <!-- the theme of your GWT application by uncommenting          -->
      <!-- any one of the following lines.                            -->
      <inherits name='com.google.gwt.user.theme.standard.Standard'/>
      <!-- <inherits name='com.google.gwt.user.theme.chrome.Chrome'/> -->
      <!-- <inherits name='com.google.gwt.user.theme.dark.Dark'/>     -->

      <!-- Other module inherits                                      -->
      <inherits name="com.google.gwt.http.HTTP" />
      <inherits name="com.google.gwt.json.JSON" />

 <inherits name='com.extjs.gxt.ui.GXT'/> 

      <!-- Specify the app entry point class.                         -->
      <entry-point class='org.developerworks.digg.client.DiggApp'/>
    
      <!-- Specify the application specific style sheet.              -->
      <stylesheet src='DiggApp.css' />
</module>

リスト 13 をリスト 9 と見比べてみると、その違いは 1 つしかありません。それは、リスト 13 のモジュールが com.extjs.gxt.ui.GXT モジュールから継承されていることです。つまり、Ext GWT が構成済みになったので、Ext のウィジェットを使用できるということです。

よりリッチな検索結果

Ext には GWT テーブルよりも遙かにリッチな Grid ウィジェットが組み込まれています。Flex の DataGrid で実行できることは、ほとんど Ext の Grid ウィジェットでも実行できます (例えば、列のソートやサイズ変更など)。新しい検索結果の表示ウィジェットには、このウィジェットをベースとして使用します (リスト 14 を参照)。


リスト 14. StoryGrid ウィジェット

public class StoryGrid extends LayoutContainer {

    public StoryGrid(List<Story> stories){
        this.setLayout(new FlowLayout(10));
        this.setSize(750, 300);
        ListStore<BaseModelData> store = this.buildDataModel(stories);
        Grid<BaseModelData> grid = 
            new Grid<BaseModelData>(store, createColumnModel());
        grid.setBorders(true);
        add(grid);
    }
    
    private ColumnModel createColumnModel(){
        List<ColumnConfig> configs = new ArrayList<ColumnConfig>();
        ColumnConfig column = new ColumnConfig();
        column.setId("titleLink");
        column.setHeader("Story");
        column.setWidth(200);
        configs.add(column);
        column = new ColumnConfig();
        column.setId("category");
        column.setHeader("Category");
        column.setWidth(100);
        configs.add(column);
        column = new ColumnConfig();
        column.setId("description");
        column.setHeader("Description");
        column.setWidth(400);
        configs.add(column);
        return new ColumnModel(configs);
    }
    
    private ListStore<BaseModelData> buildDataModel(List<Story> stories){
        ListStore<BaseModelData> data = new ListStore<BaseModelData>();
        for (Story story : stories){
            BaseModelData model = new BaseModelData(story.properties());
            data.add(model);
        }
        return data;
    }
}

Ext の Grid には、列定義とデータ・ストアという 2 つの基本的な要素が必要です。Grid の列定義は、createColumnModel メソッドによって作成されます。列定義は ColumnConfig オブジェクトのリストで、ColumnConfig で重要なのはその ID プロパティーです。この ID が Grid に対し、使用するデータ・モデルのプロパティー名を指示します。データ・モデルは buildDataModel メソッドによって作成されます。ここで使用する GXT の BaseModelData クラスは、単に Java Map をラップするだけに過ぎません。この例では、Story クラスの properties() メソッドを使用しました。リスト 15 を見てください。


リスト 15. Story.properties メソッド

public class Story {    
    public Map<String,Object> properties(){
        Map<String,Object> props = new HashMap<String,Object>();
        props.put("id",id);
        props.put("link", link);
        props.put("title", title);
        props.put("description",description);
        props.put("tags",tags);
        props.put("category",category);
        props.put("votesFor",votesFor);
        props.put("votesAgainst",votesAgainst);
        props.put("titleLink", getTitleLink().getHTML());
        return props;
    }
}

リスト 14createColumnModel メソッドに作成した ColumnConfig オブジェクトをもう一度見てみると、ColumnConfig の ID を「titleLink」に設定すると、そのストリングが、リスト 15 で作成した Map へのキーとして使用されていることがわかります。リスト 5SearchTable を単純に StoryGrid に置き換えて再コンパイルし、デプロイしてください。すると、図 4 に示すようにアプリケーションの外観が多少変わってきます。


図 4. 改善された検索アプリケーション

このアプリケーションをさらに改善するには、UI を強化する方法が考えられます。例えば、Grid にはさらに列を追加することができます。あるいは、ソートに使用可能な列をカスタマイズしたり、サイズを変更できる列や、カスタマイズ・メニューにアクセスできる列をカスタマイズしたりするのも一考です。また、検索フォーム・ウィジェットを Ext の多数のウィジェットで置換することさえも可能です。可能性は無限に広がっています。




上に戻る


まとめ

この連載の最初の記事では、Grails を使用して SOA バックエンドを作成し、Flex を使用して UI を作成しました。今回は、このバックエンドを利用して新しい Web サービスを追加しました。この作業を容易にしたのも、同じく Grails と Groovy です。また、GWT を使用して新しい検索アプリケーションを作成し、GWT の HTTP ライブラリーと JSON ライブラリーを使用することで、GWT は任意の Web サービスと連動できることも説明しました。そして最後に取り上げたのは、よりリッチな UI ウィジェットを GWT に追加する方法となる Ext GWT です。この連載を読み終えた今、皆さんは充実した機能を備えたアプリケーションを Java で作成し、その Java アプリケーションを JavaScript にコンパイルして完全にクライアント上で実行できるようになったはずです。





上に戻る


ダウンロード

内容ファイル名サイズダウンロード形式
Example Grails Applicationdigg.zip1101KBHTTP
Example GWT app sourcedigg-GWT.zip2990KBHTTP
ダウンロード形式について


参考文献

学ぶために

製品や技術を入手するために
  • Grails: この記事では、Grails バージョン 1.0.3 を使用しています。

  • Google Web Toolkit をダウンロードしてください。この記事では、バージョン 1.5.3 を使用しています。

  • Java SDK: この記事では Java SE 1.6_05 を使用しています。

  • IBM 製品の評価版をダウンロードして、DB2、Lotus、Rational、Tivoli、および WebSphere のアプリケーション開発ツールとミドルウェア製品を使ってみてください。


議論するために


著者について

Galpin Michael photo

Michael Galpin は 90年代後半から Web アプリケーションの開発に従事しています。彼は California Institute of Technology で数学の学位を取得しており、現在は、カリフォルニア州サンノゼにある eBay にアーキテクトとして勤務しています。




記事の評価


サイト改善のため、ご意見をお寄せください。こちらのフォームからお願いいたします。



 


 


不充分・不完全である大変素晴らしい
 


この記事を共有する

del.icio.us del.icio.us newsing newsing FC2ブックマーク FC2ブックマーク
Choix! Choix! ニフティクリップ ニフティクリップ Yahoo!ブックマーク Yahoo!ブックマーク
MM/memo MM/memo CZブックマーク CZブックマーク livedoorクリップ livedoorクリップ
はてなブックマーク はてなブックマーク Buzzurl(バザール) Buzzurl(バザール)




上に戻る


Java およびすべての Java 関連の商標およびロゴは、Sun Microsystems, Inc. の米国およびその他の国における商標です。 他の会社名、製品名およびサービス名等はそれぞれ各社の商標です。

    日本IBMについて プライバシー お問い合わせ