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