Symfoware

Symfowareについての考察blog

Google Web Toolkit 2.1 JSON形式のデータの利用

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」
として、完了ボタンを押下。
新しいクラスを作成します。

11_001_20101109002730.png



作成した「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
を表示すると、こんな結果が得られると思います。

11_002_20101109002730.png







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」
として、完了を押下します。

11_003.png


作成した「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への変更は完了です。

開発用のサーバーを起動して、ブラウザからアクセスすると従来どおりの
表示が行われているはず。


11_004.png




最終的に、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);
    }
}









関連記事

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2010/11/09(火) 00:29:51|
  2. Google Web Toolkit
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集
<<Google Web Toolkit 2.1 はてなブックマークエントリーをJSONPで取得 その1 | ホーム | Google Web Toolkit 2.1 GWT RPCの使用>>

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバック URL
https://symfoware.blog.fc2.com/tb.php/641-a155a061
この記事にトラックバックする(FC2ブログユーザー)