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     /**
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 : undefined,
125
126     // private
127     initEl : function(el){
128         this.el = Roo.get(el);
129         this.id = this.el.id || Roo.id();
130         this.el.on('submit', this.onSubmit, this);
131         this.el.addClass('x-form');
132     },
133
134     // private
135     onSubmit : function(e){
136         e.stopEvent();
137     },
138
139     /**
140      * Returns true if client-side validation on the form is successful.
141      * @return Boolean
142      */
143     isValid : function(){
144         var valid = true;
145         this.items.each(function(f){
146            if(!f.validate()){
147                valid = false;
148            }
149         });
150         return valid;
151     },
152
153     /**
154      * Returns true if any fields in this form have changed since their original load.
155      * @return Boolean
156      */
157     isDirty : function(){
158         var dirty = false;
159         this.items.each(function(f){
160            if(f.isDirty()){
161                dirty = true;
162                return false;
163            }
164         });
165         return dirty;
166     },
167
168     /**
169      * Performs a predefined action (submit or load) or custom actions you define on this form.
170      * @param {String} actionName The name of the action type
171      * @param {Object} options (optional) The options to pass to the action.  All of the config options listed
172      * below are supported by both the submit and load actions unless otherwise noted (custom actions could also
173      * accept other config options):
174      * <pre>
175 Property          Type             Description
176 ----------------  ---------------  ----------------------------------------------------------------------------------
177 url               String           The url for the action (defaults to the form's url)
178 method            String           The form method to use (defaults to the form's method, or POST if not defined)
179 params            String/Object    The params to pass (defaults to the form's baseParams, or none if not defined)
180 clientValidation  Boolean          Applies to submit only.  Pass true to call form.isValid() prior to posting to
181                                    validate the form on the client (defaults to false)
182      * </pre>
183      * @return {BasicForm} this
184      */
185     doAction : function(action, options){
186         if(typeof action == 'string'){
187             action = new Roo.form.Action.ACTION_TYPES[action](this, options);
188         }
189         if(this.fireEvent('beforeaction', this, action) !== false){
190             this.beforeAction(action);
191             action.run.defer(100, action);
192         }
193         return this;
194     },
195
196     /**
197      * Shortcut to do a submit action.
198      * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
199      * @return {BasicForm} this
200      */
201     submit : function(options){
202         this.doAction('submit', options);
203         return this;
204     },
205
206     /**
207      * Shortcut to do a load action.
208      * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
209      * @return {BasicForm} this
210      */
211     load : function(options){
212         this.doAction('load', options);
213         return this;
214     },
215
216     /**
217      * Persists the values in this form into the passed Roo.data.Record object in a beginEdit/endEdit block.
218      * @param {Record} record The record to edit
219      * @return {BasicForm} this
220      */
221     updateRecord : function(record){
222         record.beginEdit();
223         var fs = record.fields;
224         fs.each(function(f){
225             var field = this.findField(f.name);
226             if(field){
227                 record.set(f.name, field.getValue());
228             }
229         }, this);
230         record.endEdit();
231         return this;
232     },
233
234     /**
235      * Loads an Roo.data.Record into this form.
236      * @param {Record} record The record to load
237      * @return {BasicForm} this
238      */
239     loadRecord : function(record){
240         this.setValues(record.data);
241         return this;
242     },
243
244     // private
245     beforeAction : function(action){
246         var o = action.options;
247         
248         if(typeof(this.waitMsgTarget) == 'undefined'){
249             Roo.get(document.documentElement).mask(o.waitMsg || "Sending", 'x-mask-loading');
250         if(this.waitMsgTarget === true){
251             this.el.mask(o.waitMsg || "Sending", 'x-mask-loading');
252         }else if(this.waitMsgTarget){
253             this.waitMsgTarget = Roo.get(this.waitMsgTarget);
254             this.waitMsgTarget.mask(o.waitMsg || "Sending", 'x-mask-loading');
255         }else {
256             Roo.MessageBox.wait(o.waitMsg || "Sending", o.waitTitle || this.waitTitle || 'Please Wait...');
257         }
258          
259     },
260
261     // private
262     afterAction : function(action, success){
263         this.activeAction = null;
264         var o = action.options;
265         if(o.waitMsg){
266             if(this.waitMsgTarget === true){
267                 this.el.unmask();
268             }else if(this.waitMsgTarget){
269                 this.waitMsgTarget.unmask();
270             }else{
271                 Roo.MessageBox.updateProgress(1);
272                 Roo.MessageBox.hide();
273             }
274         }
275         if(success){
276             if(o.reset){
277                 this.reset();
278             }
279             Roo.callback(o.success, o.scope, [this, action]);
280             this.fireEvent('actioncomplete', this, action);
281             
282         }else{
283             Roo.callback(o.failure, o.scope, [this, action]);
284             // show an error message if no failed handler is set..
285             if (!this.hasListener('actionfailed')) {
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;