Upgrade to ExtJS 4.0.2 - Released 06/09/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 #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 #errorReader}'s {@link Ext.data.reader.Xml#xmlData xmlData} property.</p>
70  */
71 Ext.define('Ext.form.action.Submit', {
72     extend:'Ext.form.action.Action',
73     alternateClassName: 'Ext.form.Action.Submit',
74     alias: 'formaction.submit',
75
76     type: 'submit',
77
78     /**
79      * @cfg {boolean} clientValidation Determines whether a Form's fields are validated
80      * in a final call to {@link Ext.form.Basic#isValid isValid} prior to submission.
81      * Pass <tt>false</tt> in the Form's submit options to prevent this. Defaults to true.
82      */
83
84     // inherit docs
85     run : function(){
86         var form = this.form;
87         if (this.clientValidation === false || form.isValid()) {
88             this.doSubmit();
89         } else {
90             // client validation failed
91             this.failureType = Ext.form.action.Action.CLIENT_INVALID;
92             form.afterAction(this, false);
93         }
94     },
95
96     /**
97      * @private
98      * Perform the submit of the form data.
99      */
100     doSubmit: function() {
101         var formEl,
102             ajaxOptions = Ext.apply(this.createCallback(), {
103                 url: this.getUrl(),
104                 method: this.getMethod(),
105                 headers: this.headers
106             });
107
108         // For uploads we need to create an actual form that contains the file upload fields,
109         // and pass that to the ajax call so it can do its iframe-based submit method.
110         if (this.form.hasUpload()) {
111             formEl = ajaxOptions.form = this.buildForm();
112             ajaxOptions.isUpload = true;
113         } else {
114             ajaxOptions.params = this.getParams();
115         }
116
117         Ext.Ajax.request(ajaxOptions);
118
119         if (formEl) {
120             Ext.removeNode(formEl);
121         }
122     },
123
124     /**
125      * @private
126      * Build the full set of parameters from the field values plus any additional configured params.
127      */
128     getParams: function() {
129         var nope = false,
130             configParams = this.callParent(),
131             fieldParams = this.form.getValues(nope, nope, this.submitEmptyText !== nope);
132         return Ext.apply({}, fieldParams, configParams);
133     },
134
135     /**
136      * @private
137      * Build a form element containing fields corresponding to all the parameters to be
138      * submitted (everything returned by {@link #getParams}.
139      * NOTE: the form element is automatically added to the DOM, so any code that uses
140      * it must remove it from the DOM after finishing with it.
141      * @return HTMLFormElement
142      */
143     buildForm: function() {
144         var fieldsSpec = [],
145             formSpec,
146             formEl,
147             basicForm = this.form,
148             params = this.getParams(),
149             uploadFields = [];
150
151         basicForm.getFields().each(function(field) {
152             if (field.isFileUpload()) {
153                 uploadFields.push(field);
154             }
155         });
156
157         function addField(name, val) {
158             fieldsSpec.push({
159                 tag: 'input',
160                 type: 'hidden',
161                 name: name,
162                 value: Ext.String.htmlEncode(val)
163             });
164         }
165
166         // Add the form field values
167         Ext.iterate(params, function(key, val) {
168             if (Ext.isArray(val)) {
169                 Ext.each(val, function(v) {
170                     addField(key, v);
171                 });
172             } else {
173                 addField(key, val);
174             }
175         });
176
177         formSpec = {
178             tag: 'form',
179             action: this.getUrl(),
180             method: this.getMethod(),
181             target: this.target || '_self',
182             style: 'display:none',
183             cn: fieldsSpec
184         };
185
186         // Set the proper encoding for file uploads
187         if (uploadFields.length) {
188             formSpec.encoding = formSpec.enctype = 'multipart/form-data';
189         }
190
191         // Create the form
192         formEl = Ext.core.DomHelper.append(Ext.getBody(), formSpec);
193
194         // Special handling for file upload fields: since browser security measures prevent setting
195         // their values programatically, and prevent carrying their selected values over when cloning,
196         // we have to move the actual field instances out of their components and into the form.
197         Ext.Array.each(uploadFields, function(field) {
198             if (field.rendered) { // can only have a selected file value after being rendered
199                 formEl.appendChild(field.extractFileInput());
200             }
201         });
202
203         return formEl;
204     },
205
206
207
208     /**
209      * @private
210      */
211     onSuccess: function(response) {
212         var form = this.form,
213             success = true,
214             result = this.processResponse(response);
215         if (result !== true && !result.success) {
216             if (result.errors) {
217                 form.markInvalid(result.errors);
218             }
219             this.failureType = Ext.form.action.Action.SERVER_INVALID;
220             success = false;
221         }
222         form.afterAction(this, success);
223     },
224
225     /**
226      * @private
227      */
228     handleResponse: function(response) {
229         var form = this.form,
230             errorReader = form.errorReader,
231             rs, errors, i, len, records;
232         if (errorReader) {
233             rs = errorReader.read(response);
234             records = rs.records;
235             errors = [];
236             if (records) {
237                 for(i = 0, len = records.length; i < len; i++) {
238                     errors[i] = records[i].data;
239                 }
240             }
241             if (errors.length < 1) {
242                 errors = null;
243             }
244             return {
245                 success : rs.success,
246                 errors : errors
247             };
248         }
249         return Ext.decode(response.responseText);
250     }
251 });
252