6 description: Powerful all purpose Request Class. Uses XMLHTTPRequest.
8 license: MIT-style license.
10 requires: [Object, Element, Chain, Events, Options, Browser]
19 var empty = function(){},
20 progressSupport = ('onprogress' in new Browser.Request);
22 var Request = this.Request = new Class({
24 Implements: [Chain, Events, Options],
27 onRequest: function(){},
28 onLoadstart: function(event, xhr){},
29 onProgress: function(event, xhr){},
30 onComplete: function(){},
31 onCancel: function(){},
32 onSuccess: function(responseText, responseXML){},
33 onFailure: function(xhr){},
34 onException: function(headerName, value){},
35 onTimeout: function(){},
41 'X-Requested-With': 'XMLHttpRequest',
42 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
58 initialize: function(options){
59 this.xhr = new Browser.Request();
60 this.setOptions(options);
61 this.headers = this.options.headers;
64 onStateChange: function(){
66 if (xhr.readyState != 4 || !this.running) return;
69 Function.attempt(function(){
70 var status = xhr.status;
71 this.status = (status == 1223) ? 204 : status;
73 xhr.onreadystatechange = empty;
74 if (progressSupport) xhr.onprogress = xhr.onloadstart = empty;
75 clearTimeout(this.timer);
77 this.response = {text: this.xhr.responseText || '', xml: this.xhr.responseXML};
78 if (this.options.isSuccess.call(this, this.status))
79 this.success(this.response.text, this.response.xml);
84 isSuccess: function(){
85 var status = this.status;
86 return (status >= 200 && status < 300);
89 isRunning: function(){
90 return !!this.running;
93 processScripts: function(text){
94 if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return Browser.exec(text);
95 return text.stripScripts(this.options.evalScripts);
98 success: function(text, xml){
99 this.onSuccess(this.processScripts(text), xml);
102 onSuccess: function(){
103 this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain();
110 onFailure: function(){
111 this.fireEvent('complete').fireEvent('failure', this.xhr);
114 loadstart: function(event){
115 this.fireEvent('loadstart', [event, this.xhr]);
118 progress: function(event){
119 this.fireEvent('progress', [event, this.xhr]);
123 this.fireEvent('timeout', this.xhr);
126 setHeader: function(name, value){
127 this.headers[name] = value;
131 getHeader: function(name){
132 return Function.attempt(function(){
133 return this.xhr.getResponseHeader(name);
138 if (!this.running) return true;
139 switch (this.options.link){
140 case 'cancel': this.cancel(); return true;
141 case 'chain': this.chain(this.caller.pass(arguments, this)); return false;
146 send: function(options){
147 if (!this.check(options)) return this;
149 this.options.isSuccess = this.options.isSuccess || this.isSuccess;
152 var type = typeOf(options);
153 if (type == 'string' || type == 'element') options = {data: options};
155 var old = this.options;
156 options = Object.append({data: old.data, url: old.url, method: old.method}, options);
157 var data = options.data, url = String(options.url), method = options.method.toLowerCase();
159 switch (typeOf(data)){
160 case 'element': data = document.id(data).toQueryString(); break;
161 case 'object': case 'hash': data = Object.toQueryString(data);
164 if (this.options.format){
165 var format = 'format=' + this.options.format;
166 data = (data) ? format + '&' + data : format;
169 if (this.options.emulation && !['get', 'post'].contains(method)){
170 var _method = '_method=' + method;
171 data = (data) ? _method + '&' + data : _method;
175 if (this.options.urlEncoded && ['post', 'put'].contains(method)){
176 var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : '';
177 this.headers['Content-type'] = 'application/x-www-form-urlencoded' + encoding;
180 if (!url) url = document.location.pathname;
182 var trimPosition = url.lastIndexOf('/');
183 if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition);
185 if (this.options.noCache)
186 url += (url.indexOf('?') > -1 ? '&' : '?') + String.uniqueID();
188 if (data && (method == 'get' || method == 'delete')){
189 url += (url.indexOf('?') > -1 ? '&' : '?') + data;
194 if (progressSupport){
195 xhr.onloadstart = this.loadstart.bind(this);
196 xhr.onprogress = this.progress.bind(this);
199 xhr.open(method.toUpperCase(), url, this.options.async, this.options.user, this.options.password);
200 if (this.options.user && 'withCredentials' in xhr) xhr.withCredentials = true;
202 xhr.onreadystatechange = this.onStateChange.bind(this);
204 Object.each(this.headers, function(value, key){
206 xhr.setRequestHeader(key, value);
208 this.fireEvent('exception', [key, value]);
212 this.fireEvent('request');
214 if (!this.options.async) this.onStateChange();
215 else if (this.options.timeout) this.timer = this.timeout.delay(this.options.timeout, this);
220 if (!this.running) return this;
221 this.running = false;
224 clearTimeout(this.timer);
225 xhr.onreadystatechange = empty;
226 if (progressSupport) xhr.onprogress = xhr.onloadstart = empty;
227 this.xhr = new Browser.Request();
228 this.fireEvent('cancel');
235 ['get', 'post', 'put', 'delete', 'GET', 'POST', 'PUT', 'DELETE'].each(function(method){
236 methods[method] = function(data){
240 if (data != null) object.data = data;
241 return this.send(object);
245 Request.implement(methods);
247 Element.Properties.send = {
249 set: function(options){
250 var send = this.get('send').cancel();
251 send.setOptions(options);
256 var send = this.retrieve('send');
259 data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action')
261 this.store('send', send);
271 var sender = this.get('send');
272 sender.send({data: this, url: url || sender.options.url});