Connection Manager(その2)

朝の続きで、YUIのConnection Managerのトップページを概観する。

CAllbackオブジェクトとScope

「this」という予約語は、相当に厄介な代物に思われる。
thisやsuperといった予約語は、Javaでは当たり前に使われるが、JavaScritptはそもそも(ページスコープ内での)インスタンスを記述するもの。
要するに全てがthis。
関数系やイベントが全て、インスタンスと考えると、thisは「それを含む関数や、変数」を表すと考えるのが普通。
ここでは、success、failure時のハンドラに、オブジェクト・リテラルで記述された変数(これまたオブジェクト)を利用する際には、thisという予約語が正常に働かないよと書いてある。
この場合に、thisを正常に「このインスタンス」と認識させるには、callbackオブジェクト内に、

scope: ハンドラー・オブジェクト

と記載しなさいとのこと。

var AjaxObject = {

        // 通信成功時のコークバックのハンドラ
	handleSuccess:function(o){
                // 以下のthis.processResult()は、AjaxObject..processResult()
         // を意味する。
		// and passes the response object o to AjaxObject's
		// processResult member.
		this.processResult(o);
	},

        // 通信失敗時のコークバックのハンドラ
	handleFailure:function(o){
	},

        // 通信成功時の処理を記述する
	processResult:function(o){
	},

        // XMLHttpRequestの送信(パラメータnew=1&old=2)
	startRequest:function() {
	   YAHOO.util.Connect.asyncRequest('POST', 'php/post.php', callback, "new=1&old=2");
	}

};

/*
 * Callbackオブジェクト
 */
var callback =
{
        // 通信成功時には、AjaxObjectのhandleSuccessにレスポンスオブジェクトを渡す。
	success:AjaxObject.handleSuccess,
        // 通信失敗時には、AjaxObjectのhandlehandleFailureにレスポンスオブジェクトを渡す。
	failure:AjaxObject.handleFailure,

        // このscope属性を指定するのが重要
	scope: AjaxObject
};

// XMLHttpRequestのSend
AjaxObject.startRequest();
Callbackオブジェクトとタイムアウト

コールバックオブジェクトに、以下のように記述すると、通信タイムアウトの設定ができる。

	var callback =
	{
	  success: function(o) {/*success handler code*/},
	  failure: function(o) {/*failure handler code*/},
	  timeout: 5000,
	  argument: [argument1, argument2, argument3]
	}

このうちの

timeout: 5000,

がそれで、単位はmillisec。この場合には、5sec待って、responseが無ければ、通信は中止(abort)されて、Failureのコールバックハンドラに処理が移る。

通信に成功した場合のresponseオブジェクトのプロパティー

通信がsuccessで終了した場合、レスポンスオブジェクトには、以下のプロパティーが設定されて、sucess時のハンドラに渡される。

プロパティー 意味
tId 一意のインクリメントされたトランザクション(通信処理)ID
status HTTPのステータスコード
statusText HTTPのステータスコードに対応したメッセージ
getResponseHeader[label] 指定されたヘッダーラベルの値(String)
getAllResponseHeaders HTTPヘッダー全体。ラベル−値のペアは、"\n"で区切られている。
responseText サーバーレスポンス(String:平文のテキスト)
responseXML サーバーのレスポンス(XMLドキュメント)
argument ユーザー定義の引数、もしくは、コールバック関数に定義された引数
通信に失敗した場合のresponseオブジェクトのプロパティー

通信エラーになったり、タイムアウトになったときにも、Connection Managerはできるだけの情報を得ようとする。以下はエラー、タイムアウト(aborted)の場合で最低保障されているプロパティー値。

1.通信エラーの場合

プロパティー 意味
tId 一意のインクリメントされたトランザクション(通信処理)ID
status 0
statusText "communication failure"
argument ユーザー定義の引数、もしくは、コールバック関数に定義された引数

2.タイムアウトの場合

プロパティー 意味
tId 一意のインクリメントされたトランザクション(通信処理)ID
status -1
statusText "transaction aborted"
argument ユーザー定義の引数、もしくは、コールバック関数に定義された引数
Uploadの場合

(ここは具体的なイメージがわかない。後でサンプルが出てくるか??)
iframe(インラインフレーム)からuploadが行われた場合、Connection Managerは、レスポンスのステータスコードで成功か、失敗かの判断ができない。
その代わりに、CallbackのUploadハンドラーは、文字列で、インラインフレーム用にbodyタグで括られたテキストを受け取ることができる。このレスポンスはどんなタグ(Markup)やScriptを含むことができるから、カスタマイズをすることができる。

プロパティー 意味
tId 一意のインクリメントされたトランザクション(通信処理)ID
responseText Bodyタグで括られたiframe内に示すテキスト(String:平文)
responseXML サーバーのレスポンス(XMLドキュメント)
argument ユーザー定義の引数、もしくは、コールバック関数に定義された引数
FormsとSetForm

