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