/** * The Connection class encapsulates a connection to the page's originating domain, allowing * requests to be made either to a configured URL, or to a URL specified at request time. * * Requests made by this class are asynchronous, and will return immediately. No data from the * server will be available to the statement immediately following the {@link #request} call. * To process returned data, use a success callback in the request options object, or an * {@link #requestcomplete event listener}. * * # File Uploads * * File uploads are not performed using normal "Ajax" techniques, that is they are not performed * using XMLHttpRequests. Instead the form is submitted in the standard manner with the DOM * <form> element temporarily modified to have its target set to refer to a dynamically * generated, hidden <iframe> which is inserted into the document but removed after the * return data has been gathered. * * The server response is parsed by the browser to create the document for the IFRAME. If the * server is using JSON to send the return object, then the Content-Type header must be set to * "text/html" in order to tell the browser to insert the text unchanged into the document body. * * Characters which are significant to an HTML parser must be sent as HTML entities, so encode * `<` as `<`, `&` as `&` etc. * * The response text is retrieved from the document, and a fake XMLHttpRequest object is created * containing a responseText property in order to conform to the requirements of event handlers and * callbacks. * * Be aware that file upload packets are sent with the content type multipart/form and some server * technologies (notably JEE) may require some custom processing in order to retrieve parameter * names and parameter values from the packet content. * * Also note that it's not possible to check the response code of the hidden iframe, so the success * handler will ALWAYS fire. * * # Binary Posts * * The class supports posting binary data to the server by using native browser capabilities, * or a flash polyfill plugin in browsers that do not support native binary posting (e.g. * Internet Explorer version 9 or less). A number of limitations exist when the polyfill is used: * * - Only asynchronous connections are supported. * - Only the POST method can be used. * - The return data can only be binary for now. Set the {@link Ext.data.Connection#binary binary} * parameter to `true`. * - Only the 0, 1 and 4 (complete) readyState values will be reported to listeners. * - The flash object will be injected at the bottom of the document and should be invisible. * - Important: See note about packaing the flash plugin with the app in the documenetation of * {@link Ext.data.flash.BinaryXhr BinaryXhr}. * */Ext.define('Ext.data.Connection', { mixins: { observable: 'Ext.mixin.Observable' }, requires: [ 'Ext.data.request.Ajax', 'Ext.data.request.Form', 'Ext.data.flash.BinaryXhr', 'Ext.Deferred' ], statics: { requestId: 0 }, enctypeRe: /multipart\/form-data/i, config: { /** * @cfg {String} url * The URL for this connection. */ url: null, /** * @cfg {Boolean} async * `true` if this request should run asynchronously. Setting this to `false` should * generally be avoided, since it will cause the UI to be blocked, the user won't be able * to interact with the browser until the request completes. */ async: true, // eslint-disable-line id-blacklist /** * @cfg {String} username * The username to pass when using {@link #withCredentials}. */ username: '', /** * @cfg {String} password * The password to pass when using {@link #withCredentials}. */ password: '', /** * @cfg {Boolean} disableCaching * True to add a unique cache-buster param to GET requests. */ disableCaching: true, /** * @cfg {Boolean} withCredentials * True to set `withCredentials = true` on the XHR object */ withCredentials: false, /** * @cfg {Boolean} binary * True if the response should be treated as binary data. If true, the binary * data will be accessible as a "responseBytes" property on the response object. */ binary: false, /** * @cfg {Boolean} cors * True to enable CORS support on the XHR object. Currently the only effect of this option * is to use the XDomainRequest object instead of XMLHttpRequest if the browser is IE8 * or above. */ cors: false, isXdr: false, defaultXdrContentType: 'text/plain', /** * @cfg {String} disableCachingParam * Change the parameter which is sent went disabling caching through a cache buster. */ disableCachingParam: '_dc', /** * @cfg {Number} [timeout=30000] The timeout in milliseconds to be used for * requests. * Defaults to 30000 milliseconds (30 seconds). * * When a request fails due to timeout the XMLHttpRequest response object will * contain: * * timedout: true */ timeout: 30000, /** * @cfg {Object} [extraParams] Any parameters to be appended to the request. */ extraParams: null, /** * @cfg {Boolean} [autoAbort=false] * Whether this request should abort any pending requests. */ autoAbort: false, /** * @cfg {String} method * The default HTTP method to be used for requests. * * If not set, but {@link #request} params are present, POST will be used; * otherwise, GET will be used. */ method: null, /** * @cfg {Object} defaultHeaders * An object containing request headers which are added to each request made by this object. */ defaultHeaders: null, /** * @cfg {String} defaultPostHeader * The default header to be sent out with any post request. */ defaultPostHeader: 'application/x-www-form-urlencoded; charset=UTF-8', /** * @cfg {Boolean} useDefaultXhrHeader * `true` to send the {@link #defaultXhrHeader} along with any request. */ useDefaultXhrHeader: true, /** * @cfg {String} * The header to send with Ajax requests. Also see {@link #useDefaultXhrHeader}. */ defaultXhrHeader: 'XMLHttpRequest' }, /** * @event beforerequest * @preventable * Fires before a network request is made to retrieve a data object. * @param {Ext.data.Connection} conn This Connection object. * @param {Object} options The options config object passed to the {@link #request} method. */ /** * @event requestcomplete * Fires if the request was successfully completed. * @param {Ext.data.Connection} conn This Connection object. * @param {Object} response The XHR object containing the response data. * See [The XMLHttpRequest Object](http://www.w3.org/TR/XMLHttpRequest/) for details. * @param {Object} options The options config object passed to the {@link #request} method. */ /** * @event requestexception * Fires if an error HTTP status was returned from the server. This event may also * be listened to in the event that a request has timed out or has been aborted. * See [HTTP Status Code Definitions](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html) * for details of HTTP status codes. * @param {Ext.data.Connection} conn This Connection object. * @param {Object} response The XHR object containing the response data. * See [The XMLHttpRequest Object](http://www.w3.org/TR/XMLHttpRequest/) for details. * @param {Object} options The options config object passed to the {@link #request} method. */ constructor: function(config) { // Will call initConfig this.mixins.observable.constructor.call(this, config); this.requests = {}; }, /** * @method request * Sends an HTTP (Ajax) request to a remote server. * * **Important:** Ajax server requests are asynchronous, and this call will * return before the response has been received. * * Instead, process any returned data using a promise: * * Ext.Ajax.request({ * url: 'ajax_demo/sample.json' * }).then(function(response, opts) { * var obj = Ext.decode(response.responseText); * console.dir(obj); * }, * function(response, opts) { * console.log('server-side failure with status code ' + response.status); * }); * * Or in callback functions: * * Ext.Ajax.request({ * url: 'ajax_demo/sample.json', * * success: function(response, opts) { * var obj = Ext.decode(response.responseText); * console.dir(obj); * }, * * failure: function(response, opts) { * console.log('server-side failure with status code ' + response.status); * } * }); * * To execute a callback function in the correct scope, use the `scope` option. * * @param {Object} options An object which may contain the following properties: * * (The options object may also contain any other property which might be needed to perform * postprocessing in a callback because it is passed to callback functions.) * * @param {String/Function} options.url The URL to which to send the request, or a function * to call which returns a URL string. The scope of the function is specified by the `scope` * option. Defaults to the configured `url`. * * @param {Boolean} options.async `true` if this request should run asynchronously. * Setting this to `false` should generally be avoided, since it will cause the UI to be * blocked, the user won't be able to interact with the browser until the request completes. * Defaults to `true`. * * @param {Object/String/Function} options.params An object containing properties which are * used as parameters to the request, a url encoded string or a function to call to get either. * The scope of the function is specified by the `scope` option. * * @param {String} options.method The HTTP method to use * for the request. Defaults to the configured method, or if no method was configured, * "GET" if no parameters are being sent, and "POST" if parameters are being sent. Note that * the method name is case-sensitive and should be all caps. * * @param {Function} options.callback The function to be called upon receipt of the HTTP * response. The callback is called regardless of success or failure and is passed the * following parameters: * @param {Object} options.callback.options The parameter to the request call. * @param {Boolean} options.callback.success True if the request succeeded. * @param {Object} options.callback.response The XMLHttpRequest object containing the response * data. See [www.w3.org/TR/XMLHttpRequest/](http://www.w3.org/TR/XMLHttpRequest/) for details * about accessing elements of the response. * * @param {Function} options.success The function to be called upon success of the request. * The callback is passed the following parameters: * @param {Object} options.success.response The XMLHttpRequest object containing the response * data. * @param {Object} options.success.options The parameter to the request call. * * @param {Function} options.failure The function to be called upon failure of the request. * The callback is passed the following parameters: * @param {Object} options.failure.response The XMLHttpRequest object containing the response * data. * @param {Object} options.failure.options The parameter to the request call. * * @param {Object} options.scope The scope in which to execute the callbacks: The "this" object * for the callback function. If the `url`, or `params` options were specified as functions * from which to draw values, then this also serves as the scope for those function calls. * Defaults to the browser window. * * @param {Number} options.timeout The timeout in milliseconds to be used for this * request. * Defaults to 30000 milliseconds (30 seconds). * * When a request fails due to timeout the XMLHttpRequest response object will * contain: * * timedout: true * * @param {Ext.Element/HTMLElement/String} options.form The `<form>` Element or the id of the * `<form>` to pull parameters from. * * @param {Boolean} options.isUpload **Only meaningful when used with the `form` option.** * * True if the form object is a file upload (will be set automatically if the form was * configured with **`enctype`** `"multipart/form-data"`). * * File uploads are not performed using normal "Ajax" techniques, that is they are **not** * performed using XMLHttpRequests. Instead the form is submitted in the standard manner with * the DOM `<form>` element temporarily modified to have its * [target](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/target) * set to refer to a dynamically generated, hidden `<iframe>` which is inserted * into the document but removed after the return data has been gathered. * * The server response is parsed by the browser to create the document for the IFRAME. If the * server is using JSON to send the return object, then the * [Content-Type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) * header must be set to "text/html" in order to tell the browser to insert the text * unchanged into the document body. * * The response text is retrieved from the document, and a fake XMLHttpRequest object is created * containing a `responseText` property in order to conform to the requirements of event * handlers and callbacks. * * Be aware that file upload packets are sent with the content type * [multipart/form](https://tools.ietf.org/html/rfc7233#section-4.1) and some server * technologies (notably JEE) may require some custom processing in order to retrieve parameter * names and parameter values from the packet content. * * - [target](http://www.w3.org/TR/REC-html40/present/frames.html#adef-target) * - [Content-Type](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17) * - [multipart/form](http://www.faqs.org/rfcs/rfc2388.html) * * @param {Object} options.headers Request headers to set for the request. * The XHR will attempt to set an appropriate Content-Type based on the params/data passed * to the request. To prevent this, setting the Content-Type header to `null` or `undefined` * will not attempt to set any Content-Type and it will be left to the browser. * * @param {Object} options.xmlData XML document to use for the post. Note: This will be used * instead of params for the post data. Any params will be appended to the URL. * * @param {Object/String} options.jsonData JSON data to use as the post. Note: This will be used * instead of params for the post data. Any params will be appended to the URL. * * @param {String} options.rawData A raw string to use as the post. Note: This will be used * instead of params for the post data. Any params will be appended to the URL. * * @param {Array} options.binaryData An array of bytes to submit in binary form. Any params * will be appended to the URL. If binaryData is present, you must set * {@link Ext.data.Connection#binary binary} to `true` and options.method to `POST`. * * @param {Boolean} options.disableCaching True to add a unique cache-buster param to GET * requests. * * @param {Boolean} options.withCredentials True to add the withCredentials property to the * XHR object * * @param {String} options.username The username to pass when using `withCredentials`. * * @param {String} options.password The password to pass when using `withCredentials`. * * @param {Boolean} options.binary True if the response should be treated as binary data. * If true, the binary data will be accessible as a "responseBytes" property on the response * object. * * @return {Ext.data.request.Base} The request object. This may be used to abort the * request. */ request: function(options) { var me = this, requestOptions, request; options = options || {}; if (me.fireEvent('beforerequest', me, options) !== false) { requestOptions = me.setOptions(options, options.scope || Ext.global); request = me.createRequest(options, requestOptions); return request.start(requestOptions.data); } // Reusing for response request = { status: -1, statusText: 'Request cancelled in beforerequest event handler' }; Ext.callback(options.callback, options.scope, [options, false, request]); return Ext.Deferred.rejected([options, false, request]); }, createRequest: function(options, requestOptions) { var me = this, type = options.type || requestOptions.type, request; // If request type is not specified we have to deduce it if (!type) { type = me.isFormUpload(options) ? 'form' : 'ajax'; } // if autoabort is set, cancel the current transactions if (options.autoAbort || me.getAutoAbort()) { me.abort(); } // It is possible for the original options object to be mutated if somebody // had overridden Connection.setOptions method; it is also possible that such // override would do a sensible thing and mutate outgoing requestOptions instead. // So we have to pass *both* to the Request constructor, along with the set // of defaults potentially set on the Connection instance. // If it looks ridiculous, that's because it is; things we have to do for // backward compatibility... request = Ext.Factory.request({ type: type, owner: me, options: options, requestOptions: requestOptions, ownerConfig: me.getConfig() }); me.requests[request.id] = request; me.latestId = request.id; return request; }, /** * Detects whether the form is intended to be used for an upload. * @private */ isFormUpload: function(options) { var form = this.getForm(options); if (form) { return options.isUpload || this.enctypeRe.test(form.getAttribute('enctype')); } return false; }, /** * Gets the form object from options. * @private * @param {Object} options The request options * @return {HTMLElement} The form, null if not passed */ getForm: function(options) { return Ext.getDom(options.form); }, /** * Sets various options such as the url, params for the request * @param {Object} options The initial options * @param {Object} scope The scope to execute in * @return {Object} The params for the request */ setOptions: function(options, scope) { var me = this, params = options.params || {}, extraParams = me.getExtraParams(), urlParams = options.urlParams, url = options.url || me.getUrl(), cors = options.cors, jsonData = options.jsonData, method, disableCache, data; if (cors !== undefined) { me.setCors(cors); } // allow params to be a method that returns the params object if (Ext.isFunction(params)) { params = params.call(scope, options); } // allow url to be a method that returns the actual url if (Ext.isFunction(url)) { url = url.call(scope, options); } url = this.setupUrl(options, url); //<debug> if (!url) { Ext.raise({ options: options, msg: 'No URL specified' }); } //</debug> // check for xml or json data, and make sure json data is encoded data = options.rawData || options.binaryData || options.xmlData || jsonData || null; if (jsonData && !Ext.isPrimitive(jsonData)) { data = Ext.encode(data); } // Check for binary data. Transform if needed if (options.binaryData) { //<debug> if (!Ext.isArray(options.binaryData)) { Ext.log.warn("Binary submission data must be an array of byte values! " + "Instead got " + typeof(options.binaryData)); } //</debug> if (me.nativeBinaryPostSupport()) { data = (new Uint8Array(options.binaryData)); // eslint-disable-line no-undef if ((Ext.isChrome && Ext.chromeVersion < 22) || Ext.isSafari || Ext.isGecko) { // send the underlying buffer, not the view, since that's not supported // on versions of chrome older than 22 data = data.buffer; } } } // make sure params are a url encoded string and include any extraParams if specified if (Ext.isObject(params)) { params = Ext.Object.toQueryString(params); } if (Ext.isObject(extraParams)) { extraParams = Ext.Object.toQueryString(extraParams); } params = params + ((extraParams) ? ((params) ? '&' : '') + extraParams : ''); urlParams = Ext.isObject(urlParams) ? Ext.Object.toQueryString(urlParams) : urlParams; params = this.setupParams(options, params); // decide the proper method for this request method = (options.method || me.getMethod() || ((params || data) ? 'POST' : 'GET')).toUpperCase(); this.setupMethod(options, method); disableCache = options.disableCaching !== false ? (options.disableCaching || me.getDisableCaching()) : false; // if the method is get append date to prevent caching if (method === 'GET' && disableCache) { url = Ext.urlAppend(url, (options.disableCachingParam || me.getDisableCachingParam()) + '=' + (new Date().getTime())); } // if the method is get or there is json/xml data append the params to the url if ((method === 'GET' || data) && params) { url = Ext.urlAppend(url, params); params = null; } // allow params to be forced into the url if (urlParams) { url = Ext.urlAppend(url, urlParams); } return { url: url, method: method, data: data || params || null }; }, /** * Template method for overriding url * @private * @param {Object} options * @param {String} url * @return {String} The modified url */ setupUrl: function(options, url) { var form = this.getForm(options); if (form) { url = url || form.action; } return url; }, /** * Template method for overriding params * @private * @param {Object} options * @param {String} params * @return {String} The modified params */ setupParams: function(options, params) { var form = this.getForm(options), serializedForm; if (form && !this.isFormUpload(options)) { serializedForm = Ext.Element.serializeForm(form); params = params ? (params + '&' + serializedForm) : serializedForm; } return params; }, /** * Template method for overriding method * @private * @param {Object} options * @param {String} method * @return {String} The modified method */ setupMethod: function(options, method) { if (this.isFormUpload(options)) { return 'POST'; } return method; }, /** * Determines whether this object has a request outstanding. * * @param {Object} [request] Defaults to the last transaction * * @return {Boolean} True if there is an outstanding request. */ isLoading: function(request) { if (!request) { request = this.getLatest(); } return request ? request.isLoading() : false; }, /** * Aborts an active request. * @param {Ext.ajax.Request} [request] Defaults to the last request */ abort: function(request) { if (!request) { request = this.getLatest(); } if (request && request.isLoading()) { request.abort(); } }, /** * Aborts all active requests */ abortAll: function() { var requests = this.requests, id; for (id in requests) { this.abort(requests[id]); } }, /** * Gets the most recent request * @return {Object} The request. Null if there is no recent request * @private */ getLatest: function() { var id = this.latestId, request; if (id) { request = this.requests[id]; } return request || null; }, /** * Clears the timeout on the request * @param {Object} request The request * @private */ clearTimeout: function(request) { if (!request) { request = this.getLatest(); } if (request) { request.clearTimer(); } }, onRequestComplete: function(request) { delete this.requests[request.id]; }, /** * @return {Boolean} `true` if the browser can natively post binary data. * @private */ nativeBinaryPostSupport: function() { return Ext.isChrome || (Ext.isSafari && Ext.isDefined(window.Uint8Array)) || (Ext.isGecko && Ext.isDefined(window.Uint8Array)); }});