Connection Managerは、FromをsetFormメソッドによって、GETでもPOSTでも送ることができる。
通信トランザクションを開始する前に(つまり、XMLHttpRequestを送信する前に)、このメソッドを呼ぶことで、Formの内容からGETのQuery文字列や、Formメッセージを生成して、指定したURLに送ることができる。
この機能を使うには、文字列のname属性値を定義しなくてはならない。
こんな風に書く。

//引数のformIdはidか、HTML Form、もしくはFormオブジェクトのname属性。
var formObject = document.getElementById('aForm');
YAHOO.util.Connect.setForm(formObject);
//この例では、POSTでおくっているが、GETの時は第1引数を'GET'にする。
var cObj = YAHOO.util.Connect.asyncRequest('POST', 'http://www.yahoo.com', callback);

FormにFileをアップロードする際(input type="file"が含まれるとき)には、setFormの第2引数にtrueを指定する。
Fileのアップロードが終わったら、Callbackオブジェクトのuploadメソッド(その1の最後に記述したもの)が呼ばれる。
こんな感じ。

var formObject = document.getElementById('aForm');

   // 第2引数がtrueというのは、FileをUploadすることを意味する。
   YAHOO.util.Connect.setForm(formObject, true);

   var cObj = YAHOO.util.Connect.asyncRequest('POST', 'http://www.yahoo.com', callback);

IEからSSLを通してUploadするときには、setFormの第3引数にもtrueを設定する。

   YAHOO.util.Connect.setForm(formObject, true, trure);

これにより、IEドメインセキュリティーエラーを投げるのを防ぐことができる。

トランザクションとカスタムイベント

