リポジトリの差分を追う
第四回目です。
前回、
これはSubversionのannotateサブコマンドで確認することができます。
% svn annotate http://svn.rubyonrails.org/rails/spinoffs/prototype/trunk/src/prototype.js 3362 sam <%= include 'HEADER' %> 3362 sam 3362 sam var Prototype = { ...
これでその行が最後に変更されたリビジョンがわかるので、
最終的に変更点のリビジョンを突き止めたら、
では、
Ajaxオブジェクト
0932: var Ajax = {
0933: getTransport: function() {
0934: return Try.these(
0935: function() {return new XMLHttpRequest()},
0936: function() {return new ActiveXObject('Msxml2.XMLHTTP')},
0937: function() {return new ActiveXObject('Microsoft.XMLHTTP')}
0938: ) || false;
0939: },
0940:
0941: activeRequestCount: 0
0942: }
0943:
Ajaxは複数のクラスを包含するための名前空間となっており、
getTransport()関数は、
941行目のactiveRequestCountは、
0944: Ajax.Responders = {
0945: responders: [],
0946:
0947: _each: function(iterator) {
0948: this.responders._each(iterator);
0949: },
0950:
Ajax.
register()、
Ajax.
948行目では、
0951: register: function(responder) {
0952: if (!this.include(responder))
0953: this.responders.push(responder);
0954: },
0955:
0956: unregister: function(responder) {
0957: this.responders = this.responders.without(responder);
0958: },
0959:
951行目からのregister()関数では、
ここで、
このとき、
956行目からはunregister()関数です。Array.
0960: dispatch: function(callback, request, transport, json) {
0961: this.each(function(responder) {
0962: if (typeof responder[callback] == 'function') {
0963: try {
0964: responder[callback].apply(responder, [request, transport, json]);
0965: } catch (e) {}
0966: }
0967: });
0968: }
0969: };
0970:
960行目からのdispatch()関数は、
最初の引数callbackには、
961行目のeach()で、
0971: Object.extend(Ajax.Responders, Enumerable);
0972:
Ajax.
0973: Ajax.Responders.register({
0974: onCreate: function() {
0975: Ajax.activeRequestCount++;
0976: },
0977: onComplete: function() {
0978: Ajax.activeRequestCount--;
0979: }
0980: });
0981:
973行目で、
これにより、
0982: Ajax.Base = function() {};
0983: Ajax.Base.prototype = {
0984: setOptions: function(options) {
0985: this.options = {
0986: method: 'post',
0987: asynchronous: true,
0988: contentType: 'application/x-www-form-urlencoded',
0989: encoding: 'UTF-8',
0990: parameters: ''
0991: }
0992: Object.extend(this.options, options || {});
0993:
0994: this.options.method = this.options.method.toLowerCase();
0995: if (typeof this.options.parameters == 'string')
0996: this.options.parameters = this.options.parameters.toQueryParams();
0997: }
0998: }
0999:
他のAjax.*クラスで利用するための、
982行目でコンストラクタ用に空の関数を定義して、
ここでは、
994行目ではoptions.
options.
Ajax.Requestクラス
1000: Ajax.Request = Class.create();
1001: Ajax.Request.Events =
1002: ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
1003:
1000行目からはAjax.
いつもどおりClass.
1004: Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
1005: _complete: false,
1006:
1007: initialize: function(url, options) {
1008: this.transport = Ajax.getTransport();
1009: this.setOptions(options);
1010: this.request(url);
1011: },
1012:
1004行目からAjax.
まずはObject.
ここではまず、
これにより、
_complete変数は、
1007行目からはコンストラクタです。先に定義したAjax.
1013: request: function(url) {
1014: this.url = url;
1015: this.method = this.options.method;
1016: var params = Object.clone(this.options.parameters);
1017:
1018: if (!['get', 'post'].include(this.method)) {
1019: // simulate other verbs over post
1020: params['_method'] = this.method;
1021: this.method = 'post';
1022: }
1023:
1024: this.parameters = params;
1025:
1026: if (params = Hash.toQueryString(params)) {
1027: // when GET, append parameters to URL
1028: if (this.method == 'get')
1029: this.url += (this.url.include('?') ? '&' : '?') + params;
1030: else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
1031: params += '&_=';
1032: }
1033:
1034: try {
1035: if (this.options.onCreate) this.options.onCreate(this.transport);
1036: Ajax.Responders.dispatch('onCreate', this, this.transport);
1037:
1038: this.transport.open(this.method.toUpperCase(), this.url,
1039: this.options.asynchronous);
1040:
1041: if (this.options.asynchronous)
1042: setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10);
1043:
1044: this.transport.onreadystatechange = this.onStateChange.bind(this);
1045: this.setRequestHeaders();
1046:
1047: this.body = this.method == 'post' ? (this.options.postBody || params) : null;
1048: this.transport.send(this.body);
1049:
1050: /* Force Firefox to handle ready state 4 for synchronous requests */
1051: if (!this.options.asynchronous && this.transport.overrideMimeType)
1052: this.onStateChange();
1053:
1054: }
1055: catch (e) {
1056: this.dispatchException(e);
1057: }
1058: },
1059:
実際のXHR通信を行うrequest()メソッドです。直接渡される引数はurlだけですが、
まず1014~1015行目でurlとmethodをインスタンス内に格納します。ここで関数ローカルの変数として定義していないのは、
1016行目では、
1018行目からは、
1024行目でこの時点でのparamsをthis.
1024行目からで、
メソッドが'get'の場合、
メソッドが'post'の際に、
この部分はRailsのSubversionリポジトリに入った当初からあったようで、
1034行目からいよいよXHRの実行です。全体をtry {}で括って、
まず、
次に、
1038行目でXHRのopen()メソッドを呼んでいます。
1041行目において、
XHRのonreadystatechangeイベントハンドラには、
1045行目で、
1047行目では、
1048行目で、
1050行目からは、
try{}内で例外が発生した場合、
- mozilla nsIXMLHttpRequest インターフェイス
- XMLHttpRequest Object (W3C Working Draft)
- ADC - Dynamic HTML and XML: The XMLHttpRequest Object
1060: onStateChange: function() {
1061: var readyState = this.transport.readyState;
1062: if (readyState > 1 && !((readyState == 4) && this._complete))
1063: this.respondToReadyState(this.transport.readyState);
1064: },
1065:
1060行目からはonStateChange()メソッドです。
XHRが保持するreadyState変数を取得し、
1066: setRequestHeaders: function() {
1067: var headers = {
1068: 'X-Requested-With': 'XMLHttpRequest',
1069: 'X-Prototype-Version': Prototype.Version,
1070: 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
1071: };
1072:
1073: if (this.method == 'post') {
1074: headers['Content-type'] = this.options.contentType +
1075: (this.options.encoding ? '; charset=' + this.options.encoding : '');
1076:
1077: /* Force "Connection: close" for older Mozilla browsers to work
1078: * around a bug where XMLHttpRequest sends an incorrect
1079: * Content-length header. See Mozilla Bugzilla #246651.
1080: */
1081: if (this.transport.overrideMimeType &&
1082: (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
1083: headers['Connection'] = 'close';
1084: }
1085:
1086: // user-defined headers
1087: if (typeof this.options.requestHeaders == 'object') {
1088: var extras = this.options.requestHeaders;
1089:
1090: if (typeof extras.push == 'function')
1091: for (var i = 0, length = extras.length; i < length; i += 2)
1092: headers[extras[i]] = extras[i+1];
1093: else
1094: $H(extras).each(function(pair) { headers[pair.key] = pair.value });
1095: }
1096:
1097: for (var name in headers)
1098: this.transport.setRequestHeader(name, headers[name]);
1099: },
1100:
1066行目からはsetRequestHeaders()メソッドです。これは他の場所からも利用しうるから単独の関数にした、
まず、
次に、
1077行目からは、
1086行目からはoptions.
ここではHash形式のオブジェクトか配列が想定されているため、
いったんextrasという変数に代入した後に、
最後に1097行目から、
1101: success: function() {
1102: return !this.transport.status
1103: || (this.transport.status >= 200 && this.transport.status < 300);
1104: },
1105:
1101行目からはsuccess()メソッドです。
XHRのstatusプロパティが偽なら偽を返し、
statusプロパティが利用できるのは、
statusにまだ値が入っていなければ偽を返します。
1106: respondToReadyState: function(readyState) {
1107: var state = Ajax.Request.Events[readyState];
1108: var transport = this.transport, json = this.evalJSON();
1109:
1110: if (state == 'Complete') {
1111: try {
1112: this._complete = true;
1113: (this.options['on' + this.transport.status]
1114: || this.options['on' + (this.success() ? 'Success' : 'Failure')]
1115: || Prototype.emptyFunction)(transport, json);
1116: } catch (e) {
1117: this.dispatchException(e);
1118: }
1119:
1120: var contentType = this.getHeader('Content-type');
1121: if (contentType && contentType.strip().
1122: match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i))
1123: this.evalResponse();
1124: }
1125:
1126: try {
1127: (this.options['on' + state] || Prototype.emptyFunction)(transport, json);
1128: Ajax.Responders.dispatch('on' + state, this, transport, json);
1129: } catch (e) {
1130: this.dispatchException(e);
1131: }
1132:
1133: if (state == 'Complete') {
1134: // avoid memory leak in MSIE: clean up
1135: this.transport.onreadystatechange = Prototype.emptyFunction;
1136: }
1137: },
1138:
1106行目からはrespondToReadyState()メソッドです。
readyStateの各状態に対応して、
まず、
jsonという変数に、
1110行目からはstateが'Complete'の時の処理です。XHRでは、
this._completeフラグをtrueに設定し、
ここでは'on200'などのステータスコードに関連付けられたイベント処理関数がoptionsに登録されていればそれを優先し、
1120行目からは、
- application/
ecmascript - application/
javascript - application/
x-ecmascript - application/
x-javascript - text/
ecmascript - text/
javascript - text/
x-ecmascript - text/
x-javascript
ならevalResponse()メソッドを使ってレスポンスボディをJSONとみなしeval()します。
1126行目からは、
最後に、
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()の返り値をそのまま返します。
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.
1152: evalResponse: function() {
1153: try {
1154: return eval((this.transport.responseText || '').unfilterJSON());
1155: } catch (e) {
1156: this.dispatchException(e);
1157: }
1158: },
1159:
1152行目からはevalResponse()メソッドです。レスポンスボディ
ほぼ似たようなことをしているので、
1160: dispatchException: function(exception) {
1161: (this.options.onException || Prototype.emptyFunction)(this, exception);
1162: Ajax.Responders.dispatch('onException', this, exception);
1163: }
1164: });
1165:
Ajax.
Ajax.Updaterクラス
1166: Ajax.Updater = Class.create();
1167:
1166行目からはAjax.
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.
1169行目からのコンストラクタでは、
1175行目、
1178行目からは、
まず、
options.
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.
まずは、
1191行目では、
1193行目で、
options.
options.
1200行目からは、
Ajax.PeriodicalUpdaterクラス
1207: Ajax.PeriodicalUpdater = Class.create();
1208: Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
1209: initialize: function(container, url, options) {
1210: this.setOptions(options);
1211: this.onComplete = this.options.onComplete;
1212:
1213: this.frequency = (this.options.frequency || 2);
1214: this.decay = (this.options.decay || 1);
1215:
1216: this.updater = {};
1217: this.container = container;
1218: this.url = url;
1219:
1220: this.start();
1221: },
1222:
1207行目からはAjax.
例のごとくClass.
コンストラクタではsetOptions()を使ってthis.
最後に周期的なタイマーを開始するためにthis.
1223: start: function() {
1224: this.options.onComplete = this.updateComplete.bind(this);
1225: this.onTimerEvent();
1226: },
1227:
まず、
その設定が終わると、
1228: stop: function() {
1229: this.updater.options.onComplete = undefined;
1230: clearTimeout(this.timer);
1231: (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
1232: },
1233:
1228行目からはstop()メソッドです。定期的に行われている更新処理を中断します。
もしAjax.
最後に保存しておいたthis.
1234: updateComplete: function(request) {
1235: if (this.options.decay) {
1236: this.decay = (request.responseText == this.lastText ?
1237: this.decay * this.options.decay : 1);
1238:
1239: this.lastText = request.responseText;
1240: }
1241: this.timer = setTimeout(this.onTimerEvent.bind(this),
1242: this.decay * this.frequency * 1000);
1243: },
1244:
1234行目からはupdateComplete()メソッドです。これはstart()経由でonTimerEvent()でnew Ajax.
1235行目からはoptions.
そして1241行目で次のタイマーをセットしています。タイマー終了後に呼び出されるのはonTimerEventで、
1245: onTimerEvent: function() {
1246: this.updater = new Ajax.Updater(this.container, this.url, this.options);
1247: }
1248: });
最後はonTimerEvent()メソッドです。
初回、
1249: function $(element) {
1250: if (arguments.length > 1) {
1251: for (var i = 0, elements = [], length = arguments.length; i < length; i++)
1252: elements.push($(arguments[i]));
1253: return elements;
1254: }
1255: if (typeof element == 'string')
1256: element = document.getElementById(element);
1257: return Element.extend(element);
1258: }
1259:
1249行目からは、
まず、
1255行目では、
最後に、
1260: if (Prototype.BrowserFeatures.XPath) {
1261: document._getElementsByXPath = function(expression, parentElement) {
1262: var results = [];
1263: var query = document.evaluate(expression, $(parentElement) || document,
1264: null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
1265: for (var i = 0, length = query.snapshotLength; i < length; i++)
1266: results.push(query.snapshotItem(i));
1267: return results;
1268: };
1269:
1270: document.getElementsByClassName = function(className, parentElement) {
1271: var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]";
1272: return document._getElementsByXPath(q, parentElement);
1273: }
1274:
1275: } else document.getElementsByClassName = function(className, parentElement) {
1276: var children = ($(parentElement) || document.body).getElementsByTagName('*');
1277: var elements = [], child, pattern = new RegExp("(^|\\s)" + className + "(\\s|$)");
1278: for (var i = 0, length = children.length; i < length; i++) {
1279: child = children[i];
1280: var elementClassName = child.className;
1281: if (elementClassName.length == 0) continue;
1282: if (elementClassName == className || elementClassName.match(pattern))
1283: elements.push(Element.extend(child));
1284: }
1285: return elements;
1286: };
1287:
1288: /*--------------------------------------------------------------------------*/
1289:
1260行目からはdocument.
ブラウザ側でXPathが使えるかどうかによって実装が変わっています。
1261行目からはXPathが使える場合です。まずは、
evaluate()はXPathResult型を返します。snapshotLengthプロパティと、
1270行目からdocument.
ここで、
式 | 意味 |
---|---|
.//* | コンテキストノード |
[contains( foo, bar )] | ……のうちfooがbarを含んでいるもの |
concat(' ', @class, ' ') | 渡された文字列を連結して返す。@classはその要素のclass 属性 |
' ' + className + ' ' | 変数classNameの前後に空白を加えたもの |
という形になっています。これにより
1275行目からは、
まず、
返り値を入れておくためのelements変数と、
あとはchildren中の全ての要素をfor文でループし、
ここで、
この非XPath版のgetElementsByClassName()は、