]> git.proxmox.com Git - sencha-touch.git/blob - src/src/form/Panel.js
import Sencha Touch 2.4.2 source
[sencha-touch.git] / src / src / form / Panel.js
1 /**
2 * The Form panel presents a set of form fields and provides convenient ways to load and save data. Usually a form
3 * panel just contains the set of fields you want to display, ordered inside the items configuration like this:
4 *
5 * @example
6 * var form = Ext.create('Ext.form.Panel', {
7 * fullscreen: true,
8 * items: [
9 * {
10 * xtype: 'textfield',
11 * name: 'name',
12 * label: 'Name'
13 * },
14 * {
15 * xtype: 'emailfield',
16 * name: 'email',
17 * label: 'Email'
18 * },
19 * {
20 * xtype: 'passwordfield',
21 * name: 'password',
22 * label: 'Password'
23 * }
24 * ]
25 * });
26 *
27 * Here we just created a simple form panel which could be used as a registration form to sign up to your service. We
28 * added a plain {@link Ext.field.Text text field} for the user's Name, an {@link Ext.field.Email email field} and
29 * finally a {@link Ext.field.Password password field}. In each case we provided a {@link Ext.field.Field#name name}
30 * config on the field so that we can identify it later on when we load and save data on the form.
31 *
32 * ##Loading data
33 *
34 * Using the form we created above, we can load data into it in a few different ways, the easiest is to use
35 * {@link #setValues}:
36 *
37 * form.setValues({
38 * name: 'Ed',
39 * email: 'ed@sencha.com',
40 * password: 'secret'
41 * });
42 *
43 * It's also easy to load {@link Ext.data.Model Model} instances into a form - let's say we have a User model and want
44 * to load a particular instance into our form:
45 *
46 * Ext.define('MyApp.model.User', {
47 * extend: 'Ext.data.Model',
48 * config: {
49 * fields: ['name', 'email', 'password']
50 * }
51 * });
52 *
53 * var ed = Ext.create('MyApp.model.User', {
54 * name: 'Ed',
55 * email: 'ed@sencha.com',
56 * password: 'secret'
57 * });
58 *
59 * form.setRecord(ed);
60 *
61 * ##Retrieving form data
62 *
63 * Getting data out of the form panel is simple and is usually achieve via the {@link #getValues} method:
64 *
65 * var values = form.getValues();
66 *
67 * //values now looks like this:
68 * {
69 * name: 'Ed',
70 * email: 'ed@sencha.com',
71 * password: 'secret'
72 * }
73 *
74 * It's also possible to listen to the change events on individual fields to get more timely notification of changes
75 * that the user is making. Here we expand on the example above with the User model, updating the model as soon as
76 * any of the fields are changed:
77 *
78 * var form = Ext.create('Ext.form.Panel', {
79 * listeners: {
80 * '> field': {
81 * change: function(field, newValue, oldValue) {
82 * ed.set(field.getName(), newValue);
83 * }
84 * }
85 * },
86 * items: [
87 * {
88 * xtype: 'textfield',
89 * name: 'name',
90 * label: 'Name'
91 * },
92 * {
93 * xtype: 'emailfield',
94 * name: 'email',
95 * label: 'Email'
96 * },
97 * {
98 * xtype: 'passwordfield',
99 * name: 'password',
100 * label: 'Password'
101 * }
102 * ]
103 * });
104 *
105 * The above used a new capability of Sencha Touch 2.0, which enables you to specify listeners on child components of any
106 * container. In this case, we attached a listener to the {@link Ext.field.Text#change change} event of each form
107 * field that is a direct child of the form panel. Our listener gets the name of the field that fired the change event,
108 * and updates our {@link Ext.data.Model Model} instance with the new value. For example, changing the email field
109 * in the form will update the Model's email field.
110 *
111 * ##Submitting forms
112 *
113 * There are a few ways to submit form data. In our example above we have a Model instance that we have updated, giving
114 * us the option to use the Model's {@link Ext.data.Model#save save} method to persist the changes back to our server,
115 * without using a traditional form submission. Alternatively, we can send a normal browser form submit using the
116 * {@link #method} method:
117 *
118 * form.submit({
119 * url: 'url/to/submit/to',
120 * method: 'POST',
121 * success: function() {
122 * alert('form submitted successfully!');
123 * }
124 * });
125 *
126 * In this case we provided the `url` to submit the form to inside the submit call - alternatively you can just set the
127 * {@link #url} configuration when you create the form. We can specify other parameters (see {@link #method} for a
128 * full list), including callback functions for success and failure, which are called depending on whether or not the
129 * form submission was successful. These functions are usually used to take some action in your app after your data
130 * has been saved to the server side.
131 *
132 * For more information regarding forms and fields, please review [Using Forms in Sencha Touch Guide](../../../components/forms.html)
133 */
134 Ext.define('Ext.form.Panel', {
135 alternateClassName: 'Ext.form.FormPanel',
136 extend : 'Ext.Panel',
137 xtype : 'formpanel',
138 requires: ['Ext.XTemplate', 'Ext.field.Checkbox', 'Ext.Ajax'],
139
140 /**
141 * @event submit
142 * @preventable doSubmit
143 * Fires upon successful (Ajax-based) form submission.
144 * @param {Ext.form.Panel} this This FormPanel.
145 * @param {Object} result The result object as returned by the server.
146 * @param {Ext.EventObject} e The event object.
147 */
148
149 /**
150 * @event beforesubmit
151 * @preventable doBeforeSubmit
152 * Fires immediately preceding any Form submit action.
153 * Implementations may adjust submitted form values or options prior to execution.
154 * A return value of `false` from this listener will abort the submission
155 * attempt (regardless of `standardSubmit` configuration).
156 * @param {Ext.form.Panel} this This FormPanel.
157 * @param {Object} values A hash collection of the qualified form values about to be submitted.
158 * @param {Object} options Submission options hash (only available when `standardSubmit` is `false`).
159 * @param {Ext.EventObject} e The event object if the form was submitted via a HTML5 form submit event.
160 */
161
162 /**
163 * @event exception
164 * Fires when either the Ajax HTTP request reports a failure OR the server returns a `success:false`
165 * response in the result payload.
166 * @param {Ext.form.Panel} this This FormPanel.
167 * @param {Object} result Either a failed Ext.data.Connection request object or a failed (logical) server.
168 * response payload.
169 */
170
171 config: {
172 /**
173 * @cfg {String} baseCls
174 * @inheritdoc
175 */
176 baseCls: Ext.baseCSSPrefix + 'form',
177
178 /**
179 * @cfg {Boolean} standardSubmit
180 * Whether or not we want to perform a standard form submit.
181 * @accessor
182 */
183 standardSubmit: false,
184
185 /**
186 * @cfg {String} url
187 * The default url for submit actions.
188 * @accessor
189 */
190 url: null,
191
192 /**
193 * @cfg (String} enctype
194 * The enctype attribute for the form, specifies how the form should be encoded when submitting
195 */
196 enctype: null,
197
198 /**
199 * @cfg {Object} baseParams
200 * Optional hash of params to be sent (when `standardSubmit` configuration is `false`) on every submit.
201 * @accessor
202 */
203 baseParams: null,
204
205 /**
206 * @cfg {Object} submitOnAction
207 * When this is set to `true`, the form will automatically submit itself whenever the `action`
208 * event fires on a field in this form. The action event usually fires whenever you press
209 * go or enter inside a textfield.
210 * @accessor
211 */
212 submitOnAction: false,
213
214 /**
215 * @cfg {Ext.data.Model} record The model instance of this form. Can by dynamically set at any time.
216 * @accessor
217 */
218 record: null,
219
220 /**
221 * @cfg {String} method
222 * The method which this form will be submitted. `post` or `get`.
223 */
224 method: 'post',
225
226 /**
227 * @cfg {Object} scrollable
228 * Possible values are true, false, and null. The true value indicates that
229 * users can scroll the panel. The false value disables scrolling, but developers
230 * can enable it in the app. The null value indicates that the object cannot be
231 * scrolled and that scrolling cannot be enabled for this object.
232 *
233 * Example:
234 * title: 'Sliders',
235 * xtype: 'formpanel',
236 * iconCls: Ext.filterPlatform('blackberry') ? 'list' : null,
237 * scrollable: true,
238 * items: [ ...
239 * @inheritdoc
240 */
241 scrollable: {
242 translatable: {
243 translationMethod: 'scrollposition'
244 }
245 },
246
247 /**
248 * @cfg {Boolean} trackResetOnLoad
249 * If set to true, {@link #reset}() resets to the last loaded or {@link #setValues}() data instead of
250 * when the form was first created.
251 */
252 trackResetOnLoad:false,
253
254 /**
255 * @cfg {Object} api
256 * If specified, load and submit actions will be loaded and submitted via Ext.Direct. Methods which have been imported by
257 * {@link Ext.direct.Manager} can be specified here to load and submit forms. API methods may also be
258 * specified as strings and will be parsed into the actual functions when the first submit or load has occurred. Such as the following:
259 *
260 * api: {
261 * load: App.ss.MyProfile.load,
262 * submit: App.ss.MyProfile.submit
263 * }
264 *
265 * api: {
266 * load: 'App.ss.MyProfile.load',
267 * submit: 'App.ss.MyProfile.submit'
268 * }
269 *
270 * Load actions can use {@link #paramOrder} or {@link #paramsAsHash} to customize how the load method
271 * is invoked. Submit actions will always use a standard form submit. The `formHandler` configuration
272 * (see Ext.direct.RemotingProvider#action) must be set on the associated server-side method which has
273 * been imported by {@link Ext.direct.Manager}.
274 */
275 api: null,
276
277 /**
278 * @cfg {String/String[]} paramOrder
279 * A list of params to be executed server side. Only used for the {@link #api} `load`
280 * configuration.
281 *
282 * Specify the params in the order in which they must be executed on the
283 * server-side as either (1) an Array of String values, or (2) a String of params
284 * delimited by either whitespace, comma, or pipe. For example,
285 * any of the following would be acceptable:
286 *
287 * paramOrder: ['param1','param2','param3']
288 * paramOrder: 'param1 param2 param3'
289 * paramOrder: 'param1,param2,param3'
290 * paramOrder: 'param1|param2|param'
291 */
292 paramOrder: null,
293
294 /**
295 * @cfg {Boolean} paramsAsHash
296 * Only used for the {@link #api} `load` configuration. If true, parameters will be sent as a
297 * single hash collection of named arguments. Providing a {@link #paramOrder} nullifies this
298 * configuration.
299 */
300 paramsAsHash: null,
301
302 /**
303 * @cfg {Number} timeout
304 * Timeout for form actions in seconds.
305 */
306 timeout: 30,
307
308 /**
309 * @cfg {Boolean} multipartDetection
310 * If this is enabled the form will automatically detect the need to use 'multipart/form-data' during submission.
311 */
312 multipartDetection: true,
313
314 /**
315 * @cfg {Boolean} enableSubmissionForm
316 * The submission form is generated but never added to the dom. It is a submittable version of your form panel, allowing for fields
317 * that are not simple textfields to be properly submitted to servers. It will also send values that are easier to parse
318 * with server side code.
319 *
320 * If this is false we will attempt to subject the raw form inside the form panel.
321 */
322 enableSubmissionForm: true
323 },
324
325 getElementConfig: function() {
326 var config = this.callParent();
327 config.tag = "form";
328 // Added a submit input for standard form submission. This cannot have "display: none;" or it will not work
329 config.children.push({
330 tag: 'input',
331 type: 'submit',
332 style: 'visibility: hidden; width: 0; height: 0; position: absolute; right: 0; bottom: 0;'
333 });
334
335 return config;
336 },
337
338 // @private
339 initialize: function() {
340 var me = this;
341 me.callParent();
342
343 me.element.on({
344 submit: 'onSubmit',
345 scope : me
346 });
347 },
348
349 applyEnctype: function(newValue) {
350 var form = this.element.dom || null;
351 if(form) {
352 if (newValue) {
353 form.setAttribute("enctype", newValue);
354 } else {
355 form.setAttribute("enctype");
356 }
357 }
358 },
359
360 updateRecord: function(newRecord) {
361 var fields, values, name;
362
363 if (newRecord && (fields = newRecord.fields)) {
364 values = this.getValues();
365 for (name in values) {
366 if (values.hasOwnProperty(name) && fields.containsKey(name)) {
367 newRecord.set(name, values[name]);
368 }
369 }
370 }
371 return this;
372 },
373
374 /**
375 * Loads matching fields from a model instance into this form.
376 * @param {Ext.data.Model} record The model instance.
377 * @return {Ext.form.Panel} This form.
378 */
379 setRecord: function(record) {
380 var me = this;
381
382 if (record && record.data) {
383 me.setValues(record.data);
384 }
385
386 me._record = record;
387
388 return this;
389 },
390
391 // @private
392 onSubmit: function(e) {
393 var me = this;
394 if (e && !me.getStandardSubmit()) {
395 e.stopEvent();
396 } else {
397 // Stop the submit event on the original for if we are swapping a form in
398 if (me.getEnableSubmissionForm()) {
399 e.stopEvent();
400 }
401 this.submit(null, e);
402 }
403 },
404
405 updateSubmitOnAction: function(newSubmitOnAction) {
406 if (newSubmitOnAction) {
407 this.on({
408 action: 'onFieldAction',
409 scope: this
410 });
411 } else {
412 this.un({
413 action: 'onFieldAction',
414 scope: this
415 });
416 }
417 },
418
419 // @private
420 onFieldAction: function(field) {
421 if (this.getSubmitOnAction()) {
422 field.blur();
423 this.submit();
424 }
425 },
426
427 /**
428 * Performs a Ajax-based submission of form values (if {@link #standardSubmit} is false) or otherwise
429 * executes a standard HTML Form submit action.
430 *
431 * **Notes**
432 *
433 * 1. Only the first parameter is implemented. Put all other parameters inside the first
434 * parameter:
435 *
436 * submit({params: "" ,headers: "" etc.})
437 *
438 * 2. Submit example:
439 *
440 * myForm.submit({
441 * url: 'PostMyData/To',
442 * method: 'Post',
443 * success: function() { Ext.Msg.alert("success"); },
444 * failure: function() { Ext.Msg.alert("error"); }
445 * });
446 *
447 * 3. Parameters and values only submit for a POST and not for a GET.
448 *
449 * @param {Object} options
450 * The configuration when submitting this form.
451 *
452 * The following are the configurations when submitting via Ajax only:
453 *
454 * @param {String} options.url
455 * The url for the action (defaults to the form's {@link #url}).
456 *
457 * @param {String} options.method
458 * The form method to use (defaults to the form's {@link #method}, or POST if not defined).
459 *
460 * @param {Object} options.headers
461 * Request headers to set for the action.
462 *
463 * @param {Boolean} [options.autoAbort=false]
464 * `true` to abort any pending Ajax request prior to submission.
465 * __Note:__ Has no effect when `{@link #standardSubmit}` is enabled.
466 *
467 * @param {Number} options.timeout
468 * The number is seconds the loading will timeout in.
469 *
470 * The following are the configurations when loading via Ajax or Direct:
471 *
472 * @param {String/Object} options.params
473 * The params to pass when submitting this form (defaults to this forms {@link #baseParams}).
474 * Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.
475 *
476 * @param {Boolean} [options.submitDisabled=false]
477 * `true` to submit all fields regardless of disabled state.
478 * __Note:__ Has no effect when `{@link #standardSubmit}` is enabled.
479 *
480 * @param {String/Object} [options.waitMsg]
481 * If specified, the value which is passed to the loading {@link #masked mask}. See {@link #masked} for
482 * more information.
483 *
484 * @param {Function} options.success
485 * The callback that will be invoked after a successful response. A response is successful if
486 * a response is received from the server and is a JSON object where the `success` property is set
487 * to `true`, `{"success": true}`.
488 *
489 * The function is passed the following parameters and can be used for submitting via Ajax or Direct:
490 *
491 * @param {Ext.form.Panel} options.success.form
492 * The {@link Ext.form.Panel} that requested the action.
493 *
494 * @param {Object/Ext.direct.Event} options.success.result
495 * The result object returned by the server as a result of the submit request. If the submit is sent using Ext.Direct,
496 * this will return the {@link Ext.direct.Event} instance, otherwise will return an Object.
497 *
498 * @param {Object} options.success.data
499 * The parsed data returned by the server.
500 *
501 * @param {Function} options.failure
502 * The callback that will be invoked after a failed transaction attempt.
503 *
504 * The function is passed the following parameters and can be used for submitting via Ajax or Direct:
505 *
506 * @param {Ext.form.Panel} options.failure.form
507 * The {@link Ext.form.Panel} that requested the submit.
508 *
509 * @param {Ext.form.Panel} options.failure.result
510 * The failed response or result object returned by the server which performed the operation.
511 *
512 * @param {Object} options.success.data
513 * The parsed data returned by the server.
514 *
515 * @param {Object} options.scope
516 * The scope in which to call the callback functions (The `this` reference for the callback functions).
517 *
518 * @return {Ext.data.Connection} The request object if the {@link #standardSubmit} config is false.
519 * If the standardSubmit config is true, then the return value is undefined.
520 */
521 submit: function(options, e) {
522 options = options || {};
523
524 var me = this,
525 formValues = me.getValues(me.getStandardSubmit() || !options.submitDisabled),
526 form = me.element.dom || {};
527
528 if(this.getEnableSubmissionForm()) {
529 form = this.createSubmissionForm(form, formValues);
530 }
531
532 options = Ext.apply({
533 url : me.getUrl() || form.action,
534 submit: false,
535 form: form,
536 method : me.getMethod() || form.method || 'post',
537 autoAbort : false,
538 params : null,
539 waitMsg : null,
540 headers : null,
541 success : null,
542 failure : null
543 }, options || {});
544
545 return me.fireAction('beforesubmit', [me, formValues, options, e], 'doBeforeSubmit');
546 },
547
548 createSubmissionForm: function(form, values) {
549 var fields = this.getFields(),
550 name, input, field, fileinputElement, inputComponent;
551
552 if(form.nodeType === 1) {
553 form = form.cloneNode(false);
554
555 for (name in values) {
556 input = document.createElement("input");
557 input.setAttribute("type", "text");
558 input.setAttribute("name", name);
559 input.setAttribute("value", values[name]);
560 form.appendChild(input);
561 }
562 }
563
564 for (name in fields) {
565 if (fields.hasOwnProperty(name)) {
566 field = fields[name];
567 if(field.isFile) {
568 if(!form.$fileswap) form.$fileswap = [];
569
570 inputComponent = field.getComponent().input;
571 fileinputElement = inputComponent.dom;
572 input = fileinputElement.cloneNode(true);
573 fileinputElement.parentNode.insertBefore(input, fileinputElement.nextSibling);
574 form.appendChild(fileinputElement);
575 form.$fileswap.push({original: fileinputElement, placeholder: input});
576 } else if(field.isPassword) {
577 if(field.getComponent().getType !== "password") {
578 field.setRevealed(false);
579 }
580 }
581 }
582 }
583
584 return form;
585 },
586
587 doBeforeSubmit: function(me, formValues, options) {
588 var form = options.form || {},
589 multipartDetected = false;
590
591 if(this.getMultipartDetection() === true) {
592 this.getFieldsAsArray().forEach(function(field) {
593 if(field.isFile === true) {
594 multipartDetected = true;
595 return false;
596 }
597 });
598
599 if(multipartDetected) {
600 form.setAttribute("enctype", "multipart/form-data");
601 }
602 }
603
604 if(options.enctype) {
605 form.setAttribute("enctype", options.enctype);
606 }
607
608 if (me.getStandardSubmit()) {
609 if (options.url && Ext.isEmpty(form.action)) {
610 form.action = options.url;
611 }
612
613 // Spinner fields must have their components enabled *before* submitting or else the value
614 // will not be posted.
615 var fields = this.query('spinnerfield'),
616 ln = fields.length,
617 i, field;
618
619 for (i = 0; i < ln; i++) {
620 field = fields[i];
621 if (!field.getDisabled()) {
622 field.getComponent().setDisabled(false);
623 }
624 }
625
626 form.method = (options.method || form.method).toLowerCase();
627 form.submit();
628 } else {
629 var api = me.getApi(),
630 url = options.url || me.getUrl(),
631 scope = options.scope || me,
632 waitMsg = options.waitMsg,
633 failureFn = function(response, responseText) {
634 if (Ext.isFunction(options.failure)) {
635 options.failure.call(scope, me, response, responseText);
636 }
637
638 me.fireEvent('exception', me, response);
639 },
640 successFn = function(response, responseText) {
641 if (Ext.isFunction(options.success)) {
642 options.success.call(options.scope || me, me, response, responseText);
643 }
644
645 me.fireEvent('submit', me, response);
646 },
647 submit;
648
649 if (options.waitMsg) {
650 if (typeof waitMsg === 'string') {
651 waitMsg = {
652 xtype : 'loadmask',
653 message : waitMsg
654 };
655 }
656
657 me.setMasked(waitMsg);
658 }
659
660 if (api) {
661 submit = api.submit;
662
663 if (typeof submit === 'string') {
664 submit = Ext.direct.Manager.parseMethod(submit);
665
666 if (submit) {
667 api.submit = submit;
668 }
669 }
670
671 if (submit) {
672 return submit(this.element, function(data, response, success) {
673 me.setMasked(false);
674
675 if (success) {
676 if (data.success) {
677 successFn(response, data);
678 } else {
679 failureFn(response, data);
680 }
681 } else {
682 failureFn(response, data);
683 }
684 }, this);
685 }
686 } else {
687 var request = Ext.merge({},
688 {
689 url: url,
690 timeout: this.getTimeout() * 1000,
691 form: form,
692 scope: me
693 },
694 options
695 );
696 delete request.success;
697 delete request.failure;
698
699 request.params = Ext.merge(me.getBaseParams() || {}, options.params);
700 request.header = Ext.apply(
701 {
702 'Content-Type' : 'application/x-www-form-urlencoded; charset=UTF-8'
703 },
704 options.headers || {}
705 );
706 request.callback = function(callbackOptions, success, response) {
707 var responseText = response.responseText,
708 responseXML = response.responseXML,
709 statusResult = Ext.Ajax.parseStatus(response.status, response);
710
711 if(form.$fileswap) {
712 var original, placeholder;
713 Ext.each(form.$fileswap, function(item) {
714 original = item.original;
715 placeholder = item.placeholder;
716
717 placeholder.parentNode.insertBefore(original, placeholder.nextSibling);
718 placeholder.parentNode.removeChild(placeholder);
719 });
720 form.$fileswap = null;
721 delete form.$fileswap;
722 }
723
724 me.setMasked(false);
725
726 if(response.success === false) success = false;
727 if (success) {
728 if (statusResult && responseText && responseText.length == 0) {
729 success = true;
730 } else {
731 if(!Ext.isEmpty(response.responseBytes)) {
732 success = statusResult.success;
733 }else {
734 if(Ext.isString(responseText) && response.request.options.responseType === "text") {
735 response.success = true;
736 } else if(Ext.isString(responseText)) {
737 try {
738 response = Ext.decode(responseText);
739 }catch (e){
740 response.success = false;
741 response.error = e;
742 response.message = e.message;
743 }
744 } else if(Ext.isSimpleObject(responseText)) {
745 response = responseText;
746 Ext.applyIf(response, {success:true});
747 }
748
749 if(!Ext.isEmpty(responseXML)){
750 response.success = true;
751 }
752 success = !!response.success;
753 }
754 }
755 if (success) {
756 successFn(response, responseText);
757 } else {
758 failureFn(response, responseText);
759 }
760 }
761 else {
762 failureFn(response, responseText);
763 }
764 };
765
766 if(Ext.feature.has.XHR2 && request.xhr2) {
767 delete request.form;
768 var formData = new FormData(form);
769 if (request.params) {
770 Ext.iterate(request.params, function(name, value) {
771 if (Ext.isArray(value)) {
772 Ext.each(value, function(v) {
773 formData.append(name, v);
774 });
775 } else {
776 formData.append(name, value);
777 }
778 });
779 delete request.params;
780 }
781 request.data = formData;
782 }
783
784 return Ext.Ajax.request(request);
785 }
786 }
787 },
788
789 /**
790 * Performs an Ajax or Ext.Direct call to load values for this form.
791 *
792 * @param {Object} options
793 * The configuration when loading this form.
794 *
795 * The following are the configurations when loading via Ajax only:
796 *
797 * @param {String} options.url
798 * The url for the action (defaults to the form's {@link #url}).
799 *
800 * @param {String} options.method
801 * The form method to use (defaults to the form's {@link #method}, or GET if not defined).
802 *
803 * @param {Object} options.headers
804 * Request headers to set for the action.
805 *
806 * @param {Number} options.timeout
807 * The number is seconds the loading will timeout in.
808 *
809 * The following are the configurations when loading via Ajax or Direct:
810 *
811 * @param {Boolean} [options.autoAbort=false]
812 * `true` to abort any pending Ajax request prior to loading.
813 *
814 * @param {String/Object} options.params
815 * The params to pass when submitting this form (defaults to this forms {@link #baseParams}).
816 * Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.
817 *
818 * @param {String/Object} [options.waitMsg]
819 * If specified, the value which is passed to the loading {@link #masked mask}. See {@link #masked} for
820 * more information.
821 *
822 * @param {Function} options.success
823 * The callback that will be invoked after a successful response. A response is successful if
824 * a response is received from the server and is a JSON object where the `success` property is set
825 * to `true`, `{"success": true}`.
826 *
827 * The function is passed the following parameters and can be used for loading via Ajax or Direct:
828 *
829 * @param {Ext.form.Panel} options.success.form
830 * The {@link Ext.form.Panel} that requested the load.
831 *
832 * @param {Object/Ext.direct.Event} options.success.result
833 * The result object returned by the server as a result of the load request. If the loading was done via Ext.Direct,
834 * will return the {@link Ext.direct.Event} instance, otherwise will return an Object.
835 *
836 * @param {Object} options.success.data
837 * The parsed data returned by the server.
838 *
839 * @param {Function} options.failure
840 * The callback that will be invoked after a failed transaction attempt.
841 *
842 * The function is passed the following parameters and can be used for loading via Ajax or Direct:
843 *
844 * @param {Ext.form.Panel} options.failure.form
845 * The {@link Ext.form.Panel} that requested the load.
846 *
847 * @param {Ext.form.Panel} options.failure.result
848 * The failed response or result object returned by the server which performed the operation.
849 *
850 * @param {Object} options.success.data
851 * The parsed data returned by the server.
852 *
853 * @param {Object} options.scope
854 * The scope in which to call the callback functions (The `this` reference for the callback functions).
855 *
856 * @return {Ext.data.Connection} The request object.
857 */
858 load : function(options) {
859 options = options || {};
860
861 var me = this,
862 api = me.getApi(),
863 url = me.getUrl() || options.url,
864 waitMsg = options.waitMsg,
865 successFn = function(response, data) {
866 me.setValues(data.data);
867
868 if (Ext.isFunction(options.success)) {
869 options.success.call(options.scope || me, me, response, data);
870 }
871
872 me.fireEvent('load', me, response);
873 },
874 failureFn = function(response, data) {
875 if (Ext.isFunction(options.failure)) {
876 options.failure.call(scope, me, response, data);
877 }
878
879 me.fireEvent('exception', me, response);
880 },
881 load, method, args;
882
883 if (options.waitMsg) {
884 if (typeof waitMsg === 'string') {
885 waitMsg = {
886 xtype : 'loadmask',
887 message : waitMsg
888 };
889 }
890
891 me.setMasked(waitMsg);
892 }
893
894 if (api) {
895 load = api.load;
896
897 if (typeof load === 'string') {
898 load = Ext.direct.Manager.parseMethod(load);
899
900 if (load) {
901 api.load = load;
902 }
903 }
904
905 if (load) {
906 method = load.directCfg.method;
907 args = method.getArgs(me.getParams(options.params), me.getParamOrder(), me.getParamsAsHash());
908
909 args.push(function(data, response, success) {
910 me.setMasked(false);
911
912 if (success) {
913 successFn(response, data);
914 } else {
915 failureFn(response, data);
916 }
917 }, me);
918
919 return load.apply(window, args);
920 }
921 } else if (url) {
922 return Ext.Ajax.request({
923 url: url,
924 timeout: (options.timeout || this.getTimeout()) * 1000,
925 method: options.method || 'GET',
926 autoAbort: options.autoAbort,
927 headers: Ext.apply(
928 {
929 'Content-Type' : 'application/x-www-form-urlencoded; charset=UTF-8'
930 },
931 options.headers || {}
932 ),
933 callback: function(callbackOptions, success, response) {
934 var responseText = response.responseText,
935 statusResult = Ext.Ajax.parseStatus(response.status, response);
936
937 me.setMasked(false);
938
939 if (success) {
940 if (statusResult && responseText.length == 0) {
941 success = true;
942 } else {
943 response = Ext.decode(responseText);
944 success = !!response.success;
945 }
946 if (success) {
947 successFn(response, responseText);
948 } else {
949 failureFn(response, responseText);
950 }
951 }
952 else {
953 failureFn(response, responseText);
954 }
955 }
956 });
957 }
958 },
959
960 //@private
961 getParams : function(params) {
962 return Ext.apply({}, params, this.getBaseParams());
963 },
964
965 /**
966 * Sets the values of form fields in bulk. Example usage:
967 *
968 * myForm.setValues({
969 * name: 'Ed',
970 * crazy: true,
971 * username: 'edspencer'
972 * });
973 *
974 * If there groups of checkbox fields with the same name, pass their values in an array. For example:
975 *
976 * myForm.setValues({
977 * name: 'Jacky',
978 * crazy: false,
979 * hobbies: [
980 * 'reading',
981 * 'cooking',
982 * 'gaming'
983 * ]
984 * });
985 *
986 * @param {Object} values field name => value mapping object.
987 * @return {Ext.form.Panel} This form.
988 */
989 setValues: function(values) {
990 var fields = this.getFields(),
991 me = this,
992 name, field, value, ln, i, f;
993
994 values = values || {};
995
996 for (name in values) {
997 if (values.hasOwnProperty(name)) {
998 field = fields[name];
999 value = values[name];
1000
1001 if (field) {
1002 // If there are multiple fields with the same name. Checkboxes, radio fields and maybe event just normal fields..
1003 if (Ext.isArray(field)) {
1004 ln = field.length;
1005
1006 // Loop through each of the fields
1007 for (i = 0; i < ln; i++) {
1008 f = field[i];
1009
1010 if (f.isRadio) {
1011 // If it is a radio field just use setGroupValue which will handle all of the radio fields
1012 f.setGroupValue(value);
1013 break;
1014 } else if (f.isCheckbox) {
1015 if (Ext.isArray(value)) {
1016 f.setChecked((value.indexOf(f._value) != -1));
1017 } else {
1018 f.setChecked((value == f._value));
1019 }
1020 } else {
1021 // If it is a bunch of fields with the same name, check if the value is also an array, so we can map it
1022 // to each field
1023 if (Ext.isArray(value)) {
1024 f.setValue(value[i]);
1025 }
1026 }
1027 }
1028 } else {
1029 if (field.isRadio || field.isCheckbox) {
1030 // If the field is a radio or a checkbox
1031 field.setChecked(value);
1032 } else {
1033 // If just a normal field
1034 field.setValue(value);
1035 }
1036 }
1037
1038 if (me.getTrackResetOnLoad()) {
1039 field.resetOriginalValue();
1040 }
1041 }
1042 }
1043 }
1044
1045 return this;
1046 },
1047
1048 /**
1049 * Returns an object containing the value of each field in the form, keyed to the field's name.
1050 * For groups of checkbox fields with the same name, it will be arrays of values. For example:
1051 *
1052 * {
1053 * name: "Jacky Nguyen", // From a TextField
1054 * favorites: [
1055 * 'pizza',
1056 * 'noodle',
1057 * 'cake'
1058 * ]
1059 * }
1060 *
1061 * @param {Boolean} [enabled] `true` to return only enabled fields.
1062 * @param {Boolean} [all] `true` to return all fields even if they don't have a
1063 * {@link Ext.field.Field#name name} configured.
1064 * @return {Object} Object mapping field name to its value.
1065 */
1066 getValues: function(enabled, all) {
1067 var fields = this.getFields(),
1068 values = {},
1069 isArray = Ext.isArray,
1070 field, value, addValue, bucket, name, ln, i;
1071
1072 // Function which you give a field and a name, and it will add it into the values
1073 // object accordingly
1074 addValue = function(field, name) {
1075 if (!all && (!name || name === 'null') || field.isFile) {
1076 return;
1077 }
1078
1079 if (field.isCheckbox) {
1080 value = field.getSubmitValue();
1081 } else {
1082 value = field.getValue();
1083 }
1084
1085
1086 if (!(enabled && field.getDisabled())) {
1087 // RadioField is a special case where the value returned is the fields valUE
1088 // ONLY if it is checked
1089 if (field.isRadio) {
1090 if (field.isChecked()) {
1091 values[name] = value;
1092 }
1093 } else {
1094 // Check if the value already exists
1095 bucket = values[name];
1096 if (!Ext.isEmpty(bucket)) {
1097 // if it does and it isn't an array, we need to make it into an array
1098 // so we can push more
1099 if (!isArray(bucket)) {
1100 bucket = values[name] = [bucket];
1101 }
1102
1103 // Check if it is an array
1104 if (isArray(value)) {
1105 // Concat it into the other values
1106 bucket = values[name] = bucket.concat(value);
1107 } else {
1108 // If it isn't an array, just pushed more values
1109 bucket.push(value);
1110 }
1111 } else {
1112 values[name] = value;
1113 }
1114 }
1115 }
1116 };
1117
1118 // Loop through each of the fields, and add the values for those fields.
1119 for (name in fields) {
1120 if (fields.hasOwnProperty(name)) {
1121 field = fields[name];
1122
1123 if (isArray(field)) {
1124 ln = field.length;
1125 for (i = 0; i < ln; i++) {
1126 addValue(field[i], name);
1127 }
1128 } else {
1129 addValue(field, name);
1130 }
1131 }
1132 }
1133
1134 return values;
1135 },
1136
1137 /**
1138 * Resets all fields in the form back to their original values.
1139 * @return {Ext.form.Panel} This form.
1140 */
1141 reset: function() {
1142 this.getFieldsAsArray().forEach(function(field) {
1143 field.reset();
1144 });
1145
1146 return this;
1147 },
1148
1149 /**
1150 * A convenient method to disable all fields in this form.
1151 * @return {Ext.form.Panel} This form.
1152 */
1153 doSetDisabled: function(newDisabled) {
1154 this.getFieldsAsArray().forEach(function(field) {
1155 field.setDisabled(newDisabled);
1156 });
1157
1158 return this;
1159 },
1160
1161 /**
1162 * @private
1163 */
1164 getFieldsAsArray: function() {
1165 var fields = [],
1166 getFieldsFrom = function(item) {
1167 if (item.isField) {
1168 fields.push(item);
1169 }
1170
1171 if (item.isContainer) {
1172 item.getItems().each(getFieldsFrom);
1173 }
1174 };
1175
1176 this.getItems().each(getFieldsFrom);
1177
1178 return fields;
1179 },
1180
1181 /**
1182 * Returns all {@link Ext.field.Field field} instances inside this form.
1183 * @param {Boolean} byName return only fields that match the given name, otherwise return all fields.
1184 * @return {Object/Array} All field instances, mapped by field name; or an array if `byName` is passed.
1185 */
1186 getFields: function(byName) {
1187 var fields = {},
1188 itemName;
1189
1190 var getFieldsFrom = function(item) {
1191 if (item.isField) {
1192 itemName = item.getName();
1193
1194 if ((byName && itemName == byName) || typeof byName == 'undefined') {
1195 if (fields.hasOwnProperty(itemName)) {
1196 if (!Ext.isArray(fields[itemName])) {
1197 fields[itemName] = [fields[itemName]];
1198 }
1199
1200 fields[itemName].push(item);
1201 } else {
1202 fields[itemName] = item;
1203 }
1204 }
1205
1206 }
1207
1208 if (item.isContainer) {
1209 item.items.each(getFieldsFrom);
1210 }
1211 };
1212
1213 this.getItems().each(getFieldsFrom);
1214
1215 return (byName) ? (fields[byName] || []) : fields;
1216 },
1217
1218 /**
1219 * Returns an array of fields in this formpanel.
1220 * @return {Ext.field.Field[]} An array of fields in this form panel.
1221 * @private
1222 */
1223 getFieldsArray: function() {
1224 var fields = [];
1225
1226 var getFieldsFrom = function(item) {
1227 if (item.isField) {
1228 fields.push(item);
1229 }
1230
1231 if (item.isContainer) {
1232 item.items.each(getFieldsFrom);
1233 }
1234 };
1235
1236 this.items.each(getFieldsFrom);
1237
1238 return fields;
1239 },
1240
1241 getFieldsFromItem: Ext.emptyFn,
1242
1243 /**
1244 * Shows a generic/custom mask over a designated Element.
1245 * @param {String/Object} cfg Either a string message or a configuration object supporting
1246 * the following options:
1247 *
1248 * {
1249 * message : 'Please Wait',
1250 * cls : 'form-mask'
1251 * }
1252 *
1253 * @param {Object} target
1254 * @return {Ext.form.Panel} This form
1255 * @deprecated 2.0.0 Please use {@link #setMasked} instead.
1256 */
1257 showMask: function(cfg, target) {
1258 //<debug>
1259 Ext.Logger.warn('showMask is now deprecated. Please use Ext.form.Panel#setMasked instead');
1260 //</debug>
1261
1262 cfg = Ext.isObject(cfg) ? cfg.message : cfg;
1263
1264 if (cfg) {
1265 this.setMasked({
1266 xtype: 'loadmask',
1267 message: cfg
1268 });
1269 } else {
1270 this.setMasked(true);
1271 }
1272
1273 return this;
1274 },
1275
1276 /**
1277 * Hides a previously shown wait mask (See {@link #showMask}).
1278 * @return {Ext.form.Panel} this
1279 * @deprecated 2.0.0 Please use {@link #unmask} or {@link #setMasked} instead.
1280 */
1281 hideMask: function() {
1282 this.setMasked(false);
1283 return this;
1284 },
1285
1286 /**
1287 * Returns the currently focused field
1288 * @return {Ext.field.Field} The currently focused field, if one is focused or `null`.
1289 * @private
1290 */
1291 getFocusedField: function() {
1292 var fields = this.getFieldsArray(),
1293 ln = fields.length,
1294 field, i;
1295
1296 for (i = 0; i < ln; i++) {
1297 field = fields[i];
1298 if (field.isFocused) {
1299 return field;
1300 }
1301 }
1302
1303 return null;
1304 },
1305
1306 /**
1307 * @return {Boolean/Ext.field.Field} The next field if one exists, or `false`.
1308 * @private
1309 */
1310 getNextField: function() {
1311 var fields = this.getFieldsArray(),
1312 focusedField = this.getFocusedField(),
1313 index;
1314
1315 if (focusedField) {
1316 index = fields.indexOf(focusedField);
1317
1318 if (index !== fields.length - 1) {
1319 index++;
1320 return fields[index];
1321 }
1322 }
1323
1324 return false;
1325 },
1326
1327 /**
1328 * Tries to focus the next field in the form, if there is currently a focused field.
1329 * @return {Boolean/Ext.field.Field} The next field that was focused, or `false`.
1330 * @private
1331 */
1332 focusNextField: function() {
1333 var field = this.getNextField();
1334 if (field) {
1335 field.focus();
1336 return field;
1337 }
1338
1339 return false;
1340 },
1341
1342 /**
1343 * @private
1344 * @return {Boolean/Ext.field.Field} The next field if one exists, or `false`.
1345 */
1346 getPreviousField: function() {
1347 var fields = this.getFieldsArray(),
1348 focusedField = this.getFocusedField(),
1349 index;
1350
1351 if (focusedField) {
1352 index = fields.indexOf(focusedField);
1353
1354 if (index !== 0) {
1355 index--;
1356 return fields[index];
1357 }
1358 }
1359
1360 return false;
1361 },
1362
1363 /**
1364 * Tries to focus the previous field in the form, if there is currently a focused field.
1365 * @return {Boolean/Ext.field.Field} The previous field that was focused, or `false`.
1366 * @private
1367 */
1368 focusPreviousField: function() {
1369 var field = this.getPreviousField();
1370 if (field) {
1371 field.focus();
1372 return field;
1373 }
1374
1375 return false;
1376 }
1377 }, function() {
1378
1379 //<deprecated product=touch since=2.0>
1380 Ext.deprecateClassMethod(this, {
1381 /**
1382 * @method
1383 * @inheritdoc Ext.form.Panel#setRecord
1384 * @deprecated 2.0.0 Please use #setRecord instead.
1385 */
1386 loadRecord: 'setRecord',
1387 /**
1388 * @method
1389 * @inheritdoc Ext.form.Panel#setRecord
1390 * @deprecated 2.0.0 Please use #setRecord instead.
1391 */
1392 loadModel: 'setRecord'
1393 });
1394
1395 this.override({
1396 constructor: function(config) {
1397 /**
1398 * @cfg {Ext.XTemplate/String/String[]} waitTpl
1399 * The defined waitMsg template. Used for precise control over the masking agent used
1400 * to mask the FormPanel (or other Element) during form Ajax/submission actions. For more options, see
1401 * {@link #showMask} method.
1402 * @removed 2.0.0 Please use a custom {@link Ext.LoadMask} class and the {@link #masked} configuration
1403 * when {@link #method submitting} your form.
1404 */
1405
1406 /**
1407 * @cfg {Ext.dom.Element} waitMsgTarget The target of any mask shown on this form.
1408 * @removed 2.0.0 There is no need to set a mask target anymore. Please see the {@link #masked} configuration instead.
1409 */
1410 if (config && config.hasOwnProperty('waitMsgTarget')) {
1411 delete config.waitMsgTarget;
1412 }
1413
1414 this.callParent([config]);
1415 }
1416 });
1417 //</deprecated>
1418 });