/** * This class manages a pending form submit. Instances of this type are created by the * `{@link Ext.data.Connection#request}` method. * @since 6.0.0 */Ext.define('Ext.data.request.Form', { extend: 'Ext.data.request.Base', alias: 'request.form', start: function(data) { var me = this, options = me.options, requestOptions = me.requestOptions; // Parent will set the timeout me.callParent([data]); me.form = me.upload(options.form, requestOptions.url, requestOptions.data, options); return me; }, abort: function(force) { var me = this, frame; if (me.isLoading()) { try { frame = me.frame.dom; if (frame.stop) { frame.stop(); } else { frame.document.execCommand('Stop'); } } catch (e) { // ignore } } me.callParent([force]); me.onComplete(); me.cleanup(); }, /* * Clean up any left over information from the form submission. */ cleanup: function() { var me = this, frame = me.frame; if (frame) { // onComplete hasn't fired yet if frame != null so need to clean up frame.un('load', me.onComplete, me); Ext.removeNode(frame); } me.frame = me.form = null; }, isLoading: function() { return !!this.frame; }, /** * Uploads a form using a hidden iframe. * @param {String/HTMLElement/Ext.dom.Element} form The form to upload * @param {String} url The url to post to * @param {String} params Any extra parameters to pass * @param {Object} options The initial options * @private */ upload: function(form, url, params, options) { form = Ext.getDom(form); options = options || {}; var frameDom = document.createElement('iframe'), frame = Ext.get(frameDom), id = frame.id, hiddens = [], encoding = 'multipart/form-data', buf = { target: form.target, method: form.method, encoding: form.encoding, enctype: form.enctype, action: form.action }, addField = function(name, value) { hiddenItem = document.createElement('input'); Ext.fly(hiddenItem).set({ type: 'hidden', value: value, name: name }); form.appendChild(hiddenItem); hiddens.push(hiddenItem); }, hiddenItem, obj, value, name, vLen, v, hLen, h, request; /* * Originally this behaviour was modified for Opera 10 to apply the secure URL after * the frame had been added to the document. It seems this has since been corrected in * Opera so the behaviour has been reverted, the URL will be set before being added. */ frame.set({ name: id, cls: Ext.baseCSSPrefix + 'hidden-display', src: Ext.SSL_SECURE_URL, tabIndex: -1 }); document.body.appendChild(frameDom); // This is required so that IE doesn't pop the response up in a new window. if (document.frames) { document.frames[id].name = id; } Ext.fly(form).set({ target: id, method: 'POST', enctype: encoding, encoding: encoding, action: url || buf.action }); // add dynamic params if (params) { obj = Ext.Object.fromQueryString(params) || {}; for (name in obj) { if (obj.hasOwnProperty(name)) { value = obj[name]; if (Ext.isArray(value)) { vLen = value.length; for (v = 0; v < vLen; v++) { addField(name, value[v]); } } else { addField(name, value); } } } } this.frame = frame; frame.on({ load: this.onComplete, scope: this, // Opera introduces multiple 'load' events, so account for extras as well single: !Ext.isOpera }); form.submit(); // Restore form to previous settings Ext.fly(form).set(buf); for (hLen = hiddens.length, h = 0; h < hLen; h++) { Ext.removeNode(hiddens[h]); } return form; }, getDoc: function() { var frame = this.frame.dom; return (frame && (frame.contentWindow.document || frame.contentDocument)) || (window.frames[frame.id] || {}).document; }, getTimeout: function() { // For a form post, since it can include large file uploads, we do not use the // default timeout from the owner. Only explicit timeouts passed in the options // are meaningful here. return this.options.timeout; }, /** * Callback handler for the upload function. After we've submitted the form via the * iframe this creates a bogus response object to simulate an XHR and populates its * responseText from the now-loaded iframe's document body (or a textarea inside the * body). We then clean up by removing the iframe. * @private */ onComplete: function() { var me = this, frame = me.frame, owner = me.owner, options = me.options, callback, doc, success, contentNode, response; // Nulled out frame means onComplete was fired already if (!frame) { return; } if (me.aborted || me.timedout) { me.result = response = me.createException(); response.responseXML = null; response.responseText = '{success:false,message:"' + Ext.String.trim(response.statusText) + '"}'; callback = options.failure; success = false; } else { try { doc = me.getDoc(); // bogus response object me.result = response = { responseText: '', responseXML: null }; // Opera will fire an extraneous load event on about:blank // We want to ignore this since the load event will be fired twice if (doc) { //TODO: See if this still applies vs Current opera-webkit releases if (Ext.isOpera && doc.location == Ext.SSL_SECURE_URL) { return; } if (doc.body) { // Response sent as Content-Type: text/json or text/plain. // Browser will embed it in a <pre> element. // Note: The statement below tests the result of an assignment. if ((contentNode = doc.body.firstChild) && /pre/i.test(contentNode.tagName)) { response.responseText = contentNode.textContent || contentNode.innerText; } // Response sent as Content-Type: text/html. We must still support // JSON response wrapped in textarea. // Note: The statement below tests the result of an assignment. else if ((contentNode = doc.getElementsByTagName('textarea')[0])) { response.responseText = contentNode.value; } // Response sent as Content-Type: text/html with no wrapping. Scrape // JSON response out of text else { response.responseText = doc.body.textContent || doc.body.innerText; } } //in IE the document may still have a body even if returns XML. // TODO What is this about? response.responseXML = doc.XMLDocument || doc; callback = options.success; success = true; response.status = 200; } else { Ext.raise("Could not acquire a suitable connection for the file upload service."); } } catch (e) { me.result = response = me.createException(); // Report any error in the message property response.status = 400; response.statusText = (e.message || e.description) + ''; response.responseText = '{success:false,message:"' + Ext.String.trim(response.statusText) + '"}'; response.responseXML = null; callback = options.failure; success = false; } } me.frame = null; me.success = success; owner.fireEvent(success ? 'requestcomplete' : 'requestexception', owner, response, options); Ext.callback(callback, options.scope, [response, options]); Ext.callback(options.callback, options.scope, [options, success, response]); owner.onRequestComplete(me); // Must defer slightly to permit full exit from load event before destruction Ext.asap(frame.destroy, frame); me.callParent(); }, destroy: function() { this.cleanup(); this.callParent(); }});