Google Web Toolkit 2.1のチュートリアルを試してみています。
Google Web Toolkit に関する記事の一覧前回はGWT RPCを使用したクライアントとサーバー間の通信を試しました。
Google Web Toolkit 2.1 GWT RPCの使用今回は、GWT RPCではなく普通のJSON形式のデータを取り扱う方法を試して見ます。
Retrieving JSON Dataこちらを参考に、プログラムを作成しました。
1. Creating a source of JSON data
StockWatcher.javaのrefreshWatchListメソッドで、株価をランダムに生成しています。
Google Web Toolkit 2.1 GWT RPCの使用この記事では、GWT RPCを使用してサーバー側で株価を生成するように
変更しました。
今回は、株価をJavaのサーブレットで作成し、JSON形式のレスポンスを作成。
クライアント側のJavaScriptで解析し表示するように変更します。
パッケージ・エクスプローラーの
「com.google.gwt.sample.stockwatcher.client」
を選択した状態で、Eclipseのツールバーの[ファイル]-[新規]-[クラス]を選択します。
パッケージ名を「com.google.gwt.sample.stockwatcher.server」に変更
クラス名を「JsonStockData」
として、完了ボタンを押下。
新しいクラスを作成します。

作成した「JsonStockData」を以下の内容に変更します。
package com.google.gwt.sample.stockwatcher.server;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Random;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class JsonStockData extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final double MAX_PRICE = 100.0; // $100.00
private static final double MAX_PRICE_CHANGE = 0.02; // +/- 2%
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Random rnd = new Random();
PrintWriter out = resp.getWriter();
out.println('[');
String[] stockSymbols = req.getParameter("q").split(" ");
for (String stockSymbol : stockSymbols) {
double price = rnd.nextDouble() * MAX_PRICE;
double change = price * MAX_PRICE_CHANGE
* (rnd.nextDouble() * 2f - 1f);
out.println(" {");
out.print(" \"symbol\": \"");
out.print(stockSymbol);
out.println("\",");
out.print(" \"price\": ");
out.print(price);
out.println(',');
out.print(" \"change\": ");
out.println(change);
out.println(" },");
}
out.println(']');
out.flush();
}
}
このサーブレットにアクセスできるよう、WEB-INF/web.xmlを編集します。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<!-- Servlets -->
<servlet>
<!--====== jsonStockDataというサーブレットを定義 ======-->
<servlet-name>jsonStockData</servlet-name>
<servlet-class>com.google.gwt.sample.stockwatcher.server.JsonStockData</servlet-class>
</servlet>
<servlet-mapping>
<!--====== /stockwatcher/stockPricesというURLに関連付け ======-->
<servlet-name>jsonStockData</servlet-name>
<url-pattern>/stockwatcher/stockPrices</url-pattern>
</servlet-mapping>
<!-- Default page to serve -->
<welcome-file-list>
<welcome-file>StockWatcher.html</welcome-file>
</welcome-file-list>
</web-app>
ここまでで、JSONデータの取得が行えるようになっているはずです。
ブラウザで
http://localhost:8888/stockwatcher/stockPrices?q=ABC+DEF
を表示すると、こんな結果が得られると思います。

2. Manipulating JSON data in the client-side code
まず、JSONデータをラップするためのクラスを作成します。
パッケージ・エクスプローラーで、
「com.google.gwt.sample.stockwatcher.client」
を選択した状態で、Eclipseのツールバーの[ファイル]-[新規作成]-[クラス]を選択。
パッケージは「com.google.gwt.sample.stockwatcher.client」
クラス名は「StockData」
スーパークラスは「com.google.gwt.core.client.JavaScriptObject」
として、完了を押下します。

