Merge pull request #2741 from timwienk/gruntfile-restructuring
[mootools.git] / Source / Request / Request.js
blobcb27a05587e654c06ac0f1f3b594b73f200cf673
1 /*
2 ---
4 name: Request
6 description: Powerful all purpose Request Class. Uses XMLHTTPRequest.
8 license: MIT-style license.
10 requires: [Object, Element, Chain, Events, Options, Browser]
12 provides: Request
14 ...
17 (function(){
19 var empty = function(){},
20         progressSupport = ('onprogress' in new Browser.Request);
22 var Request = this.Request = new Class({
24         Implements: [Chain, Events, Options],
26         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(){},
36                 user: '',
37                 password: '',
38                 withCredentials: false,*/
39                 url: '',
40                 data: '',
41                 headers: {
42                         'X-Requested-With': 'XMLHttpRequest',
43                         'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
44                 },
45                 async: true,
46                 format: false,
47                 method: 'post',
48                 link: 'ignore',
49                 isSuccess: null,
50                 emulation: true,
51                 urlEncoded: true,
52                 encoding: 'utf-8',
53                 evalScripts: false,
54                 evalResponse: false,
55                 timeout: 0,
56                 noCache: false
57         },
59         initialize: function(options){
60                 this.xhr = new Browser.Request();
61                 this.setOptions(options);
62                 this.headers = this.options.headers;
63         },
65         onStateChange: function(){
66                 var xhr = this.xhr;
67                 if (xhr.readyState != 4 || !this.running) return;
68                 this.running = false;
69                 this.status = 0;
70                 Function.attempt(function(){
71                         var status = xhr.status;
72                         this.status = (status == 1223) ? 204 : status;
73                 }.bind(this));
74                 xhr.onreadystatechange = empty;
75                 if (progressSupport) xhr.onprogress = xhr.onloadstart = empty;
76                 if (this.timer){
77                         clearTimeout(this.timer);
78                         delete this.timer;
79                 }
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);
84                 else
85                         this.failure();
86         },
88         isSuccess: function(){
89                 var status = this.status;
90                 return (status >= 200 && status < 300);
91         },
93         isRunning: function(){
94                 return !!this.running;
95         },
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);
100         },
102         success: function(text, xml){
103                 this.onSuccess(this.processScripts(text), xml);
104         },
106         onSuccess: function(){
107                 this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain();
108         },
110         failure: function(){
111                 this.onFailure();
112         },
114         onFailure: function(){
115                 this.fireEvent('complete').fireEvent('failure', this.xhr);
116         },
118         loadstart: function(event){
119                 this.fireEvent('loadstart', [event, this.xhr]);
120         },
122         progress: function(event){
123                 this.fireEvent('progress', [event, this.xhr]);
124         },
126         timeout: function(){
127                 this.fireEvent('timeout', this.xhr);
128         },
130         setHeader: function(name, value){
131                 this.headers[name] = value;
132                 return this;
133         },
135         getHeader: function(name){
136                 return Function.attempt(function(){
137                         return this.xhr.getResponseHeader(name);
138                 }.bind(this));
139         },
141         check: function(){
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;
146                 }
147                 return false;
148         },
150         send: function(options){
151                 if (!this.check(options)) return this;
153                 this.options.isSuccess = this.options.isSuccess || this.isSuccess;
154                 this.running = true;
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);
166                 }
168                 if (this.options.format){
169                         var format = 'format=' + this.options.format;
170                         data = (data) ? format + '&' + data : format;
171                 }
173                 if (this.options.emulation && !['get', 'post'].contains(method)){
174                         var _method = '_method=' + method;
175                         data = (data) ? _method + '&' + data : _method;
176                         method = 'post';
177                 }
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;
182                 }
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;
194                         data = null;
195                 }
197                 var xhr = this.xhr;
198                 if (progressSupport){
199                         xhr.onloadstart = this.loadstart.bind(this);
200                         xhr.onprogress = this.progress.bind(this);
201                 }
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){
209                         try {
210                                 xhr.setRequestHeader(key, value);
211                         } catch (e){
212                                 this.fireEvent('exception', [key, value]);
213                         }
214                 }, this);
216                 this.fireEvent('request');
217                 xhr.send(data);
218                 if (!this.options.async) this.onStateChange();
219                 else if (this.options.timeout) this.timer = this.timeout.delay(this.options.timeout, this);
220                 return this;
221         },
223         cancel: function(){
224                 if (!this.running) return this;
225                 this.running = false;
226                 var xhr = this.xhr;
227                 xhr.abort();
228                 if (this.timer){
229                         clearTimeout(this.timer);
230                         delete this.timer;
231                 }
232                 xhr.onreadystatechange = empty;
233                 if (progressSupport) xhr.onprogress = xhr.onloadstart = empty;
234                 this.xhr = new Browser.Request();
235                 this.fireEvent('cancel');
236                 return this;
237         }
241 var methods = {};
242 ['get', 'post', 'put', 'delete', 'patch', 'head', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD'].each(function(method){
243         methods[method] = function(data){
244                 var object = {
245                         method: method
246                 };
247                 if (data != null) object.data = data;
248                 return this.send(object);
249         };
252 Request.implement(methods);
254 Element.Properties.send = {
256         set: function(options){
257                 var send = this.get('send').cancel();
258                 send.setOptions(options);
259                 return this;
260         },
262         get: function(){
263                 var send = this.retrieve('send');
264                 if (!send){
265                         send = new Request({
266                                 data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action')
267                         });
268                         this.store('send', send);
269                 }
270                 return send;
271         }
275 Element.implement({
277         send: function(url){
278                 var sender = this.get('send');
279                 sender.send({data: this, url: url || sender.options.url});
280                 return this;
281         }
285 })();