Ajax.Respondersオブジェクト

【抜粋】一部省略
Ajax.Responders = {
  responders: [],
(省略)
  dispatch: function(callback, request, transport, json) {
    this.each(function(responder) {
      if (responder[callback] && typeof responder[callback] == 'function') {
        try {
          responder[callback].apply(responder, [request, transport, json]);
        } catch (e) {}
      }
    });
  }
};

Object.extend(Ajax.Responders, Enumerable);

Ajax.Responders.register({
  onCreate: function() {
    Ajax.activeRequestCount++;
  },

  onComplete: function() {
    Ajax.activeRequestCount--;
  }
});

Ajax.Respondersオブジェクトは、Ajax関連オブジェクトのイベント発生時の共通処理を担います。Ajax関連オブジェクトとは、具体的にはAjax.Requestクラス、Ajax.Updaterクラス、Ajax.PeriodicalUpdaterクラスのインスタンスすべてです。イベントと発生時期は以下です

イベント 発生時期
onCreate インスタンス生成時(Ajax.Request.requestメソッド呼び出し時)
onLoading インスタンス生成の0.01秒後
onLoaded データ送信時(ローカルでは発生しない)
onInteractive データ受信開始時
onComplete 通信完了時(エラー時含む)
onException 例外発生時

0:Uninitializedは初期状態なので、prototype.jsだろうがなかろうが「onUninitialized」は発生しないと思います。*1onLoadedは発生します。ただし、ローカルだと発生しないようです。*2

onLoadingはXMLHttpRequestのreadyStateプロパティとは意味が異なっています。readyStateプロパティで1:Loadingは「openメソッド呼び出し後〜sendメソッド呼び出し前」を意味しますが、ここでは「インスタンス生成*3の0.01秒後」に発生します。このため、イベント発生時は必ずしもreadyStateの1:Loading状態ではありません。

【参考サイト】
http://jsgt.org/mt/archives/01/000278.html
【例】
<html>
<head>
<title></title>
<script language="javascript" src="prototype.js" charset="utf-8"></script>
<script language="javascript">
var str = "";
Ajax.Responders.register({ //各イベント発生時にstrへ書き込み
  onUninitialized: function() {
    str += "Uninitialized,";
  },
  onCreate: function() {
    str += "Create,";
  },
  onLoading: function() {
    str += "Loading,";
    $("test").innerHTML = str; //onLoading発生時にstrを要素に書き込み  
  },
  onLoaded: function() {
    str += "Loaded,";
  },
  onInteractive: function() {
    str += "Interactive,";
  },
  onComplete: function() {
    str += "Complete,";
  }
});
function test(){ //Ajaxで'test.txt'を読み込んでalert表示
  var req = new Ajax.Request(
    'test.txt', 
    { method: 'get',
      onComplete:function(_req){
        alert(_req.responseText);
      }
    });
}
</script>
</head>
<body>
<div id="test"></div>
<button onclick="test();">TEST</button>
</body>
</html>

registerは後述のメソッドでイベント発生時の処理を設定します。TESTボタンを押すと、test.txtの内容がalertで表示された後、画面に以下の文字列が表示されます。

【上記例の実行結果(ローカル)】
Create,Interactive,Complete,Loading,

場合によっては違う結果になるかもしれませんが、ローカルで実行すると高い確率でこうなると思います。つまり、インスタンス生成後0.01秒以内に処理が終了、その後Loadingイベントが発生していることになります。onLaodingはちょっと扱いにくいですね・・・。

Webサーバ経由で実行すると以下のようになります。(要素への書き込みはonCompleteで行います)

【上記例の実行結果(Webサーバ経由)】※Loadingの位置は後ろにずれる場合あり
Create,Loading,Loaded,Interactive,Complete,

Loadedが表示されます。Loadingの位置は後ろにずれる場合があります。

onreadysatechange時のreadyStateの値を調査したところ、1>1>2>3>4となっていました。なぜ1:Loadingが2回発生するのかはわかりません。。。*4 proptotype.jsはこれを嫌い、1回にしようとしている気がします。ただ、0.01秒後というのはやっぱり意味が分かりませんね・・・。

onCreateとonCompleteにはデフォルトで Ajax.activeRequestCountをインクリメント・デクリメントする関数が設定されていますが、これを上書きする心配はありません。各処理は基本的に上書きされず、追加されるだけだからです。流れとしては

  • registerメソッドによりrespondersプロパティ(配列)にオブジェクトを追加。
  • dispatchメソッドによりresponders配列の各オブジェクトの該当メソッド(onComlete等)をすべて実行。

となっています。

