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