Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / form / action / Submit.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
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.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @class Ext.form.action.Submit
17  * @extends Ext.form.action.Action
18  * <p>A class which handles submission of data from {@link Ext.form.Basic Form}s
19  * and processes the returned response.</p>
20  * <p>Instances of this class are only created by a {@link Ext.form.Basic Form} when
21  * {@link Ext.form.Basic#submit submit}ting.</p>
22  * <p><u><b>Response Packet Criteria</b></u></p>
23  * <p>A response packet may contain:
24  * <div class="mdetail-params"><ul>
25  * <li><b><code>success</code></b> property : Boolean
26  * <div class="sub-desc">The <code>success</code> property is required.</div></li>
27  * <li><b><code>errors</code></b> property : Object
28  * <div class="sub-desc"><div class="sub-desc">The <code>errors</code> property,
29  * which is optional, contains error messages for invalid fields.</div></li>
30  * </ul></div>
31  * <p><u><b>JSON Packets</b></u></p>
32  * <p>By default, response packets are assumed to be JSON, so a typical response
33  * packet may look like this:</p><pre><code>
34 {
35     success: false,
36     errors: {
37         clientCode: "Client not found",
38         portOfLoading: "This field must not be null"
39     }
40 }</code></pre>
41  * <p>Other data may be placed into the response for processing by the {@link Ext.form.Basic}'s callback
42  * or event handler methods. The object decoded from this JSON is available in the
43  * {@link Ext.form.action.Action#result result} property.</p>
44  * <p>Alternatively, if an {@link Ext.form.Basic#errorReader errorReader} is specified as an {@link Ext.data.reader.Xml XmlReader}:</p><pre><code>
45     errorReader: new Ext.data.reader.Xml({
46             record : 'field',
47             success: '@success'
48         }, [
49             'id', 'msg'
50         ]
51     )
52 </code></pre>
53  * <p>then the results may be sent back in XML format:</p><pre><code>
54 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
55 &lt;message success="false"&gt;
56 &lt;errors&gt;
57     &lt;field&gt;
58         &lt;id&gt;clientCode&lt;/id&gt;
59         &lt;msg&gt;&lt;![CDATA[Code not found. &lt;br /&gt;&lt;i&gt;This is a test validation message from the server &lt;/i&gt;]]&gt;&lt;/msg&gt;
60     &lt;/field&gt;
61     &lt;field&gt;
62         &lt;id&gt;portOfLoading&lt;/id&gt;
63         &lt;msg&gt;&lt;![CDATA[Port not found. &lt;br /&gt;&lt;i&gt;This is a test validation message from the server &lt;/i&gt;]]&gt;&lt;/msg&gt;
64     &lt;/field&gt;
65 &lt;/errors&gt;
66 &lt;/message&gt;
67 </code></pre>
68  * <p>Other elements may be placed into the response XML for processing by the {@link Ext.form.Basic}'s callback
69  * or event handler methods. The XML document is available in the {@link Ext.form.Basic#errorReader errorReader}'s
70  * {@link Ext.data.reader.Xml#xmlData xmlData} property.</p>
71  */
72 Ext.define('Ext.form.action.Submit', {
73     extend:'Ext.form.action.Action',
74     alternateClassName: 'Ext.form.Action.Submit',
75     alias: 'formaction.submit',
76
77     type: 'submit',
78
79     /**
80      * @cfg {Boolean} clientValidation Determines whether a Form's fields are validated
81      * in a final call to {@link Ext.form.Basic#isValid isValid} prior to submission.
82      * Pass <tt>false</tt> in the Form's submit options to prevent this. Defaults to true.
83      */
84
85     // inherit docs
86     run : function(){
87         var form = this.form;
88         if (this.clientValidation === false || form.isValid()) {
89             this.doSubmit();
90         } else {
91             // client validation failed
92             this.failureType = Ext.form.action.Action.CLIENT_INVALID;
93             form.afterAction(this, false);
94         }
95     },
96
97     /**
98      * @private
99      * Perform the submit of the form data.
100      */
101     doSubmit: function() {
102         var formEl,
103             ajaxOptions = Ext.apply(this.createCallback(), {
104                 url: this.getUrl(),
105                 method: this.getMethod(),
106                 headers: this.headers
107             });
108
109         // For uploads we need to create an actual form that contains the file upload fields,
110         // and pass that to the ajax call so it can do its iframe-based submit method.
111         if (this.form.hasUpload()) {
112             formEl = ajaxOptions.form = this.buildForm();
113             ajaxOptions.isUpload = true;
114         } else {
115             ajaxOptions.params = this.getParams();
116         }
117
118         Ext.Ajax.request(ajaxOptions);
119
120         if (formEl) {
121             Ext.removeNode(formEl);
122         }
123     },
124
125     /**
126      * @private
127      * Build the full set of parameters from the field values plus any additional configured params.
128      */
129     getParams: function() {
130         var nope = false,
131             configParams = this.callParent(),
132             fieldParams = this.form.getValues(nope, nope, this.submitEmptyText !== nope);
133         return Ext.apply({}, fieldParams, configParams);
134     },
135
136     /**
137      * @private
138      * Build a form element containing fields corresponding to all the parameters to be
139      * submitted (everything returned by {@link #getParams}.
140      * NOTE: the form element is automatically added to the DOM, so any code that uses
141      * it must remove it from the DOM after finishing with it.
142      * @return HTMLFormElement
143      */
144     buildForm: function() {
145         var fieldsSpec = [],
146             formSpec,
147             formEl,
148             basicForm = this.form,
149             params = this.getParams(),
150             uploadFields = [];
151
152         basicForm.getFields().each(function(field) {
153             if (field.isFileUpload()) {
154                 uploadFields.push(field);
155             }
156         });
157
158         function addField(name, val) {
159             fieldsSpec.push({
160                 tag: 'input',
161                 type: 'hidden',
162                 name: name,
163                 value: Ext.String.htmlEncode(val)
164             });
165         }
166
167         // Add the form field values
168         Ext.iterate(params, function(key, val) {
169             if (Ext.isArray(val)) {
170                 Ext.each(val, function(v) {
171                     addField(key, v);
172                 });
173             } else {
174                 addField(key, val);
175             }
176         });
177
178         formSpec = {
179             tag: 'form',
180             action: this.getUrl(),
181             method: this.getMethod(),
182             target: this.target || '_self',
183             style: 'display:none',
184             cn: fieldsSpec
185         };
186
187         // Set the proper encoding for file uploads
188         if (uploadFields.length) {
189             formSpec.encoding = formSpec.enctype = 'multipart/form-data';
190         }
191
192         // Create the form
193         formEl = Ext.DomHelper.append(Ext.getBody(), formSpec);
194
195         // Special handling for file upload fields: since browser security measures prevent setting
196         // their values programatically, and prevent carrying their selected values over when cloning,
197         // we have to move the actual field instances out of their components and into the form.
198         Ext.Array.each(uploadFields, function(field) {
199             if (field.rendered) { // can only have a selected file value after being rendered
200                 formEl.appendChild(field.extractFileInput());
201             }
202         });
203
204         return formEl;
205     },
206
207
208
209     /**
210      * @private
211      */
212     onSuccess: function(response) {
213         var form = this.form,
214             success = true,
215             result = this.processResponse(response);
216         if (result !== true && !result.success) {
217             if (result.errors) {
218                 form.markInvalid(result.errors);
219             }
220             this.failureType = Ext.form.action.Action.SERVER_INVALID;
221             success = false;
222         }
223         form.afterAction(this, success);
224     },
225
226     /**
227      * @private
228      */
229     handleResponse: function(response) {
230         var form = this.form,
231             errorReader = form.errorReader,
232             rs, errors, i, len, records;
233         if (errorReader) {
234             rs = errorReader.read(response);
235             records = rs.records;
236             errors = [];
237             if (records) {
238                 for(i = 0, len = records.length; i < len; i++) {
239                     errors[i] = records[i].data;
240                 }
241             }
242             if (errors.length < 1) {
243                 errors = null;
244             }
245             return {
246                 success : rs.success,
247                 errors : errors
248             };
249         }
250         return Ext.decode(response.responseText);
251     }
252 });
253