作成した「StockData」を以下の内容で更新します。
package com.google.gwt.sample.stockwatcher.client;
import com.google.gwt.core.client.JavaScriptObject;
public class StockData extends JavaScriptObject {
// Overlay types always have protected, zero argument constructors.
protected StockData() {
}
// JSNI methods to get stock data.
public final native String getSymbol() /*-{ return this.symbol; }-*/;
public final native double getPrice() /*-{ return this.price; }-*/;
public final native double getChange() /*-{ return this.change; }-*/;
// Non-JSNI method to return change percentage. // [4]
public final double getChangePercent() {
return 100.0 * getChange() / getPrice();
}
}
このクラスが、サーバーから取得したデータの
{
"symbol": "DEF",
"price": 43.819500786906815,
"change": 0.18992536537119453
}
この部分に割り当てられるイメージでよいかな?と思っています。
3. Making HTTP requests
まず、StockWatcher.gwt.xmlに変更を加えます。
<?xml version="1.0" encoding="UTF-8"?>
<module rename-to='stockwatcher'>
<!-- 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" />
<!-- Specify the app entry point class. -->
<entry-point class='com.google.gwt.sample.stockwatcher.client.StockWatcher'/>
<!-- Specify the paths for translatable code -->
<source path='client'/>
<source path='shared'/>
</module>
「com.google.gwt.http.HTTP」の記載を追加することで、サーバー側との
HTTP通信が行えるようになるようです。
※雛形のJavaScriptにメソッドが追加出力されるようになる?
次に、StockWatcher.javaに以下のimportを追加。
import com.google.gwt.core.client.JsArray
import com.google.gwt.core.client.GWT;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.http.client.URL;
import java.util.Iterator;
データ取得を行うURLを定義
private static final String JSON_URL = GWT.getModuleBaseURL() + "stockPrices?q=";
株価データの取得を行うメソッド「refreshWatchList」を変更します。
// 価格情報の更新を行う
private void refreshWatchList() {
if (stocks.size() == 0) {
return;
}
String url = JSON_URL;
// Append watch list stock symbols to query URL.
Iterator iter = stocks.iterator();
while (iter.hasNext()) {
url += iter.next();
if (iter.hasNext()) {
url += "+";
}
}
url = URL.encode(url);
// Send request to server and catch any errors.
RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, url);
try {
Request request = builder.sendRequest(null, new RequestCallback() {
public void onError(Request request, Throwable exception) {
displayError("Couldn't retrieve JSON");
}
public void onResponseReceived(Request request,
Response response) {
if (200 == response.getStatusCode()) {
updateTable(asArrayOfStockData(response.getText()));
} else {
displayError("Couldn't retrieve JSON ("
+ response.getStatusText() + ")");
}
}
});
} catch (RequestException e) {
displayError("Couldn't retrieve JSON");
}
}
取得したJSON形式のデータを配列に変換するメソッドを追加。
private final native JsArray<StockData> asArrayOfStockData(String json) /*-{
return eval(json);
}-*/;
updateTableの引数を、「StockPrice[]」から「JsArray<StockData>」に変更します。
/*
private void updateTable(StockPrice[] prices) {
// テーブルの更新処理
for (int i = 0; i < prices.length; i++) {
updateTable(prices[i]);
}
*/
private void updateTable(JsArray<StockData> prices) {
// テーブルの更新処理
for (int i = 0; i < prices.length(); i++) {
updateTable(prices.get(i));
}
DateTimeFormat timeFormat = DateTimeFormat.getFormat("yyyy年MM月dd日 HH:mm:ss");
// 最終更新時間を表示
lastUpdatedLabel.setText("最終更新時間 : "
+ timeFormat.format(new Date(), TimeZone.createTimeZone(-60 * 9)));
// ここまで処理がくれば、エラーは発生していない
// エラーメッセージ領域を非表示にする
errorMsgLabel.setVisible(false);
}
もう一箇所、updateTableの引数「StockPrice」を「StockData」に変更。
//private void updateTable(StockPrice price) {
private void updateTable(StockData price) {
// ArrayListに指定された株式銘柄コードが存在しない場合は処理中断
if (!stocks.contains(price.getSymbol())) {
return;
}
4. Handling GET errors
エラーメッセージ表示用のメソッド「displayError」を定義します。
// エラー表示
private void displayError(String error) {
errorMsgLabel.setText("Error: " + error);
errorMsgLabel.setVisible(true);
}
駆け足でしたが、これでGWT RPCからJSONへの変更は完了です。
開発用のサーバーを起動して、ブラウザからアクセスすると従来どおりの
表示が行われているはず。

最終的に、StockWatcher.javaはこうなりました。
package com.google.gwt.sample.stockwatcher.client;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.user.client.Window;
import java.util.ArrayList;
import com.google.gwt.user.client.Timer;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.TimeZone;
import java.util.Date;
import com.google.gwt.core.client.GWT;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.http.client.URL;
import java.util.Iterator;
public class StockWatcher implements EntryPoint {
// 更新間隔を指定
private static final int REFRESH_INTERVAL = 5000; // ms
private VerticalPanel mainPanel = new VerticalPanel();
private FlexTable stocksFlexTable = new FlexTable();
private HorizontalPanel addPanel = new HorizontalPanel();
private TextBox newSymbolTextBox = new TextBox();
private Button addStockButton = new Button("追加");
private Label lastUpdatedLabel = new Label();
// テーブルに表示しているデータ保存用の変数
private ArrayList<String> stocks = new ArrayList<String>();
private StockPriceServiceAsync stockPriceSvc = GWT.create(StockPriceService.class);
private Label errorMsgLabel = new Label();
private static final String JSON_URL = GWT.getModuleBaseURL() + "stockPrices?q=";
/**
* Entry point method.
*/
public void onModuleLoad() {
// テーブルの作成
stocksFlexTable.setText(0, 0, "銘柄");
stocksFlexTable.setText(0, 1, "価格");
stocksFlexTable.setText(0, 2, "変動");
stocksFlexTable.setText(0, 3, "削除");
// CellPaddingを指定して、間隔を
stocksFlexTable.setCellPadding(6);
// スタイルの指定を追加
stocksFlexTable.getRowFormatter().addStyleName(0, "watchListHeader");
stocksFlexTable.addStyleName("watchList");
stocksFlexTable.getCellFormatter().addStyleName(0, 1, "watchListNumericColumn");
stocksFlexTable.getCellFormatter().addStyleName(0, 2, "watchListNumericColumn");
stocksFlexTable.getCellFormatter().addStyleName(0, 3, "watchListRemoveColumn");
// 部品が横に並ぶパネルに、銘柄入力テキストとボタンを追加
addPanel.add(newSymbolTextBox);
addPanel.add(addStockButton);
// スタイルの指定を追加
addPanel.addStyleName("addPanel");
// エラーメッセージ表示領域
errorMsgLabel.setStyleName("errorMessage");
errorMsgLabel.setVisible(false);
// 部品が縦に並ぶパネルに、エラー表示ラベル、テーブルと上記パネル、
// 最終更新日ラベルを追加
mainPanel.add(errorMsgLabel);
mainPanel.add(stocksFlexTable);
mainPanel.add(addPanel);
mainPanel.add(lastUpdatedLabel);
// StockWatcher.htmlのid=stockListのdivを
// ルートパネルとして取得し、mainPanelを設定する。
RootPanel.get("stockList").add(mainPanel);
// 起動時、銘柄入力のテキストボックスにフォーカスを設定する
newSymbolTextBox.setFocus(true);
// 追加ボタンにイベントリスナーを設定
addStockButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
addStock();
}
});
// 銘柄入力テキストにイベントリスナーを設定
newSymbolTextBox.addKeyPressHandler(new KeyPressHandler() {
public void onKeyPress(KeyPressEvent event) {
// 入力されたキーが「Enter」だったら、追加処理を実行する
if (event.getCharCode() == KeyCodes.KEY_ENTER) {
addStock();
}
}
});
// データの自動更新を行うために、onModuleLoadメソッドでタイマーを生成
Timer refreshTimer = new Timer() {
@Override
public void run() {
refreshWatchList();
}
};
refreshTimer.scheduleRepeating(REFRESH_INTERVAL);
}
// 株式銘柄の追加処理
private void addStock() {
// テキストボックスから入力された文字列を取得
final String symbol = newSymbolTextBox.getText().toUpperCase().trim();
newSymbolTextBox.setFocus(true);
// 証券コードは、1文字以上10文字以下
// 使える文字は数字・アルファベット・ドット(.)のみ
if (!symbol.matches("^[0-9A-Z\\.]{1,10}$")) {
// 入力された文字が証券コードではない場合は、エラーメッセージ表示
Window.alert("'" + symbol + "' は不正な証券コードです。");
newSymbolTextBox.selectAll();
return;
}
newSymbolTextBox.setText("");
// 証券コードの重複チェック処理
// 追加予定の証券コードが既にリストに含まれていたら、追加処理を実行しない。
if (stocks.contains(symbol)) {
return;
}
// テーブルへの行追加処理
// 現在のテーブルの行数を取得
int row = stocksFlexTable.getRowCount();
// ArrayListに追加した証券コードを退避
stocks.add(symbol);
// テーブルへ行の追加実行
stocksFlexTable.setText(row, 0, symbol);
// ラベルを追加
stocksFlexTable.setWidget(row, 2, new Label());
// スタイルの指定を追加
stocksFlexTable.getCellFormatter().addStyleName(row, 1, "watchListNumericColumn");
stocksFlexTable.getCellFormatter().addStyleName(row, 2, "watchListNumericColumn");
stocksFlexTable.getCellFormatter().addStyleName(row, 3, "watchListRemoveColumn");
// 追加した行に削除ボタンを配置
Button removeStockButton = new Button("x");
removeStockButton.addStyleDependentName("remove");
// 削除ボタンが押されたときのイベントを定義
removeStockButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
// 削除する行のインデックスを取得
int removedIndex = stocks.indexOf(symbol);
// ArrayListから削除
stocks.remove(removedIndex);
// テーブルからも削除
stocksFlexTable.removeRow(removedIndex + 1);
}
});
// 削除ボタンをテーブルに追加
stocksFlexTable.setWidget(row, 3, removeStockButton);
// 証券の金額を取得
refreshWatchList();
}
// 価格情報の更新を行う
private void refreshWatchList() {
if (stocks.size() == 0) {
return;
}
String url = JSON_URL;
// Append watch list stock symbols to query URL.
Iterator iter = stocks.iterator();
while (iter.hasNext()) {
url += iter.next();
if (iter.hasNext()) {
url += "+";
}
}
url = URL.encode(url);
// Send request to server and catch any errors.
RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, url);
try {
Request request = builder.sendRequest(null, new RequestCallback() {
public void onError(Request request, Throwable exception) {
displayError("Couldn't retrieve JSON");
}
public void onResponseReceived(Request request,
Response response) {
if (200 == response.getStatusCode()) {
updateTable(asArrayOfStockData(response.getText()));
} else {
displayError("Couldn't retrieve JSON ("
+ response.getStatusText() + ")");
}
}
});
} catch (RequestException e) {
displayError("Couldn't retrieve JSON");
}
}
private final native JsArray<StockData> asArrayOfStockData(String json) /*-{
return eval(json);
}-*/;
/*
private void updateTable(StockPrice[] prices) {
// テーブルの更新処理
for (int i = 0; i < prices.length; i++) {
updateTable(prices[i]);
}
*/
private void updateTable(JsArray<StockData> prices) {
// テーブルの更新処理
for (int i = 0; i < prices.length(); i++) {
updateTable(prices.get(i));
}
DateTimeFormat timeFormat = DateTimeFormat.getFormat("yyyy年MM月dd日 HH:mm:ss");
// 最終更新時間を表示
lastUpdatedLabel.setText("最終更新時間 : "
+ timeFormat.format(new Date(), TimeZone.createTimeZone(-60 * 9)));
// ここまで処理がくれば、エラーは発生していない
// エラーメッセージ領域を非表示にする
errorMsgLabel.setVisible(false);
}
//private void updateTable(StockPrice price) {
private void updateTable(StockData price) {
// ArrayListに指定された株式銘柄コードが存在しない場合は処理中断
if (!stocks.contains(price.getSymbol())) {
return;
}
int row = stocks.indexOf(price.getSymbol()) + 1;
// 価格表示用のフォーマット
String priceText = NumberFormat.getFormat("#,##0.00").format(
price.getPrice());
// 変動率表示用のフォーマット
NumberFormat changeFormat = NumberFormat
.getFormat("+#,##0.00;-#,##0.00");
// 価格・変動率をフォーマットした文字列を取得
String changeText = changeFormat.format(price.getChange());
String changePercentText = changeFormat
.format(price.getChangePercent());
// 価格・変動率のテーブル表示を更新
stocksFlexTable.setText(row, 1, priceText);
// ラベルに対してテキストを追加するようにする。
Label changeWidget = (Label)stocksFlexTable.getWidget(row, 2);
changeWidget.setText(changeText + " (" + changePercentText + "%)");
// 変動率に応じて、適用するスタイルの名称を変更
String changeStyleName = "noChange";
if (price.getChangePercent() < -0.1f) {
changeStyleName = "negativeChange";
}
else if (price.getChangePercent() > 0.1f) {
changeStyleName = "positiveChange";
}
changeWidget.setStyleName(changeStyleName);
}
// エラー表示
private void displayError(String error) {
errorMsgLabel.setText("Error: " + error);
errorMsgLabel.setVisible(true);
}
}
- 関連記事
-
テーマ:プログラミング - ジャンル:コンピュータ
- 2010/11/09(火) 00:29:51|
- Google Web Toolkit
-
| トラックバック:0
-
| コメント:0
- | 編集