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