SiNBLOG

140文字に入らないことを、極稀に書くBlog

goog.net.XhrManagerを使ってみた!

Closure LibraryでXMLHttpRequestを扱うgoog.net.XhrManagerを使ってみました。
これは前回のgoog.net.XhrIoを内部に持ち、複数リクエストを簡単に扱えるようにしたものらしいです。
WEB開発メモ: Closure Library - XhrManager


まずはコンストラクタ実行時に、インスタンスを生成します。
app.js

    /**
     * initialize.
     * @private
     */
    xhrmanagerSample.App.prototype.initialize_ = function(){
        this.eventHandler_ = new gevents.EventHandler(this);
        
        this.xhrManager_ = new goog.net.XhrManager();  //ここで生成
        this.nextXhrId_ = 1;                           //リクエスト管理用のID
        
        var viewBookListButton = gdom.getElement('view-book-list-button');
        this.eventHandler_.listen(viewBookListButton, EventType.CLICK, goog.bind(this.onClickViewBookListButton_, this));
        var bookEntryForm = gdom.getElement('book-entry-form');
        this.eventHandler_.listen(bookEntryForm, EventType.SUBMIT, goog.bind(this.onSubmit_, this));
    };

this.nextXhrId_はXMLHttpRequestを管理するためのIDです。
リクエスト毎に一意になるように、連番で採番します。

残りのコードは、以下の2つの機能を実現するためのものです。
書籍の登録
ボタンを押すと書籍の一覧が出てくる


XMLHttpRequestを送信するための関数です。
app.js

    /**
     * ajax send request.
     * @param {Object} path
     * @param {Object} query
     * @param {Object} callback
     * @param {Object} method
     * @param {Object} opt_content
     */
    xhrmanagerSample.App.prototype.sendRequest = function(path, query, callback, method, opt_content){
        var url = goog.Uri.parse('/closurelibrary/xhriosample/' + path);
        goog.object.forEach(query || {}, function(value, key){
            url.setParameterValue(key, value);
        }, this);
        var headers = {}, body = null;
        if (opt_content) {
            body = goog.json.serialize(opt_content);
            headers['Content-Type'] = 'application/json';
        }
        var error = gdom.getElement('error-message');
        gdom.removeChildren(error);
        return this.xhrManager_.send(this.nextXhrId_++, url, method, body, headers, 0, goog.bind(this.processRequest_, this, callback), 5);
    };

XmlHttpRequestを送信しているのは最後のsend()です。
send()の仕様については、以下の通りです。

id         {string} リクエストのID。他のリクエストと重ならない文字列を指定する
url        {string} リクエスト送信先のURL
method     {string=} リクエストするメソッド。デフォルトはGET
content    {string=} Postするデータ
headers    {Object|goog.structs.Map=} リクエストのヘッダー
priority   {*=} リクエストの優先順位。小さいほど優先順位が高い
callback   {Function=} リクエスト完了時に呼ばれるコールバック関数
maxRetries {number=} リトライの最大回数

GAEでは色々な理由でErrorが発生します。
その場合、リトライを行いたいのですが、XhrManagerを使えば、maxRetriesを指定するだけで実現できます。


リクエストの返答があった時に呼ばれる関数です。
これは標準で呼ばれるものではなく、XhrMnager.send()で私が指定したため、呼ばれます。
app.js

    /**
     * ajax request process method.
     * @param {Object} callback
     * @param {Object} e
     */
    xhrmanagerSample.App.prototype.processRequest_ = function(callback, e){
        var xhr = e.target;
        var error = gdom.getElement('error-message');
        if (xhr.isSuccess()) {
            callback && callback(xhr.getResponseJson('while(1);'));
        }
        else {
            gdom.removeChildren(error);
            if (xhr.getStatus() == '404') {
                gdom.append(error, gsoy.renderAsElement(templates.alert.error, {
                    'message': 'ページが見つかりませんでした。'
                }));
            }
            else {
                gdom.append(error, gsoy.renderAsElement(templates.alert.error, {
                    'message': 'エラーが発生しました。再度、試してみてください。'
                }));
            }
        }
    };

