3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
16 * @class Ext.data.Connection
17 * The Connection class encapsulates a connection to the page's originating domain, allowing requests to be made either
18 * to a configured URL, or to a URL specified at request time.
20 * Requests made by this class are asynchronous, and will return immediately. No data from the server will be available
21 * to the statement immediately following the {@link #request} call. To process returned data, use a success callback
22 * in the request options object, or an {@link #requestcomplete event listener}.
24 * <p><u>File Uploads</u></p>
26 * File uploads are not performed using normal "Ajax" techniques, that is they are not performed using XMLHttpRequests.
27 * Instead the form is submitted in the standard manner with the DOM <form> element temporarily modified to have its
28 * target set to refer to a dynamically generated, hidden <iframe> which is inserted into the document but removed
29 * after the return data has been gathered.
31 * The server response is parsed by the browser to create the document for the IFRAME. If the server is using JSON to
32 * send the return object, then the Content-Type header must be set to "text/html" in order to tell the browser to
33 * insert the text unchanged into the document body.
35 * Characters which are significant to an HTML parser must be sent as HTML entities, so encode "<" as "&lt;", "&" as
38 * The response text is retrieved from the document, and a fake XMLHttpRequest object is created containing a
39 * responseText property in order to conform to the requirements of event handlers and callbacks.
41 * Be aware that file upload packets are sent with the content type multipart/form and some server technologies
42 * (notably JEE) may require some custom processing in order to retrieve parameter names and parameter values from the
45 * Also note that it's not possible to check the response code of the hidden iframe, so the success handler will ALWAYS fire.
47 Ext.define('Ext.data.Connection', {
49 observable: 'Ext.util.Observable'
63 * @cfg {Boolean} disableCaching (Optional) True to add a unique cache-buster param to GET requests. (defaults to true)
68 * @cfg {String} disableCachingParam (Optional) Change the parameter which is sent went disabling caching
69 * through a cache buster. Defaults to '_dc'
71 disableCachingParam: '_dc',
74 * @cfg {Number} timeout (Optional) The timeout in milliseconds to be used for requests. (defaults to 30000)
79 * @cfg {Object} extraParams (Optional) Any parameters to be appended to the request.
82 useDefaultHeader : true,
83 defaultPostHeader : 'application/x-www-form-urlencoded; charset=UTF-8',
84 useDefaultXhrHeader : true,
85 defaultXhrHeader : 'XMLHttpRequest',
87 constructor : function(config) {
88 config = config || {};
89 Ext.apply(this, config);
93 * @event beforerequest
94 * Fires before a network request is made to retrieve a data object.
95 * @param {Connection} conn This Connection object.
96 * @param {Object} options The options config object passed to the {@link #request} method.
100 * @event requestcomplete
101 * Fires if the request was successfully completed.
102 * @param {Connection} conn This Connection object.
103 * @param {Object} response The XHR object containing the response data.
104 * See <a href="http://www.w3.org/TR/XMLHttpRequest/">The XMLHttpRequest Object</a>
106 * @param {Object} options The options config object passed to the {@link #request} method.
110 * @event requestexception
111 * Fires if an error HTTP status was returned from the server.
112 * See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html">HTTP Status Code Definitions</a>
113 * for details of HTTP status codes.
114 * @param {Connection} conn This Connection object.
115 * @param {Object} response The XHR object containing the response data.
116 * See <a href="http://www.w3.org/TR/XMLHttpRequest/">The XMLHttpRequest Object</a>
118 * @param {Object} options The options config object passed to the {@link #request} method.
123 this.mixins.observable.constructor.call(this);
127 * <p>Sends an HTTP request to a remote server.</p>
128 * <p><b>Important:</b> Ajax server requests are asynchronous, and this call will
129 * return before the response has been received. Process any returned data
130 * in a callback function.</p>
133 url: 'ajax_demo/sample.json',
134 success: function(response, opts) {
135 var obj = Ext.decode(response.responseText);
138 failure: function(response, opts) {
139 console.log('server-side failure with status code ' + response.status);
143 * <p>To execute a callback function in the correct scope, use the <tt>scope</tt> option.</p>
144 * @param {Object} options An object which may contain the following properties:<ul>
145 * <li><b>url</b> : String/Function (Optional)<div class="sub-desc">The URL to
146 * which to send the request, or a function to call which returns a URL string. The scope of the
147 * function is specified by the <tt>scope</tt> option. Defaults to the configured
148 * <tt>{@link #url}</tt>.</div></li>
149 * <li><b>params</b> : Object/String/Function (Optional)<div class="sub-desc">
150 * An object containing properties which are used as parameters to the
151 * request, a url encoded string or a function to call to get either. The scope of the function
152 * is specified by the <tt>scope</tt> option.</div></li>
153 * <li><b>method</b> : String (Optional)<div class="sub-desc">The HTTP method to use
154 * for the request. Defaults to the configured method, or if no method was configured,
155 * "GET" if no parameters are being sent, and "POST" if parameters are being sent. Note that
156 * the method name is case-sensitive and should be all caps.</div></li>
157 * <li><b>callback</b> : Function (Optional)<div class="sub-desc">The
158 * function to be called upon receipt of the HTTP response. The callback is
159 * called regardless of success or failure and is passed the following
161 * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
162 * <li><b>success</b> : Boolean<div class="sub-desc">True if the request succeeded.</div></li>
163 * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.
164 * See <a href="http://www.w3.org/TR/XMLHttpRequest/">http://www.w3.org/TR/XMLHttpRequest/</a> for details about
165 * accessing elements of the response.</div></li>
167 * <li><a id="request-option-success"></a><b>success</b> : Function (Optional)<div class="sub-desc">The function
168 * to be called upon success of the request. The callback is passed the following
170 * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.</div></li>
171 * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
173 * <li><b>failure</b> : Function (Optional)<div class="sub-desc">The function
174 * to be called upon failure of the request. The callback is passed the
175 * following parameters:<ul>
176 * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.</div></li>
177 * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
179 * <li><b>scope</b> : Object (Optional)<div class="sub-desc">The scope in
180 * which to execute the callbacks: The "this" object for the callback function. If the <tt>url</tt>, or <tt>params</tt> options were
181 * specified as functions from which to draw values, then this also serves as the scope for those function calls.
182 * Defaults to the browser window.</div></li>
183 * <li><b>timeout</b> : Number (Optional)<div class="sub-desc">The timeout in milliseconds to be used for this request. Defaults to 30 seconds.</div></li>
184 * <li><b>form</b> : Element/HTMLElement/String (Optional)<div class="sub-desc">The <tt><form></tt>
185 * Element or the id of the <tt><form></tt> to pull parameters from.</div></li>
186 * <li><a id="request-option-isUpload"></a><b>isUpload</b> : Boolean (Optional)<div class="sub-desc"><b>Only meaningful when used
187 * with the <tt>form</tt> option</b>.
188 * <p>True if the form object is a file upload (will be set automatically if the form was
189 * configured with <b><tt>enctype</tt></b> "multipart/form-data").</p>
190 * <p>File uploads are not performed using normal "Ajax" techniques, that is they are <b>not</b>
191 * performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the
192 * DOM <tt><form></tt> element temporarily modified to have its
193 * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
194 * to a dynamically generated, hidden <tt><iframe></tt> which is inserted into the document
195 * but removed after the return data has been gathered.</p>
196 * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
197 * server is using JSON to send the return object, then the
198 * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
199 * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
200 * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
201 * is created containing a <tt>responseText</tt> property in order to conform to the
202 * requirements of event handlers and callbacks.</p>
203 * <p>Be aware that file upload packets are sent with the content type <a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form</a>
204 * and some server technologies (notably JEE) may require some custom processing in order to
205 * retrieve parameter names and parameter values from the packet content.</p>
207 * <li><b>headers</b> : Object (Optional)<div class="sub-desc">Request
208 * headers to set for the request.</div></li>
209 * <li><b>xmlData</b> : Object (Optional)<div class="sub-desc">XML document
210 * to use for the post. Note: This will be used instead of params for the post
211 * data. Any params will be appended to the URL.</div></li>
212 * <li><b>jsonData</b> : Object/String (Optional)<div class="sub-desc">JSON
213 * data to use as the post. Note: This will be used instead of params for the post
214 * data. Any params will be appended to the URL.</div></li>
215 * <li><b>disableCaching</b> : Boolean (Optional)<div class="sub-desc">True
216 * to add a unique cache-buster param to GET requests.</div></li>
218 * <p>The options object may also contain any other property which might be needed to perform
219 * postprocessing in a callback because it is passed to callback functions.</p>
220 * @return {Object} request The request object. This may be used
221 * to cancel the request.
223 request : function(options) {
224 options = options || {};
226 scope = options.scope || window,
227 username = options.username || me.username,
228 password = options.password || me.password || '',
235 if (me.fireEvent('beforerequest', me, options) !== false) {
237 requestOptions = me.setOptions(options, scope);
239 if (this.isFormUpload(options) === true) {
240 this.upload(options.form, requestOptions.url, requestOptions.data, options);
244 // if autoabort is set, cancel the current transactions
245 if (options.autoAbort === true || me.autoAbort) {
249 // create a connection object
250 xhr = this.getXhrInstance();
252 async = options.async !== false ? (options.async || me.async) : false;
256 xhr.open(requestOptions.method, requestOptions.url, async, username, password);
258 xhr.open(requestOptions.method, requestOptions.url, async);
261 headers = me.setupHeaders(xhr, options, requestOptions.data, requestOptions.params);
263 // create the transaction object
265 id: ++Ext.data.Connection.requestId,
270 timeout: setTimeout(function() {
271 request.timedout = true;
273 }, options.timeout || me.timeout)
275 me.requests[request.id] = request;
277 // bind our statechange listener
279 xhr.onreadystatechange = Ext.Function.bind(me.onStateChange, me, [request]);
282 // start the request!
283 xhr.send(requestOptions.data);
285 return this.onComplete(request);
289 Ext.callback(options.callback, options.scope, [options, undefined, undefined]);
295 * Upload a form using a hidden iframe.
296 * @param {Mixed} form The form to upload
297 * @param {String} url The url to post to
298 * @param {String} params Any extra parameters to pass
299 * @param {Object} options The initial options
301 upload: function(form, url, params, options){
302 form = Ext.getDom(form);
303 options = options || {};
306 frame = document.createElement('iframe'),
308 encoding = 'multipart/form-data',
312 encoding: form.encoding,
313 enctype: form.enctype,
318 * Originally this behaviour was modified for Opera 10 to apply the secure URL after
319 * the frame had been added to the document. It seems this has since been corrected in
320 * Opera so the behaviour has been reverted, the URL will be set before being added.
325 cls: Ext.baseCSSPrefix + 'hide-display',
326 src: Ext.SSL_SECURE_URL
329 document.body.appendChild(frame);
331 // This is required so that IE doesn't pop the response up in a new window.
332 if (document.frames) {
333 document.frames[id].name = id;
341 action: url || buf.action
344 // add dynamic params
346 Ext.iterate(Ext.Object.fromQueryString(params), function(name, value){
347 hiddenItem = document.createElement('input');
348 Ext.fly(hiddenItem).set({
353 form.appendChild(hiddenItem);
354 hiddens.push(hiddenItem);
358 Ext.fly(frame).on('load', Ext.Function.bind(this.onUploadComplete, this, [frame, options]), null, {single: true});
361 Ext.fly(form).set(buf);
362 Ext.each(hiddens, function(h) {
367 onUploadComplete: function(frame, options){
369 // bogus response object
376 doc = frame.contentWindow.document || frame.contentDocument || window.frames[id].document;
379 if (/textarea/i.test((firstChild = doc.body.firstChild || {}).tagName)) { // json response wrapped in textarea
380 response.responseText = firstChild.value;
382 response.responseText = doc.body.innerHTML;
385 //in IE the document may still have a body even if returns XML.
386 response.responseXML = doc.XMLDocument || doc;
391 me.fireEvent('requestcomplete', me, response, options);
393 Ext.callback(options.success, options.scope, [response, options]);
394 Ext.callback(options.callback, options.scope, [options, true, response]);
396 setTimeout(function(){
397 Ext.removeNode(frame);
402 * Detect whether the form is intended to be used for an upload.
405 isFormUpload: function(options){
406 var form = this.getForm(options);
408 return (options.isUpload || (/multipart\/form-data/i).test(form.getAttribute('enctype')));
414 * Get the form object from options.
416 * @param {Object} options The request options
417 * @return {HTMLElement} The form, null if not passed
419 getForm: function(options){
420 return Ext.getDom(options.form) || null;
424 * Set various options such as the url, params for the request
425 * @param {Object} options The initial options
426 * @param {Object} scope The scope to execute in
427 * @return {Object} The params for the request
429 setOptions: function(options, scope){
431 params = options.params || {},
432 extraParams = me.extraParams,
433 urlParams = options.urlParams,
434 url = options.url || me.url,
435 jsonData = options.jsonData,
441 // allow params to be a method that returns the params object
442 if (Ext.isFunction(params)) {
443 params = params.call(scope, options);
446 // allow url to be a method that returns the actual url
447 if (Ext.isFunction(url)) {
448 url = url.call(scope, options);
451 url = this.setupUrl(options, url);
457 msg: 'No URL specified'
462 // check for xml or json data, and make sure json data is encoded
463 data = options.rawData || options.xmlData || jsonData || null;
464 if (jsonData && !Ext.isPrimitive(jsonData)) {
465 data = Ext.encode(data);
468 // make sure params are a url encoded string and include any extraParams if specified
469 if (Ext.isObject(params)) {
470 params = Ext.Object.toQueryString(params);
473 if (Ext.isObject(extraParams)) {
474 extraParams = Ext.Object.toQueryString(extraParams);
477 params = params + ((extraParams) ? ((params) ? '&' : '') + extraParams : '');
479 urlParams = Ext.isObject(urlParams) ? Ext.Object.toQueryString(urlParams) : urlParams;
481 params = this.setupParams(options, params);
483 // decide the proper method for this request
484 method = (options.method || me.method || ((params || data) ? 'POST' : 'GET')).toUpperCase();
485 this.setupMethod(options, method);
488 disableCache = options.disableCaching !== false ? (options.disableCaching || me.disableCaching) : false;
489 // if the method is get append date to prevent caching
490 if (method === 'GET' && disableCache) {
491 url = Ext.urlAppend(url, (options.disableCachingParam || me.disableCachingParam) + '=' + (new Date().getTime()));
494 // if the method is get or there is json/xml data append the params to the url
495 if ((method == 'GET' || data) && params) {
496 url = Ext.urlAppend(url, params);
500 // allow params to be forced into the url
502 url = Ext.urlAppend(url, urlParams);
508 data: data || params || null
513 * Template method for overriding url
515 * @param {Object} options
516 * @param {String} url
517 * @return {String} The modified url
519 setupUrl: function(options, url){
520 var form = this.getForm(options);
522 url = url || form.action;
529 * Template method for overriding params
531 * @param {Object} options
532 * @param {String} params
533 * @return {String} The modified params
535 setupParams: function(options, params) {
536 var form = this.getForm(options),
538 if (form && !this.isFormUpload(options)) {
539 serializedForm = Ext.core.Element.serializeForm(form);
540 params = params ? (params + '&' + serializedForm) : serializedForm;
546 * Template method for overriding method
548 * @param {Object} options
549 * @param {String} method
550 * @return {String} The modified method
552 setupMethod: function(options, method){
553 if (this.isFormUpload(options)) {
560 * Setup all the headers for the request
562 * @param {Object} xhr The xhr object
563 * @param {Object} options The options for the request
564 * @param {Object} data The data for the request
565 * @param {Object} params The params for the request
567 setupHeaders: function(xhr, options, data, params){
569 headers = Ext.apply({}, options.headers || {}, me.defaultHeaders || {}),
570 contentType = me.defaultPostHeader,
571 jsonData = options.jsonData,
572 xmlData = options.xmlData,
576 if (!headers['Content-Type'] && (data || params)) {
578 if (options.rawData) {
579 contentType = 'text/plain';
581 if (xmlData && Ext.isDefined(xmlData)) {
582 contentType = 'text/xml';
583 } else if (jsonData && Ext.isDefined(jsonData)) {
584 contentType = 'application/json';
588 headers['Content-Type'] = contentType;
591 if (me.useDefaultXhrHeader && !headers['X-Requested-With']) {
592 headers['X-Requested-With'] = me.defaultXhrHeader;
594 // set up all the request headers on the xhr object
596 for (key in headers) {
597 if (headers.hasOwnProperty(key)) {
598 header = headers[key];
599 xhr.setRequestHeader(key, header);
604 me.fireEvent('exception', key, header);
610 * Creates the appropriate XHR transport for the browser.
613 getXhrInstance: (function(){
614 var options = [function(){
615 return new XMLHttpRequest();
617 return new ActiveXObject('MSXML2.XMLHTTP.3.0');
619 return new ActiveXObject('MSXML2.XMLHTTP');
621 return new ActiveXObject('Microsoft.XMLHTTP');
623 len = options.length,
626 for(; i < len; ++i) {
637 * Determine whether this object has a request outstanding.
638 * @param {Object} request (Optional) defaults to the last transaction
639 * @return {Boolean} True if there is an outstanding request.
641 isLoading : function(request) {
642 if (!(request && request.xhr)) {
645 // if there is a connection and readyState is not 0 or 4
646 var state = request.xhr.readyState;
647 return !(state === 0 || state == 4);
651 * Aborts any outstanding request.
652 * @param {Object} request (Optional) defaults to the last request
654 abort : function(request) {
656 requests = me.requests,
659 if (request && me.isLoading(request)) {
661 * Clear out the onreadystatechange here, this allows us
662 * greater control, the browser may/may not fire the function
663 * depending on a series of conditions.
665 request.xhr.onreadystatechange = null;
667 me.clearTimeout(request);
668 if (!request.timedout) {
669 request.aborted = true;
671 me.onComplete(request);
673 } else if (!request) {
674 for(id in requests) {
675 if (requests.hasOwnProperty(id)) {
676 me.abort(requests[id]);
683 * Fires when the state of the xhr changes
685 * @param {Object} request The request
687 onStateChange : function(request) {
688 if (request.xhr.readyState == 4) {
689 this.clearTimeout(request);
690 this.onComplete(request);
691 this.cleanup(request);
696 * Clear the timeout on the request
698 * @param {Object} The request
700 clearTimeout: function(request){
701 clearTimeout(request.timeout);
702 delete request.timeout;
706 * Clean up any left over information from the request
708 * @param {Object} The request
710 cleanup: function(request){
716 * To be called when the request has come back from the server
718 * @param {Object} request
719 * @return {Object} The response
721 onComplete : function(request) {
723 options = request.options,
729 result = me.parseStatus(request.xhr.status);
731 // in some browsers we can't access the status if the readyState is not 4, so the request has failed
737 success = result.success;
740 response = me.createResponse(request);
741 me.fireEvent('requestcomplete', me, response, options);
742 Ext.callback(options.success, options.scope, [response, options]);
744 if (result.isException || request.aborted || request.timedout) {
745 response = me.createException(request);
747 response = me.createResponse(request);
749 me.fireEvent('requestexception', me, response, options);
750 Ext.callback(options.failure, options.scope, [response, options]);
752 Ext.callback(options.callback, options.scope, [options, success, response]);
753 delete me.requests[request.id];
758 * Check if the response status was successful
759 * @param {Number} status The status code
760 * @return {Object} An object containing success/status state
762 parseStatus: function(status) {
763 // see: https://prototype.lighthouseapp.com/projects/8886/tickets/129-ie-mangles-http-response-status-code-204-to-1223
764 status = status == 1223 ? 204 : status;
766 var success = (status >= 200 && status < 300) || status == 304,
783 isException: isException
788 * Create the response object
790 * @param {Object} request
792 createResponse : function(request) {
793 var xhr = request.xhr,
795 lines = xhr.getAllResponseHeaders().replace(/\r\n/g, '\n').split('\n'),
796 count = lines.length,
797 line, index, key, value, response;
801 index = line.indexOf(':');
803 key = line.substr(0, index).toLowerCase();
804 if (line.charAt(index + 1) == ' ') {
807 headers[key] = line.substr(index + 1);
816 requestId : request.id,
818 statusText : xhr.statusText,
819 getResponseHeader : function(header){ return headers[header.toLowerCase()]; },
820 getAllResponseHeaders : function(){ return headers; },
821 responseText : xhr.responseText,
822 responseXML : xhr.responseXML
825 // If we don't explicitly tear down the xhr reference, IE6/IE7 will hold this in the closure of the
826 // functions created with getResponseHeader/getAllResponseHeaders
832 * Create the exception object
834 * @param {Object} request
836 createException : function(request) {
839 requestId : request.id,
840 status : request.aborted ? -1 : 0,
841 statusText : request.aborted ? 'transaction aborted' : 'communication failure',
842 aborted: request.aborted,
843 timedout: request.timedout