Roo/form/BasicForm.js
[roojs1] / Roo / form / BasicForm.js
1 /*
2  * Based on:
3  * Ext JS Library 1.1.1
4  * Copyright(c) 2006-2007, Ext JS, LLC.
5  *
6  * Originally Released Under LGPL - original licence link has changed is not relivant.
7  *
8  * Fork - LGPL
9  * <script type="text/javascript">
10  */
11  
12 /**
13  * @class Roo.form.BasicForm
14  * @extends Roo.util.Observable
15  * Supplies the functionality to do "actions" on forms and initialize Roo.form.Field types on existing markup.
16  * @constructor
17  * @param {String/HTMLElement/Roo.Element} el The form element or its id
18  * @param {Object} config Configuration options
19  */
20 Roo.form.BasicForm = function(el, config){
21     this.allItems = [];
22     Roo.apply(this, config);
23     /*
24      * The Roo.form.Field items in this form.
25      * @type MixedCollection
26      */
27      
28      
29     this.items = new Roo.util.MixedCollection(false, function(o){
30         return o.id || (o.id = Roo.id());
31     });
32     this.addEvents({
33         /**
34          * @event beforeaction
35          * Fires before any action is performed. Return false to cancel the action.
36          * @param {Form} this
37          * @param {Action} action The action to be performed
38          */
39         beforeaction: true,
40         /**
41          * @event actionfailed
42          * Fires when an action fails.
43          * @param {Form} this
44          * @param {Action} action The action that failed
45          */
46         actionfailed : true,
47         /**
48          * @event actioncomplete
49          * Fires when an action is completed.
50          * @param {Form} this
51          * @param {Action} action The action that completed
52          */
53         actioncomplete : true
54     });
55     if(el){
56         this.initEl(el);
57     }
58     Roo.form.BasicForm.superclass.constructor.call(this);
59 };
60
61 Roo.extend(Roo.form.BasicForm, Roo.util.Observable, {
62     /**
63      * @cfg {String} method
64      * The request method to use (GET or POST) for form actions if one isn't supplied in the action options.
65      */
66     /**
67      * @cfg {DataReader} reader
68      * An Roo.data.DataReader (e.g. {@link Roo.data.XmlReader}) to be used to read data when executing "load" actions.
69      * This is optional as there is built-in support for processing JSON.
70      */
71     /**
72      * @cfg {DataReader} errorReader
73      * An Roo.data.DataReader (e.g. {@link Roo.data.XmlReader}) to be used to read data when reading validation errors on "submit" actions.
74      * This is completely optional as there is built-in support for processing JSON.
75      */
76     /**
77      * @cfg {String} url
78      * The URL to use for form actions if one isn't supplied in the action options.
79      */
80     /**
81      * @cfg {Boolean} fileUpload
82      * Set to true if this form is a file upload.
83      */
84     /**
85      * @cfg {Object} baseParams
86      * Parameters to pass with all requests. e.g. baseParams: {id: '123', foo: 'bar'}.
87      */
88     /**
89      * @cfg {Number} timeout Timeout for form actions in seconds (default is 30 seconds).
90      */
91     timeout: 30,
92
93     // private
94     activeAction : null,
95
96     /**
97      * @cfg {Boolean} trackResetOnLoad If set to true, form.reset() resets to the last loaded
98      * or setValues() data instead of when the form was first created.
99      */
100     trackResetOnLoad : false,
101     
102     
103     /**
104      * childForms - used for multi-tab forms
105      * @type {Array}
106      */
107     childForms : false,
108     
109     /**
110      * allFields - full list of fields.
111      * @type {Array}
112      */
113     allFields : false,
114     
115     /**
116      * By default wait messages are displayed with Roo.MessageBox.wait. You can target a specific
117      * element by passing it or its id or mask the form itself by passing in true.
118      * @type Mixed
119      */
120     waitMsgTarget : undefined,
121
122     // private
123     initEl : function(el){
124         this.el = Roo.get(el);
125         this.id = this.el.id || Roo.id();
126         this.el.on('submit', this.onSubmit, this);
127         this.el.addClass('x-form');
128     },
129
130     // private
131     onSubmit : function(e){
132         e.stopEvent();
133     },
134
135     /**
136      * Returns true if client-side validation on the form is successful.
137      * @return Boolean
138      */
139     isValid : function(){
140         var valid = true;
141         this.items.each(function(f){
142            if(!f.validate()){
143                valid = false;
144            }
145         });
146         return valid;
147     },
148
149     /**
150      * Returns true if any fields in this form have changed since their original load.
151      * @return Boolean
152      */
153     isDirty : function(){
154         var dirty = false;
155         this.items.each(function(f){
156            if(f.isDirty()){
157                dirty = true;
158                return false;
159            }
160         });
161         return dirty;
162     },
163
164     /**
165      * Performs a predefined action (submit or load) or custom actions you define on this form.
166      * @param {String} actionName The name of the action type
167      * @param {Object} options (optional) The options to pass to the action.  All of the config options listed
168      * below are supported by both the submit and load actions unless otherwise noted (custom actions could also
169      * accept other config options):
170      * <pre>
171 Property          Type             Description
172 ----------------  ---------------  ----------------------------------------------------------------------------------
173 url               String           The url for the action (defaults to the form's url)
174 method            String           The form method to use (defaults to the form's method, or POST if not defined)
175 params            String/Object    The params to pass (defaults to the form's baseParams, or none if not defined)
176 clientValidation  Boolean          Applies to submit only.  Pass true to call form.isValid() prior to posting to
177                                    validate the form on the client (defaults to false)
178      * </pre>
179      * @return {BasicForm} this
180      */
181     doAction : function(action, options){
182         if(typeof action == 'string'){
183             action = new Roo.form.Action.ACTION_TYPES[action](this, options);
184         }
185         if(this.fireEvent('beforeaction', this, action) !== false){
186             this.beforeAction(action);
187             action.run.defer(100, action);
188         }
189         return this;
190     },
191
192     /**
193      * Shortcut to do a submit action.
194      * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
195      * @return {BasicForm} this
196      */
197     submit : function(options){
198         this.doAction('submit', options);
199         return this;
200     },
201
202     /**
203      * Shortcut to do a load action.
204      * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
205      * @return {BasicForm} this
206      */
207     load : function(options){
208         this.doAction('load', options);
209         return this;
210     },
211
212     /**
213      * Persists the values in this form into the passed Roo.data.Record object in a beginEdit/endEdit block.
214      * @param {Record} record The record to edit
215      * @return {BasicForm} this
216      */
217     updateRecord : function(record){
218         record.beginEdit();
219         var fs = record.fields;
220         fs.each(function(f){
221             var field = this.findField(f.name);
222             if(field){
223                 record.set(f.name, field.getValue());
224             }
225         }, this);
226         record.endEdit();
227         return this;
228     },
229
230     /**
231      * Loads an Roo.data.Record into this form.
232      * @param {Record} record The record to load
233      * @return {BasicForm} this
234      */
235     loadRecord : function(record){
236         this.setValues(record.data);
237         return this;
238     },
239
240     // private
241     beforeAction : function(action){
242         var o = action.options;
243         if(o.waitMsg){
244             if(this.waitMsgTarget === true){
245                 this.el.mask(o.waitMsg, 'x-mask-loading');
246             }else if(this.waitMsgTarget){
247                 this.waitMsgTarget = Roo.get(this.waitMsgTarget);
248                 this.waitMsgTarget.mask(o.waitMsg, 'x-mask-loading');
249             }else{
250                 Roo.MessageBox.wait(o.waitMsg, o.waitTitle || this.waitTitle || 'Please Wait...');
251             }
252         }
253     },
254
255     // private
256     afterAction : function(action, success){
257         this.activeAction = null;
258         var o = action.options;
259         if(o.waitMsg){
260             if(this.waitMsgTarget === true){
261                 this.el.unmask();
262             }else if(this.waitMsgTarget){
263                 this.waitMsgTarget.unmask();
264             }else{
265                 Roo.MessageBox.updateProgress(1);
266                 Roo.MessageBox.hide();
267             }
268         }
269         if(success){
270             if(o.reset){
271                 this.reset();
272             }
273             Roo.callback(o.success, o.scope, [this, action]);
274             this.fireEvent('actioncomplete', this, action);
275         }else{
276             Roo.callback(o.failure, o.scope, [this, action]);
277             this.fireEvent('actionfailed', this, action);
278         }
279     },
280
281     /**
282      * Find a Roo.form.Field in this form by id, dataIndex, name or hiddenName
283      * @param {String} id The value to search for
284      * @return Field
285      */
286     findField : function(id){
287         var field = this.items.get(id);
288         if(!field){
289             this.items.each(function(f){
290                 if(f.isFormField && (f.dataIndex == id || f.id == id || f.getName() == id)){
291                     field = f;
292                     return false;
293                 }
294             });
295         }
296         return field || null;
297     },
298
299     /**
300      * Add a secondary form to this one, 
301      * Used to provide tabbed forms. One form is primary, with hidden values 
302      * which mirror the elements from the other forms.
303      * 
304      * @param {Roo.form.Form} form to add.
305      * 
306      */
307     addForm : function(form){
308        
309         this.childForms.push(form);
310         form.allItems.each(function (fe) {
311             
312             if (this.findField(fe.name)) { // already added..
313                 return;
314             }
315             this.add( new Roo.form.Hidden({
316                 name : fe.name
317             }));
318         }, this);
319         
320     },
321     /**
322      * Mark fields in this form invalid in bulk.
323      * @param {Array/Object} errors Either an array in the form [{id:'fieldId', msg:'The message'},...] or an object hash of {id: msg, id2: msg2}
324      * @return {BasicForm} this
325      */
326     markInvalid : function(errors){
327         if(errors instanceof Array){
328             for(var i = 0, len = errors.length; i < len; i++){
329                 var fieldError = errors[i];
330                 var f = this.findField(fieldError.id);
331                 if(f){
332                     f.markInvalid(fieldError.msg);
333                 }
334             }
335         }else{
336             var field, id;
337             for(id in errors){
338                 if(typeof errors[id] != 'function' && (field = this.findField(id))){
339                     field.markInvalid(errors[id]);
340                 }
341             }
342         }
343         Roo.each(this.childForms || [], function (f) {
344             f.markInvalid(errors);
345         });
346         
347         return this;
348     },
349
350     /**
351      * Set values for fields in this form in bulk.
352      * @param {Array/Object} values Either an array in the form [{id:'fieldId', value:'foo'},...] or an object hash of {id: value, id2: value2}
353      * @return {BasicForm} this
354      */
355     setValues : function(values){
356         if(values instanceof Array){ // array of objects
357             for(var i = 0, len = values.length; i < len; i++){
358                 var v = values[i];
359                 var f = this.findField(v.id);
360                 if(f){
361                     f.setValue(v.value);
362                     if(this.trackResetOnLoad){
363                         f.originalValue = f.getValue();
364                     }
365                 }
366             }
367         }else{ // object hash
368             var field, id;
369             for(id in values){
370                 if(typeof values[id] != 'function' && (field = this.findField(id))){
371                     
372                     if (field.setFromData && 
373                         field.valueField && 
374                         field.displayField &&
375                         // combos' with local stores can 
376                         // be queried via setValue()
377                         // to set their value..
378                         (field.store && !field.store.isLocal)
379                         ) {
380                         // it's a combo
381                         var sd = { };
382                         sd[field.valueField] = typeof(values[field.hiddenName]) == 'undefined' ? '' : values[field.hiddenName];
383                         sd[field.displayField] = typeof(values[field.name]) == 'undefined' ? '' : values[field.name];
384                         field.setFromData(sd);
385                         
386                     } else {
387                         field.setValue(values[id]);
388                     }
389                     
390                     
391                     if(this.trackResetOnLoad){
392                         field.originalValue = field.getValue();
393                     }
394                 }
395             }
396         }
397          
398         Roo.each(this.childForms || [], function (f) {
399             f.setValues(values);
400         });
401                 
402         return this;
403     },
404
405     /**
406      * Returns the fields in this form as an object with key/value pairs. If multiple fields exist with the same name
407      * they are returned as an array.
408      * @param {Boolean} asString
409      * @return {Object}
410      */
411     getValues : function(asString){
412         if (this.childForms) {
413             // copy values from the child forms
414             Roo.each(this.childForms, function (f) {
415                 if (f.allFields) {
416                     Roo.each(f.allFields, function (e) {
417                         if (e.name && e.getValue && this.findField(e.name)) {
418                             this.findField(e.name).setValue(e.getValue());
419                         }
420                     });
421                 }
422             }, this);
423         }
424         
425         
426         
427         var fs = Roo.lib.Ajax.serializeForm(this.el.dom);
428         if(asString === true){
429             return fs;
430         }
431         return Roo.urlDecode(fs);
432     },
433
434     /**
435      * Clears all invalid messages in this form.
436      * @return {BasicForm} this
437      */
438     clearInvalid : function(){
439         this.items.each(function(f){
440            f.clearInvalid();
441         });
442         
443         Roo.each(this.childForms || [], function (f) {
444             f.clearInvalid();
445         });
446         
447         
448         return this;
449     },
450
451     /**
452      * Resets this form.
453      * @return {BasicForm} this
454      */
455     reset : function(){
456         this.items.each(function(f){
457             f.reset();
458         });
459         
460         Roo.each(this.childForms || [], function (f) {
461             f.reset();
462         });
463        
464         
465         return this;
466     },
467
468     /**
469      * Add Roo.form components to this form.
470      * @param {Field} field1
471      * @param {Field} field2 (optional)
472      * @param {Field} etc (optional)
473      * @return {BasicForm} this
474      */
475     add : function(){
476         this.items.addAll(Array.prototype.slice.call(arguments, 0));
477         return this;
478     },
479
480
481     /**
482      * Removes a field from the items collection (does NOT remove its markup).
483      * @param {Field} field
484      * @return {BasicForm} this
485      */
486     remove : function(field){
487         this.items.remove(field);
488         return this;
489     },
490
491     /**
492      * Looks at the fields in this form, checks them for an id attribute,
493      * and calls applyTo on the existing dom element with that id.
494      * @return {BasicForm} this
495      */
496     render : function(){
497         this.items.each(function(f){
498             if(f.isFormField && !f.rendered && document.getElementById(f.id)){ // if the element exists
499                 f.applyTo(f.id);
500             }
501         });
502         return this;
503     },
504
505     /**
506      * Calls {@link Ext#apply} for all fields in this form with the passed object.
507      * @param {Object} values
508      * @return {BasicForm} this
509      */
510     applyToFields : function(o){
511         this.items.each(function(f){
512            Roo.apply(f, o);
513         });
514         return this;
515     },
516
517     /**
518      * Calls {@link Ext#applyIf} for all field in this form with the passed object.
519      * @param {Object} values
520      * @return {BasicForm} this
521      */
522     applyIfToFields : function(o){
523         this.items.each(function(f){
524            Roo.applyIf(f, o);
525         });
526         return this;
527     }
528 });
529
530 // back compat
531 Roo.BasicForm = Roo.form.BasicForm;