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