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
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         if (this.maskEl) {
292             this.maskEl.unmask();
293         }
294     },
295
296     /**
297      * Find a Roo.form.Field in this form by id, dataIndex, name or hiddenName
298      * @param {String} id The value to search for
299      * @return Field
300      */
301     findField : function(id){
302         var field = this.items.get(id);
303         if(!field){
304             this.items.each(function(f){
305                 if(f.isFormField && (f.dataIndex == id || f.id == id || f.getName() == id)){
306                     field = f;
307                     return false;
308                 }
309             });
310         }
311         return field || null;
312     },
313
314     /**
315      * Add a secondary form to this one, 
316      * Used to provide tabbed forms. One form is primary, with hidden values 
317      * which mirror the elements from the other forms.
318      * 
319      * @param {Roo.form.Form} form to add.
320      * 
321      */
322     addForm : function(form)
323     {
324        
325         if (this.childForms.indexOf(form) > -1) {
326             // already added..
327             return;
328         }
329         this.childForms.push(form);
330         var n = '';
331         Roo.each(form.allItems, function (fe) {
332             
333             n = typeof(fe.getName) == 'undefined' ? fe.name : fe.getName();
334             if (this.findField(n)) { // already added..
335                 return;
336             }
337             var add = new Roo.form.Hidden({
338                 name : n
339             });
340             add.render(this.el);
341             
342             this.add( add );
343         }, this);
344         
345     },
346     /**
347      * Mark fields in this form invalid in bulk.
348      * @param {Array/Object} errors Either an array in the form [{id:'fieldId', msg:'The message'},...] or an object hash of {id: msg, id2: msg2}
349      * @return {BasicForm} this
350      */
351     markInvalid : function(errors){
352         if(errors instanceof Array){
353             for(var i = 0, len = errors.length; i < len; i++){
354                 var fieldError = errors[i];
355                 var f = this.findField(fieldError.id);
356                 if(f){
357                     f.markInvalid(fieldError.msg);
358                 }
359             }
360         }else{
361             var field, id;
362             for(id in errors){
363                 if(typeof errors[id] != 'function' && (field = this.findField(id))){
364                     field.markInvalid(errors[id]);
365                 }
366             }
367         }
368         Roo.each(this.childForms || [], function (f) {
369             f.markInvalid(errors);
370         });
371         
372         return this;
373     },
374
375     /**
376      * Set values for fields in this form in bulk.
377      * @param {Array/Object} values Either an array in the form [{id:'fieldId', value:'foo'},...] or an object hash of {id: value, id2: value2}
378      * @return {BasicForm} this
379      */
380     setValues : function(values){
381         if(values instanceof Array){ // array of objects
382             for(var i = 0, len = values.length; i < len; i++){
383                 var v = values[i];
384                 var f = this.findField(v.id);
385                 if(f){
386                     f.setValue(v.value);
387                     if(this.trackResetOnLoad){
388                         f.originalValue = f.getValue();
389                     }
390                 }
391             }
392         }else{ // object hash
393             var field, id;
394             for(id in values){
395                 if(typeof values[id] != 'function' && (field = this.findField(id))){
396                     
397                     if (field.setFromData && 
398                         field.valueField && 
399                         field.displayField &&
400                         // combos' with local stores can 
401                         // be queried via setValue()
402                         // to set their value..
403                         (field.store && !field.store.isLocal)
404                         ) {
405                         // it's a combo
406                         var sd = { };
407                         sd[field.valueField] = typeof(values[field.hiddenName]) == 'undefined' ? '' : values[field.hiddenName];
408                         sd[field.displayField] = typeof(values[field.name]) == 'undefined' ? '' : values[field.name];
409                         field.setFromData(sd);
410                         
411                     } else {
412                         field.setValue(values[id]);
413                     }
414                     
415                     
416                     if(this.trackResetOnLoad){
417                         field.originalValue = field.getValue();
418                     }
419                 }
420             }
421         }
422          
423         Roo.each(this.childForms || [], function (f) {
424             f.setValues(values);
425         });
426                 
427         return this;
428     },
429
430     /**
431      * Returns the fields in this form as an object with key/value pairs. If multiple fields exist with the same name
432      * they are returned as an array.
433      * @param {Boolean} asString
434      * @return {Object}
435      */
436     getValues : function(asString){
437         if (this.childForms) {
438             // copy values from the child forms
439             Roo.each(this.childForms, function (f) {
440                 this.setValues(f.getValues());
441             }, this);
442         }
443         
444         
445         
446         var fs = Roo.lib.Ajax.serializeForm(this.el.dom);
447         if(asString === true){
448             return fs;
449         }
450         return Roo.urlDecode(fs);
451     },
452     
453     /**
454      * Returns the fields in this form as an object with key/value pairs. 
455      * This differs from getValues as it calls getValue on each child item, rather than using dom data.
456      * @return {Object}
457      */
458     getFieldValues : function()
459     {
460         if (this.childForms) {
461             // copy values from the child forms
462             Roo.each(this.childForms, function (f) {
463                 this.setValues(f.getValues());
464             }, this);
465         }
466         
467         var ret = {};
468         this.items.each(function(f){
469             if (!f.getName()) {
470                 return;
471             }
472             var v = f.getValue();
473             if ((typeof(v) == 'object') && f.getRawValue) {
474                 v = f.getRawValue() ; // dates..
475             }
476             ret[f.getName()] = v;
477         });
478         
479         return ret;
480     },
481
482     /**
483      * Clears all invalid messages in this form.
484      * @return {BasicForm} this
485      */
486     clearInvalid : function(){
487         this.items.each(function(f){
488            f.clearInvalid();
489         });
490         
491         Roo.each(this.childForms || [], function (f) {
492             f.clearInvalid();
493         });
494         
495         
496         return this;
497     },
498
499     /**
500      * Resets this form.
501      * @return {BasicForm} this
502      */
503     reset : function(){
504         this.items.each(function(f){
505             f.reset();
506         });
507         
508         Roo.each(this.childForms || [], function (f) {
509             f.reset();
510         });
511        
512         
513         return this;
514     },
515
516     /**
517      * Add Roo.form components to this form.
518      * @param {Field} field1
519      * @param {Field} field2 (optional)
520      * @param {Field} etc (optional)
521      * @return {BasicForm} this
522      */
523     add : function(){
524         this.items.addAll(Array.prototype.slice.call(arguments, 0));
525         return this;
526     },
527
528
529     /**
530      * Removes a field from the items collection (does NOT remove its markup).
531      * @param {Field} field
532      * @return {BasicForm} this
533      */
534     remove : function(field){
535         this.items.remove(field);
536         return this;
537     },
538
539     /**
540      * Looks at the fields in this form, checks them for an id attribute,
541      * and calls applyTo on the existing dom element with that id.
542      * @return {BasicForm} this
543      */
544     render : function(){
545         this.items.each(function(f){
546             if(f.isFormField && !f.rendered && document.getElementById(f.id)){ // if the element exists
547                 f.applyTo(f.id);
548             }
549         });
550         return this;
551     },
552
553     /**
554      * Calls {@link Ext#apply} for all fields in this form with the passed object.
555      * @param {Object} values
556      * @return {BasicForm} this
557      */
558     applyToFields : function(o){
559         this.items.each(function(f){
560            Roo.apply(f, o);
561         });
562         return this;
563     },
564
565     /**
566      * Calls {@link Ext#applyIf} for all field in this form with the passed object.
567      * @param {Object} values
568      * @return {BasicForm} this
569      */
570     applyIfToFields : function(o){
571         this.items.each(function(f){
572            Roo.applyIf(f, o);
573         });
574         return this;
575     }
576 });
577
578 // back compat
579 Roo.BasicForm = Roo.form.BasicForm;