各イベント時に実行される関数には引数が二つ渡されます。第一は通信で使用しているXMLHttpRequestオブジェクト、第二は受信データのヘッダにX-JSONという名前のものがあった場合に、その値をeval関数で実行して作成したオブジェクトが渡されます。jsonについてはAjax.Request.evalJSONメソッドで述べたいと思います。(jsonデータは「({name:value,...})」のように全体を括弧で括ったりしないと扱えません。後述。)

余談ですが、Ajax.RequestのonCompleteはそのオブジェクトだけの処理となります。

説明が前後してまった部分がありますが、各メンバの解読に移ります。

respondersプロパティ

【抜粋】
  responders: [],

空のArrayオブジェクト(配列)です。以降のメソッドによりオブジェクトが登録・削除されます。

_eachメソッド

【抜粋】
  _each: function(iterator) {
    this.responders._each(iterator);
  },

Ajax.Respondersオブジェクトはは後でEnumerableクラスを継承しています。_eachメソッドはEnumerableクラスの抽象メソッドの実装です。respondersプロパティの各オブジェクトをiteratorにより処理することができます。

ただ、responders(Arrayオブジェクト)のeachメソッドでなく、_eachメソッドを使用している理由がよく分かりません。処理は変わりませんけど。ルール上、eachメソッドを使ったほうがいいような。

registerメソッド

【抜粋】
  register: function(responderToAdd) {
    if (!this.include(responderToAdd))
      this.responders.push(responderToAdd);
  },

引数のオブジェクトresponderToAddが、respondersプロパティに含まれていない場合、これに追加します。responderToAddは前述したイベント名のメソッドを一つ以上持ちます

unregisterメソッド

【抜粋】
  unregister: function(responderToRemove) {
    this.responders = this.responders.without(responderToRemove);
  },

前述registerメソッドの逆。引数のオブジェクトresponderToRemoveをwithoutメソッドによりthis.respondersより取り除きます。

dispatchメソッド

【抜粋】
  dispatch: function(callback, request, transport, json) {
    this.each(function(responder) {
      if (responder[callback] && typeof responder[callback] == 'function') {
        try {
          responder[callback].apply(responder, [request, transport, json]);
        } catch (e) {}
      }
    });
  }

自オブジェクトthisの、respondersプロパティの各オブジェクトについて、引数callbackと同名のメソッド(=関数)があった場合、引数request, transport, jsonを引き継いで実行します。・・・ただ、ここでapply関数を使用する必要性はない気がするのですが。この場合は以下でも変わらないような?

【参考】apply関数を使用しない場合
responder[callback](request, transport, json);

エラーchatch時は何もせずに処理を続行しています。

dispatchメソッドはAjax.Requestクラス内で使用されます。プログラマが呼び出すケースはほとんどないと思います。

【例】
<html>
<head>
<title></title>
<script language="javascript" src="prototype.js" charset="utf-8"></script>
<script language="javascript">
var str = "";
var rsp1 = {
  onCreate : function() {
    str += "Create1,";
  }
};
var rsp2 = {
  onCreate : function() {
    str += "Create2,";
  }
};
var rsp3 = {
  onCreate : function() {
    str += "Create3,";
  }
};
Ajax.Responders.register(rsp1); //rsp1登録
Ajax.Responders.register(rsp2); //rsp2登録
Ajax.Responders.register(rsp3); //rsp3登録
Ajax.Responders.unregister(rsp2); //rsp2削除
function test(){
  var req = new Ajax.Request(
    'test.txt', 
    { method: 'get',
      onComplete:function(_req){
        alert(_req.responseText);
        $("test").innerHTML = str; //要素に書き込み
      }
    });
}
</script>
</head>
<body>
<div id="test"></div>
<button onclick="test();">TEST</button>
</body>
</html>
【上記例の実行結果】
Create1,Create3,

Enumerableクラスの継承

【抜粋】
Object.extend(Ajax.Responders, Enumerable);

Enumerableクラスを継承しています。

デフォルト処理の設定

【抜粋】
Ajax.Responders.register({
  onCreate: function() {
    Ajax.activeRequestCount++;
  },

  onComplete: function() {
    Ajax.activeRequestCount--;
  }
});

onCreate時にAjax.activeRequestCountをインクリメント、onComplete時にデクリメントしています。前述したように、これが上書きされることはありません。

*1:2006/08/21追記。XMLHttpRequestのabortメソッドで発生するかとも思ったのですが、発生しませんでした(IE6で確認)。

*2:以前2:Loadedは使われないと書いてしまいましたが、間違いです。すみません

*3:正確にはAjax.Request.requestメソッド内のsetTimeout関数(645行目)

*4:send時にもう一回1:Loadingが発生するようです。IE,Netscapeで確認