1 /**
2 * Ajax类,getTransport类方法返回一个xmlhttp对象,注意这里就用到了Try.these
3 */
4 var Ajax = {
5 getTransport: function() {
6 return Try.these(
7 function() {
8 return new XMLHttpRequest()
9 },
10 function() {
11 return new ActiveXObject('Msxml2.XMLHTTP')
12 },
13 function() {
14 return new ActiveXObject('Microsoft.XMLHTTP')
15 }
16 ) || false;
17 },
18
19 activeRequestCount: 0
20 }
21
22 Ajax.Responders = {
23 responders: [],
24
25 _each: function(iterator) {
26 this.responders._each(iterator);
27 },
28
29 register: function(responderToAdd) {
30 if (!this.include(responderToAdd))
31 this.responders.push(responderToAdd);
32 },
33
34 unregister: function(responderToRemove) {
35 this.responders = this.responders.without(responderToRemove);
36 },
37
38 dispatch: function(callback, request, transport, json) {
39 this.each(function(responder) {
40 if (responder[callback] && typeof responder[callback] == 'function') {
41 try {
42 responder[callback].apply(responder, [request, transport, json]);
43 } catch (e) {
44 }
45 }
46 });
47 }
48 };
49
50 Object.extend(Ajax.Responders, Enumerable);
51
52 Ajax.Responders.register({
53 onCreate: function() {
54 Ajax.activeRequestCount++;
55 },
56
57 onComplete: function() {
58 Ajax.activeRequestCount--;
59 }
60 });
61 /**
62 * Ajax.Base 声明为一个基础对象类型,这里没有使用Class.create()方法,
63 * 因为Ajax.Base不需要实例化,只是作为基类,让子类来继承。
64 */
65 Ajax.Base = function() {
66 };
67 /**
68 * Ajax.Base提供了一些Ajax.Request Ajax.PeriodicalUpdater共享的方法
69 * 这些方法被放在一个基类中,避免在Ajax.Request和Ajax.PeriodicalUpdater都实现。他们继承Ajax.Base
70 * The Ajax Object的options决定了怎样的Ajax Request执行,在Ajax Request初始化时,
71 * options首先设置默认属性,然后再extend参数对象,参数对象中有同名的属性,就覆盖默认的属性值
72 * 有一些属性是Ajax请求必须的,所以在Ajax.base对象中设置了默认值
73 *
74 */
75 Ajax.Base.prototype = {
76 setOptions: function(options) {
77 this.options = {
78 method: 'post',
79 asynchronous: true,
80 contentType: 'application/x-www-form-urlencoded',
81 parameters: ''
82 }
83 Object.extend(this.options, options || {});
84 },
85 //下面两个方法判断是否正常返回了响应
86 responseIsSuccess: function() {
87 return this.transport.status == undefined
88 || this.transport.status == 0
89 || (this.transport.status >= 200 && this.transport.status < 300);
90 },
91
92 responseIsFailure: function() {
93 return !this.responseIsSuccess();
94 }
95 }
96 //Request对象,这回用了Class.create(),因为下面initialize初始化
97 Ajax.Request = Class.create();
98
99 //Events对象,对应XMLHttpRequest中的请求状态
100 //Uninitialized: 未初始化;Loading: 正在加载 ;Loaded:已加载;Interactive:交互中;Complete:完成
101
102 Ajax.Request.Events =
103 ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
104
105 /**
106 * Ajax.Request继承了Ajax.Base,并且添加了一个构造方法(initialize)
107 * initialize方法是必须的,因为Ajax请求属性继承Ajax.Base的后,至少还需要一个请求URL,才能工作。
108 * 有趣的是,初始化方法中调用了一个request的方法,所以请求被立刻执行,当Ajax.Request实例被初始化时。
109 */
110 Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
111 initialize: function(url, options) {
112 this.transport = Ajax.getTransport();
113 this.setOptions(options);//设置请求的的属性,调用Ajax.Base方法
114 this.request(url);
115 },
116
117 /**
118 * 在Prototype中,一个Ajax.Request是一个到服务器的一个异步的请求,一个没有处理的response返回给调用者。
119 */
120 request: function(url) {
121 //从Ajax.Base’s options对象中,获得请求参数字符串,如果this.options.parameters为空,请求参数被设空。
122 //如果参数不为空,在请求参数后添加"&_=",应该是确保url的唯一性,避免浏览器缓存请求的结果。
123 var parameters = this.options.parameters || '';
124 if (parameters.length > 0) parameters += '&_=';
125
126 /* Simulate other verbs over post */
127 if (this.options.method != 'get' && this.options.method != 'post') {
128 parameters += (parameters.length > 0 ? '&' : '') + '_method=' + this.options.method;
129 this.options.method = 'post';
130 }
131 //下面请求将被发送,为了捕捉网络的异常,相关的代码被放到try块中
132 try {
133 this.url = url;
134 //如果请求方法是get,参数将被连接到URL后
135 if (this.options.method == 'get' && parameters.length > 0)
136 this.url += (this.url.match(/\?/) ? '&' : '?') + parameters;
137
138 //onCreate事件被发给Responders对象,
139 //请求开始 ,Ajax.Responders中注册onCreate的方法就会执行
140 Ajax.Responders.dispatch('onCreate', this, this.transport);
141
142 //XMLHttpRequest.open()方法,建立对服务器的调用。
143 this.transport.open(this.options.method, this.url,
144 this.options.asynchronous);
145
146 //这里提供了XMLHttpRequest传输过程中每个步骤的回调函数
147 //每10ms会调用一次这个方法,传的参数是Loading
148 if (this.options.asynchronous)
149 setTimeout(function() {
150 this.respondToReadyState(1)
151 }.bind(this), 10);
152
153 //XMLHttpRequest状态改变时,onStateChange方法调用。
154 this.transport.onreadystatechange = this.onStateChange.bind(this);
155
156 //设置request头
157 this.setRequestHeaders();
158
159 //如果方法是post,参数被作为POST headers 被send()方法发送出去,否则,什么也不发送。
160 var body = this.options.postBody ? this.options.postBody : parameters;
161 this.transport.send(this.options.method == 'post' ? body : null);
162
163 /* Force Firefox to handle ready state 4 for synchronous requests */
164 if (!this.options.asynchronous && this.transport.overrideMimeType)
165 this.onStateChange();
166
167 } catch (e) {
168
169 this.dispatchException(e);
170 }
171 },
172
173 //设置request头
174 setRequestHeaders: function() {
175 //默认的请求头被建立,requestHeaders是个数组
176 var requestHeaders =
177 ['X-Requested-With', 'XMLHttpRequest',
178 'X-Prototype-Version', Prototype.Version,
179 'Accept', 'text/javascript, text/html, application/xml, text/xml, */*'];
180 //如果请求用post方法,Content-type将被添加到请求头中
181 if (this.options.method == 'post') {
182 requestHeaders.push('Content-type', this.options.contentType);
183
184 /* Force "Connection: close" for Mozilla browsers to work around
185 * a bug where XMLHttpReqeuest sends an incorrect Content-length
186 * header. See Mozilla Bugzilla #246651.
187 */
188 if (this.transport.overrideMimeType)
189 requestHeaders.push('Connection', 'close');
190 }
191 //如果在request的options中有requestHeaders属性
192 //它们将添加到requestHeaders
193 if (this.options.requestHeaders)
194 requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);
195
196 //requestHeaders中每一个key-value对,通过XMLhttpRequest的setRequestHeader方法设置请求实例的请求头属性中
197
198 for (var i = 0; i < requestHeaders.length; i += 2)
199 this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i + 1]);
200 },
201 /**
202 * 状态变化,并且readyState不是Loading时就调用回调函数。
203 */
204 onStateChange: function() {
205 var readyState = this.transport.readyState;
206 if (readyState != 1)
207 this.respondToReadyState(this.transport.readyState);
208 },
209
210 //根据name取得XMLHttpRequest中指定的HTTP头
211 header: function(name) {
212 try {
213 return this.transport.getResponseHeader(name);
214 } catch (e) {
215 }
216 },
217 //执行HTTP头中'X-JSON'对应的内容
218 evalJSON: function() {
219 try {
220 return eval('(' + this.header('X-JSON') + ')');
221 } catch (e) {
222 }
223 },
224
225 /**
226 * 执行包含在response text中的脚本,并返回结果
227 */
228 evalResponse: function() {
229 try {
230 return eval(this.transport.responseText);
231 } catch (e) {
232 this.dispatchException(e);
233 }
234 },
235
236 respondToReadyState: function(readyState) {
237 //readyState将被映射到Events定义得事件
238 var event = Ajax.Request.Events[readyState];
239 //获取当前的XMLHttpRequest对象,执行HTTP头中'X-JSON'对应的内容
240 var transport = this.transport, json = this.evalJSON();
241 //如果event == 'Complete',response响应被接受。
242 if (event == 'Complete') {
243 try {//按优先级触发事件 如果处理函数不存在则执行emptyFunction忽略此事件
244 (this.options['on' + this.transport.status]
245 || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
246 || Prototype.emptyFunction)(transport, json);
247 } catch (e) {
248 this.dispatchException(e);
249 }
250 //如果响应的Content-type是 text/javascript,prototype将用evalResponse方法执行包含在请求的js代码
251 if ((this.header('Content-type') || '').match(/^text\/javascript/i))
252 this.evalResponse();
253 }
254 //XMLHttpRequest状态的变化,onLoaded, onInteractive, onComplete方法调用并传json对象
255 //同时事件还发送给Responders对象
256 try {
257 (this.options['on' + event] || Prototype.emptyFunction)(transport, json);
258 Ajax.Responders.dispatch('on' + event, this, transport, json);
259 } catch (e) {
260 this.dispatchException(e);
261 }
262
263 /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
264 if (event == 'Complete')
265 this.transport.onreadystatechange = Prototype.emptyFunction;
266 },
267
268 dispatchException: function(exception) {// 处理异常,Prototype.emptyFunction为默认的处理方法
269 (this.options.onException || Prototype.emptyFunction)(this, exception);
270 Ajax.Responders.dispatch('onException', this, exception);
271 }
272 });
273
274 /**
275 * Ajax.Updater继承Ajax.Request,是一格Ajax.Request功能增强类。它执行和Ajax.Request同样的任务,
276 * 但是response不仅可以传递个onComplete处理,还可以响应后的HTML结果自动放到指定的container
277 */
278 Ajax.Updater = Class.create();
279
280 Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
281 //初始化方法比Ajax.Request多了一个container参数,response
282 //将被放置在container中
283 initialize: function(container, url, options) {
284 //success:请求响应成功时,接收response的容器对象
285 this.containers = {
286 success: container.success ? $(container.success) : $(container),
287 failure: container.failure ? $(container.failure) :
288 (container.success ? null : $(container))
289 }
290
291 this.transport = Ajax.getTransport();
292 this.setOptions(options);
293
294 var onComplete = this.options.onComplete || Prototype.emptyFunction;
295
296 //onComplete扩展包含一个对updateContent()方法的调用
297 this.options.onComplete = (function(transport, object) {
298 this.updateContent();
299 onComplete(transport, object);
300 }).bind(this);
301
302 this.request(url);
303 },
304
305 updateContent: function() {
306 //如果AJAX请求响应成功,则获取接收的容器对象,否则获取请求错误时的容器对象。
307 var receiver = this.responseIsSuccess() ?
308 this.containers.success : this.containers.failure;
309 var response = this.transport.responseText;
310
311 //如果this.options.evalScripts为false,从response移出所有的JavaScript脚本
312 if (!this.options.evalScripts)
313 response = response.stripScripts();
314
315 //如果接收对象存在
316 if (receiver) {
317 //判断是将响应文本insertion还是update到receiver对象中
318 if (this.options.insertion) {
319 new this.options.insertion(receiver, response);
320 } else {
321 Element.update(receiver, response);
322 }
323 }
324
325 if (this.responseIsSuccess()) {
326 if (this.onComplete)
327 setTimeout(this.onComplete.bind(this), 10);
328 }
329 }
330 });
331
332 /**
333 * Ajax.PeriodicalUpdater对Ajax.Updater进行包装。对Ajax.Updater添加了一个定时器。
334 * 周期性的执行Ajax.Updater对象来更新DOM element
335 */
336 Ajax.PeriodicalUpdater = Class.create();
337 Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
338 initialize: function(container, url, options) {
339 this.setOptions(options);
340 this.onComplete = this.options.onComplete;
341
342 this.frequency = (this.options.frequency || 2);//两次刷新之间的间隔
343 this.decay = (this.options.decay || 1);//重负执行任务的时候保持的衰败水平
344
345 this.updater = {};
346 this.container = container;
347 this.url = url;
348
349 this.start();//开始Ajax.Updater循环
350 },
351
352 start: function() {
353 //使options的onComplete的指针指向当前的Ajax.PeriodicalUpdater对的updateComplete方法。
354 this.options.onComplete = this.updateComplete.bind(this);
355 this.onTimerEvent();
356 },
357
358 stop: function() {
359 this.updater.options.onComplete = undefined;
360 clearTimeout(this.timer);
361 (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
362 },
363
364 updateComplete: function(request) {
365 if (this.options.decay) {
366 this.decay = (request.responseText == this.lastText ?
367 this.decay * this.options.decay : 1);
368
369 this.lastText = request.responseText;
370 }
371 // 返回一个标志(timer)留作被stop函数调用, 以停止定时器
372 this.timer = setTimeout(this.onTimerEvent.bind(this),
373 this.decay * this.frequency * 1000);
374 },
375
376 onTimerEvent: function() {
377 this.updater = new Ajax.Updater(this.container, this.url, this.options);
378 }
379 });