prototype.jsを読み解く

第4回 Prototypeライブラリ(932~1289行目)

この記事を読むのに必要な時間:およそ 15.5 分

1139:   getHeader: function(name) {
1140:     try {
1141:       return this.transport.getResponseHeader(name);
1142:     } catch (e) { return null }
1143:   },
1144: 

1139行目からはgetHeader()メソッドです。

もしLoading, Complete状態以前にXHR.getResponseHeader()を呼び出すと例外が発生するので,その場合はtry catchしてnullを返しておきます。

例外が発生しなければgetResponseHeader()の返り値をそのまま返します。

1145:   evalJSON: function() {
1146:     try {
1147:       var json = this.getHeader('X-JSON');
1148:       return json ? json.evalJSON() : null;
1149:     } catch (e) { return null }
1150:   },
1151: 

1145行目からはevalJSON()メソッドです。

すぐ上のgetHeader()メソッドを使ってレスポンス内にX-JSONヘッダがあるかどうかを調べます。

もしあるようならString.evalJSON()を使ってその結果を返します。ないようならnullを返しています。

1152:   evalResponse: function() {
1153:     try {
1154:       return eval((this.transport.responseText || '').unfilterJSON());
1155:     } catch (e) {
1156:       this.dispatchException(e);
1157:     }
1158:   },
1159: 

1152行目からはevalResponse()メソッドです。レスポンスボディ(this.transport.responseText)に対して,念のためString.unfilterJSON()を呼び出し,その結果をeval()します。

ほぼ似たようなことをしているので,String.evalJSON()を使ってもいいような気はします。

1160:   dispatchException: function(exception) {
1161:     (this.options.onException || Prototype.emptyFunction)(this, exception);
1162:     Ajax.Responders.dispatch('onException', this, exception);
1163:   }
1164: });
1165: 

Ajax.Requestの最後はdispatchException() メソッドです。optionsにonExceptionイベント処理関数が登録されていればそれを呼び出し,Ajax.Respondersに登録されている'onException'処理関数が実行されるようにします。

Ajax.Updaterクラス

1166: Ajax.Updater = Class.create();
1167: 

1166行目からはAjax.Updaterクラスです。まずはいつもどおりにClass.create()で雛型を作ります。

1168: Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
1169:   initialize: function(container, url, options) {
1170:     this.container = {
1171:       success: (container.success || container),
1172:       failure: (container.failure || (container.success ? null : container))
1173:     }
1174: 
1175:     this.transport = Ajax.getTransport();
1176:     this.setOptions(options);
1177: 
1178:     var onComplete = this.options.onComplete || Prototype.emptyFunction;
1179:     this.options.onComplete = (function(transport, param) {
1180:       this.updateContent();
1181:       onComplete(transport, param);
1182:     }).bind(this);
1183: 
1184:     this.request(url);
1185:   },
1186: 

1168行目ではまずObject.extend()を使ってAjax.Request.prototypeを継承し,そのままAjax.Updater.prototypeの拡張を開始します。

1169行目からのコンストラクタでは,最初の引数containerとして,単純な要素,もしくはそのIDを渡すこともできますし,複雑な{ success:'要素1', failure:'要素2' }というオブジェクトを渡すこともできます。1170行目では,container引数を後者の形に正規化しています。

1175行目,1176行目,1184行目はAjax.Requestのコンストラクタと同様です。

1178行目からは,ユーザーが指定したonCompleteイベント処理関数の前に,Ajax.Updater独自のthis.updateContent()を呼び出すようにしています。

まず,通常のイベント処理関数の形(function(transport, param){})で関数オブジェクトをつくり,その中身はupdateContent()の呼び出しとそれに続くオリジナルのonComplete()の呼び出しです。作成した関数オブジェクトのthisが自分自身のインスタンスになるようにbind()して,それを元々のthis.options.onCompleteに再代入しています。

options.onCompleteの呼び出し自体は,Ajax.Requestで定義されているrequest()経由で行われ,Ajax.UpdaterではonCompleteに関してはそれ以上のことは行いません。

1187:   updateContent: function() {
1188:     var receiver = this.container[this.success() ? 'success' : 'failure'];
1189:     var response = this.transport.responseText;
1190: 
1191:     if (!this.options.evalScripts) response = response.stripScripts();
1192: 
1193:     if (receiver = $(receiver)) {
1194:       if (this.options.insertion)
1195:         new this.options.insertion(receiver, response);
1196:       else
1197:         receiver.update(response);
1198:     }
1199: 
1200:     if (this.success()) {
1201:       if (this.onComplete)
1202:         setTimeout(this.onComplete.bind(this), 10);
1203:     }
1204:   }
1205: });
1206: 

1187行目からはAjax.UpdaterのキモであるupdateContent()メソッドです。これはAjax.RequestのonCompleteのタイミングで呼び出されます。

まずは,1188行目で,this.success()で成功か失敗かの判別をした上で,更新が行われる要素をreceiverに代入します。また,this.transport.responseTextは何度も利用するので短く記述するためにresponse変数に入れておきます。

1191行目では,options.evalScriptsフラグがセットされていなければ,<script>タグを除去してしまいます。そのため,XHR経由で戻ってくるHTMLコンテンツに <script>タグを含めたい場合にはoptions.evalScriptsフラグを設定しておく必要があります。

1193行目で,receiverに入っている要素を$()関数を使ってElementオブジェクトに変換します。もし指定された要素が存在しなければこのif文はスキップされます。

options.insertionが指定されている場合,Insertion.BottomなどのInsertionオブジェクトが入っているはずなので,newでインスタンスを生成して駆動します。

options.insertionが指定されていなければ,要素receiverのupdate()メソッド(Element.update)を呼び出して要素を更新します。

1200行目からは,success()メソッドが真の時に,this.onCompleteがセットされていればそれを呼び出します。しかし現状では,Prototypeライブラリ内ではこの機能は使われていないようです。

著者プロフィール

栗山淳(くりやまじゅん)

S2ファクトリー株式会社株式会社イメージソース所属。
本業はWeb制作会社の裏方。得意分野はFreeBSDやPerlのはずだが,必要に迫られるとHTML/CSSやJavaScriptも書く。