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(){},
38 withCredentials: false,*/
42 'X-Requested-With': 'XMLHttpRequest',
43 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
59 initialize: function(options){
60 this.xhr = new Browser.Request();
61 this.setOptions(options);
62 this.headers = this.options.headers;
65 onStateChange: function(){
67 if (xhr.readyState != 4 || !this.running) return;
70 Function.attempt(function(){
71 var status = xhr.status;
72 this.status = (status == 1223) ? 204 : status;
74 xhr.onreadystatechange = empty;
75 if (progressSupport) xhr.onprogress = xhr.onloadstart = empty;
77 clearTimeout(this.timer);
81 this.response = {text: this.xhr.responseText || '', xml: this.xhr.responseXML};
82 if (this.options.isSuccess.call(this, this.status))
83 this.success(this.response.text, this.response.xml);
88 isSuccess: function(){
89 var status = this.status;
90 return (status >= 200 && status < 300);
93 isRunning: function(){
94 return !!this.running;
97 processScripts: function(text){
98 if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return Browser.exec(text);
99 return text.stripScripts(this.options.evalScripts);
102 success: function(text, xml){
103 this.onSuccess(this.processScripts(text), xml);
106 onSuccess: function(){
107 this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain();
114 onFailure: function(){
115 this.fireEvent('complete').fireEvent('failure', this.xhr);
118 loadstart: function(event){
119 this.fireEvent('loadstart', [event, this.xhr]);
122 progress: function(event){
123 this.fireEvent('progress', [event, this.xhr]);
127 this.fireEvent('timeout', this.xhr);
130 setHeader: function(name, value){
131 this.headers[name] = value;
135 getHeader: function(name){
136 return Function.attempt(function(){
137 return this.xhr.getResponseHeader(name);
142 if (!this.running) return true;
143 switch (this.options.link){
144 case 'cancel': this.cancel(); return true;
145 case 'chain': this.chain(this.caller.pass(arguments, this)); return false;
150 send: function(options){
151 if (!this.check(options)) return this;
153 this.options.isSuccess = this.options.isSuccess || this.isSuccess;
156 var type = typeOf(options);
157 if (type == 'string' || type == 'element') options = {data: options};
159 var old = this.options;
160 options = Object.append({data: old.data, url: old.url, method: old.method}, options);
161 var data = options.data, url = String(options.url), method = options.method.toLowerCase();
163 switch (typeOf(data)){
164 case 'element': data = document.id(data).toQueryString(); break;
165 case 'object': case 'hash': data = Object.toQueryString(data);
168 if (this.options.format){
169 var format = 'format=' + this.options.format;
170 data = (data) ? format + '&' + data : format;
173 if (this.options.emulation && !['get', 'post'].contains(method)){
174 var _method = '_method=' + method;
175 data = (data) ? _method + '&' + data : _method;
179 if (this.options.urlEncoded && ['post', 'put'].contains(method)){
180 var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : '';
181 this.headers['Content-type'] = 'application/x-www-form-urlencoded' + encoding;
184 if (!url) url = document.location.pathname;
186 var trimPosition = url.lastIndexOf('/');
187 if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition);
189 if (this.options.noCache)
190 url += (url.indexOf('?') > -1 ? '&' : '?') + String.uniqueID();
192 if (data && (method == 'get' || method == 'delete')){
193 url += (url.indexOf('?') > -1 ? '&' : '?') + data;
198 if (progressSupport){
199 xhr.onloadstart = this.loadstart.bind(this);
200 xhr.onprogress = this.progress.bind(this);
203 xhr.open(method.toUpperCase(), url, this.options.async, this.options.user, this.options.password);
204 if ((/*<1.4compat>*/this.options.user || /*</1.4compat>*/this.options.withCredentials) && 'withCredentials' in xhr) xhr.withCredentials = true;
206 xhr.onreadystatechange = this.onStateChange.bind(this);
208 Object.each(this.headers, function(value, key){
210 xhr.setRequestHeader(key, value);
212 this.fireEvent('exception', [key, value]);
216 this.fireEvent('request');
218 if (!this.options.async) this.onStateChange();
219 else if (this.options.timeout) this.timer = this.timeout.delay(this.options.timeout, this);
224 if (!this.running) return this;
225 this.running = false;
229 clearTimeout(this.timer);
232 xhr.onreadystatechange = empty;
233 if (progressSupport) xhr.onprogress = xhr.onloadstart = empty;
234 this.xhr = new Browser.Request();
235 this.fireEvent('cancel');
242 ['get', 'post', 'put', 'delete', 'patch', 'head', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD'].each(function(method){
243 methods[method] = function(data){
247 if (data != null) object.data = data;
248 return this.send(object);
252 Request.implement(methods);
254 Element.Properties.send = {
256 set: function(options){
257 var send = this.get('send').cancel();
258 send.setOptions(options);
263 var send = this.retrieve('send');
266 data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action')
268 this.store('send', send);
278 var sender = this.get('send');
279 sender.send({data: this, url: url || sender.options.url});