手抜きですが、Error処理なんかもちょこっと入れたりしています。
xhr.isSuccess()で結果が成功していた場合のみ、送信時に指定したコールバック関数を呼ぶようにしています。
xhrオブジェクトから、リクエスト時に指定したIDも取得できるので、ここで処理を分岐することもできると思います。


書籍一覧を取得するための関数です。
ボタン押下時に呼ばれる関数です。
app.js

    /**
     * book list get.
     */
    xhrmanagerSample.App.prototype.getBookList = function(){
        var bookList = gdom.getElement('book-list');
        var loadingIcon = gdom.createDom('span', {
            'class': 'icon-loading16'
        }, '');
        gdom.append(bookList, loadingIcon);
        
        this.sendRequest('list', {}, goog.bind(this.onGetBookListComplete, this), 'GET', {});
    };

リクエストが返ってくるまでの間、くるくる回るアイコンを表示しています。
'list'がURLの最後のPath。
onGetBookListCompleteがリクエスト成功時に呼ばれるコールバック関数です。


書籍一覧取得成功時に呼ばれるコールバック関数です。
app.js

    /**
     * ajax get book list compete.
     * @param {Object} e
     */
    xhrmanagerSample.App.prototype.onGetBookListComplete = function(e){
        var bookList = gdom.getElement('book-list');
        gdom.removeChildren(bookList);
        
        var data = {
            bookList: []
        };
        for (var i = 0, len = e['bookList'].length; i < len; i++) {
            data.bookList[i] = {
                isbn: e['bookList'][i]['isbn'],
                name: e['bookList'][i]['name'],
                link: e['bookList'][i]['link']
            };
        }
        gdom.append(bookList, gsoy.renderAsElement(templates.book.bookList, data));
    };

Closure Templatesを使っています。
この辺りは、以下のエントリーを見ていただくと分かりやすいです。
Closure Templatesを使ってみた! - SinDiary


書籍情報登録時に呼ばれる関数です。
app.js

    /**
     * ajax book form put.
     * @param {Object} param
     */
    xhrmanagerSample.App.prototype.putBook = function(param){
        this.sendRequest('entry', param, goog.bind(this.onPutBookComplete, this), 'PUT', param);
    };

'entry'がURLの最後のPath。
onPutBookCompleteがリクエスト成功時に呼ばれるコールバック関数です。
paramの中に登録する書籍の情報が入っています。


書籍情報登録成功時に呼ばれる関数です。
app.js

    /**
     * ajax book form put complete.
     * @param {Object} e
     */
    xhrmanagerSample.App.prototype.onPutBookComplete = function(e){
        var form = gdom.getElement('book-entry-form');
        form.reset();
    };

フォームをリセットしているだけです。


フォームSubmit時に呼ばれる関数です。
app.js

    /**
     * book entry form submit.
     * @param {Object} e
     */
    xhrmanagerSample.App.prototype.onSubmit_ = function(e){
        e.preventDefault();
        var form = gdom.getElement('book-entry-form');
        var param = gdom.forms.getFormDataMap(form).toObject();
        this.putBook(param);
    };

initialize_()でイベントハンドラーに登録した関数です。
フォームのSubmit時に呼ばれ、実際のSubmitを止めた後、値をXMLHttpRequestで投げるようにしています。


まだまだ、改善点はありますが、とりあえずはこんな感じです。
後、UnitTestをXhrIoの時のように作れれば良いのですが、まだ出来ていません・・・。
goog.net.XhrIoを使ってみた! - SinDiary
伊藤殿に質問して回答していただいたのですが、まだ動くものはできずorz
https://plus.google.com/u/0/114061805681880927213/posts/ZA6B9wthi3D
なんとかしたいと思ってます。


今回のソースはこちら。
app.js
Google Code Archive - Long-term storage for Google Code Project Hosting.