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