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