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