Connection ManagerはYAHOO.util.CustomEventをimplementsしている。(JavaではImplementsは意味のある言葉だが、ここでの意味は、同様の機能を実装していると捉えるのが妥当(??)
以下がその一覧。

カスタムイベント 意味
startEvent トランザクションがスタートした際に発火し、subscribeされているハンドラにトランザクションIDを送る
completeEvent トランザクションが完了した際に発火し、subscribeされているハンドラにトランザクションIDを送る
successEvent トランザクション・レスポンスがが完了し、HTTPステータスコードが200番台(正常系)の時に発火する。subscribeされているハンドラにレスポンスオブジェクトを送る。

(callback success handlerに似ている。[注意]ファイル・アップロードのトランザクションでは発火しない)|

failureEvent トランザクション・レスポンスがが完了し、HTTPステータスコードが400/500番台(異常系)の時、もしくは、ステータスコードが不明な場合に発火する。subscribeされているハンドラにレスポンスオブジェクトを送る。

(callback failure handlerに似ている。)|

uploadEvent File Uploadが完了した場合に発火する。File Uploadトランザクションの時だけに発火するもので、successEvent、failureEventに代わるもの。

subscribeされているハンドラにレスポンスオブジェクトを送る。
(callback upload handlerに似ている。)|

abortEvent トランザクションタイムアウトによる中断(abort)か、明示的にYAHOO.util.Connect.abort()によって通信を中断したときに発火する

カスタムイベントは、Global levalで(ページスコープ内の全てのトランザクションに対して)発火するが、トリガーは個々のトランザクションレベルでひかれる。
これだと複数のトランザクションを記述する際に都合が悪い。
この解決策として、以下のように、Globalなハンドラを用意して、そのハンドラをカスタムイベントにsubscribeする例が挙げられている。
こんな感じ。

// YAHOO.util.ConnectのAlias
var YUC = YAHOO.util.Connect;

/*
 * Connection managerのカスタムイベントの全てのハンドラを定義する。
 *
 * イベントハンドラーはオブジェクトメンバーではなく、グローバルなfunctionにする
 * subscribeの際にscope引数を無視すること。
 */
var handleEvents = {
    // globaleventなので、Handler関数のどこでも"this"を使ったらだめ。
	start:function(eventType, args){
    // startEvent発火時の処理を記述。
        // eventTypeはstartEvent(文字列で指定)。argは配列。
        // responseオブジェクト配列の第一引数に入る。responseオブジェクトは
        // ただ1つトランザクションIDを持つ
      // property: tId (the transaction ID).
    },
    
	complete:function(eventType, args){
	// do something when completeEvent fires.
	},

	success:function(eventType, args){
	// do something when successEvent fires.
	},

	failure:function(eventType, args){
	// do something when failureEvent fires.
	},

	// Define this event handler for file upload transactions *only*.
	// This handler will not be used for any other transaction cases.
	upload:function(eventType, args){
	// do something when uploadEvent fires.
	},

	abort:function(eventType, args){
	// do something when abortEvent fires.
	}
};

/*
 * この例は、Connection Manager Levelで発火するカスタムイベントをsubscribe
 * する例。
 * subscribeされると、全てのトランザクションで発火する。
 */

// globaleventなので、Handler関数のどこでも"this"を使ったらだめ。
YUC.startEvent.subscribe(handleEvents.start, handleEvents);
YUC.completeEvent.subscribe(handleEvents.complete, handleEvents);

// File Uploadの時は発火しない。
YUC.successEvent.subscribe(handleEvents.success, handleEvents);

// File Uploadの時は発火しない。
YUC.failureEvent.subscribe(handleEvents.failure, handleEvents);

// FileUploadのsuccess, failureのときだけ発火。
YUC.uploadEvent.subscribe(handleEvents.upload, handleEvents);
YUC.abortEvent.subscribe(handleEvents.abort, handleEvents);

/* (注)
 * asyncRequest()において、callbackオブジェクトを指定する必要はない。
 * (すでに、Connection Manager Levelでsubscribeされているので)
 * callbackオブジェクトもカスタムイベントレベルで書かれているので必要ない。
 * (書いてもいいが、冗長なコードになる。)
 */

var transaction = YUC.asyncRequest('GET', sUrl, { timeout: 3000 });

Global Custom Eventではなくて、ある特定のトランザクションにカスタムイベントを仕込みたい場合には、callbackオブジェクトに以下のように、メンバー変数*customevents*を指定して、callback.customeventsを定義する。

var callback =
{
	customevents:{
		onStart:function(eventType, args) {
		  // eventType は "startEvent".
		  // args[0].tId は整数のtransaction ID.
		  // args[1] は <code>callback.argument</code>, 
          //(ただし、callback.argumentが定義されていた場合)
		},
		onComplete:function(eventType, args) {
		  // eventType は "completeEvent".
		  // args[0].tId は整数のtransaction ID.
		  // args[1] は <code>callback.argument</code>, 
          //(ただし、callback.argumentが定義されていた場合)
		},
		onSuccess:function(eventType, args) {
		  /*
		   * eventType は "successEvent".
		   * args[0] は the response object。
		   * プロパティーは以下。
		   *
		   * args[0].tId
		   * args[0].status
		   * args[0].statusText
		   * args[0].getResponseHeader[ ]
		   * args[0].getAllResponseHeaders
		   * args[0].responseText
		   * args[0].responseXML
		   * args[0].argument
		   */
		},
		onFailure:function(eventType, args) {
		  // eventType は "failureEvent".
		  // args[0] は response object.
		},

		onUpload:function(eventType, args) {
		  // eventType は "uploadEvent".
		  // args[0] は response object.
		},
		onAbort:function(eventType, args) {
		  // eventType は "abortEvent".
		  // args[0].tId は整数のtransaction ID.
		  // args[1] は <code>callback.argument</code>, 
          //(ただし、callback.argumentが定義されていた場合)
		},
	},
}

また、以下のような例が挙げられている。

var handleEvent = {
	start:function(eventType, args){
	// do something when startEvent fires.
	},

	complete:function(eventType, args){
	// do something when completeEvent fires.
	},

	success:function(eventType, args){
	// do something when successEvent fires.
	},

	failure:function(eventType, args){
	// do something when failureEvent fires.
	},

	abort:function(eventType, args){
	// do something when abortEvent fires.
	}
};


// ハンドラー関数の中で"this"を使うには、scopeプロパティーにhandleEventを指定すること。
// scopeプロパティーをomitすると、Globalなカスタム・カスタムイベントとなる。

// 上で定義されたカスタムイベントを使う。
var callback = {
	customevents:{
		onStart:handleEvent.start,
		onComplete:handleEvent.complete,
		onSuccess:handleEvent.success,
		onFailure:handleEvent.failure,
		onAbort:handleEvent.abort
	},
	scope:handleEvent,
 	argument:["foo","bar","baz"]
};

var transaction = YAHOO.util.Connect.asyncRequest('GET', sUrl, callback);
Signature

とりあえず、飛ばし。使うときが来たら参照。

Transactionステータス
var cObj = YAHOO.util.Connect.asyncRequest('GET','http://www.yahoo.com',callback);

var callStatus = YAHOO.util.Connect.isCallInProgress(cObj);

のように、YAHOO.util.Connect.isCallInProgress()を使うと、非同期通信が処理中なのかを判断できる。(処理中の場合にはtrue)が返る。

既知の問題

FireFox3.0では、POSTリクエストに対して、自動的にヘッダーのContent-Type が application/x-www-form-urlencoded; charset=UTF-8となって受け取るようになっている。
日本語を使う場合にはインパクトあり。

ああ疲れた。