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     /**
298      * Mark fields in this form invalid in bulk.
299      * @param {Array/Object} errors Either an array in the form [{id:'fieldId', msg:'The message'},...] or an object hash of {id: msg, id2: msg2}
300      * @return {BasicForm} this
301      */
302     markInvalid : function(errors){
303         if(errors instanceof Array){
304             for(var i = 0, len = errors.length; i < len; i++){
305                 var fieldError = errors[i];
306                 var f = this.findField(fieldError.id);
307                 if(f){
308                     f.markInvalid(fieldError.msg);
309                 }
310             }
311         }else{
312             var field, id;
313             for(id in errors){
314                 if(typeof errors[id] != 'function' && (field = this.findField(id))){
315                     field.markInvalid(errors[id]);
316                 }
317             }
318         }
319         return this;
320     },
321
322     /**
323      * Set values for fields in this form in bulk.
324      * @param {Array/Object} values Either an array in the form [{id:'fieldId', value:'foo'},...] or an object hash of {id: value, id2: value2}
325      * @return {BasicForm} this
326      */
327     setValues : function(values){
328         if(values instanceof Array){ // array of objects
329             for(var i = 0, len = values.length; i < len; i++){
330                 var v = values[i];
331                 var f = this.findField(v.id);
332                 if(f){
333                     f.setValue(v.value);
334                     if(this.trackResetOnLoad){
335                         f.originalValue = f.getValue();
336                     }
337                 }
338             }
339         }else{ // object hash
340             var field, id;
341             for(id in values){
342                 if(typeof values[id] != 'function' && (field = this.findField(id))){
343                     
344                     if (field.setFromData && 
345                         field.valueField && 
346                         field.displayField &&
347                         // combos' with local stores can 
348                         // be queried via setValue()
349                         // to set their value..
350                         (field.store && !field.store.isLocal)
351                         ) {
352                         // it's a combo
353                         var sd = { };
354                         sd[field.valueField] = typeof(values[field.hiddenName]) == 'undefined' ? '' : values[field.hiddenName];
355                         sd[field.displayField] = typeof(values[field.name]) == 'undefined' ? '' : values[field.name];
356                         field.setFromData(sd);
357                         
358                     } else {
359                         field.setValue(values[id]);
360                     }
361                     
362                     
363                     if(this.trackResetOnLoad){
364                         field.originalValue = field.getValue();
365                     }
366                 }
367             }
368         }
369         if (this.childForms) {
370             Roo.each(this.childForms, function (f) {
371                 f.setValues(values);
372             });
373         }
374         
375         return this;
376     },
377
378     /**
379      * Returns the fields in this form as an object with key/value pairs. If multiple fields exist with the same name
380      * they are returned as an array.
381      * @param {Boolean} asString
382      * @return {Object}
383      */
384     getValues : function(asString){
385         if (this.childForms) {
386             // copy values from the child forms
387             Roo.each(this.childForms, function (f) {
388                 if (f.allFields) {
389                     Roo.each(f.allFields, function (e) {
390                         if (e.name && e.getValue && this.findField(e.name)) {
391                             this.findField(e.name).setValue(e.getValue());
392                         }
393                     });
394                 }
395             }, this);
396         }
397         
398         
399         
400         var fs = Roo.lib.Ajax.serializeForm(this.el.dom);
401         if(asString === true){
402             return fs;
403         }
404         return Roo.urlDecode(fs);
405     },
406
407     /**
408      * Clears all invalid messages in this form.
409      * @return {BasicForm} this
410      */
411     clearInvalid : function(){
412         this.items.each(function(f){
413            f.clearInvalid();
414         });
415         
416         Roo.each(this.childForms || [], function (f) {
417             f.clearInvalid();
418         });
419         
420         
421         return this;
422     },
423
424     /**
425      * Resets this form.
426      * @return {BasicForm} this
427      */
428     reset : function(){
429         this.items.each(function(f){
430             f.reset();
431         });
432         
433         Roo.each(this.childForms || [], function (f) {
434             f.reset();
435         });
436        
437         
438         return this;
439     },
440
441     /**
442      * Add Roo.form components to this form.
443      * @param {Field} field1
444      * @param {Field} field2 (optional)
445      * @param {Field} etc (optional)
446      * @return {BasicForm} this
447      */
448     add : function(){
449         this.items.addAll(Array.prototype.slice.call(arguments, 0));
450         return this;
451     },
452
453
454     /**
455      * Removes a field from the items collection (does NOT remove its markup).
456      * @param {Field} field
457      * @return {BasicForm} this
458      */
459     remove : function(field){
460         this.items.remove(field);
461         return this;
462     },
463
464     /**
465      * Looks at the fields in this form, checks them for an id attribute,
466      * and calls applyTo on the existing dom element with that id.
467      * @return {BasicForm} this
468      */
469     render : function(){
470         this.items.each(function(f){
471             if(f.isFormField && !f.rendered && document.getElementById(f.id)){ // if the element exists
472                 f.applyTo(f.id);
473             }
474         });
475         return this;
476     },
477
478     /**
479      * Calls {@link Ext#apply} for all fields in this form with the passed object.
480      * @param {Object} values
481      * @return {BasicForm} this
482      */
483     applyToFields : function(o){
484         this.items.each(function(f){
485            Roo.apply(f, o);
486         });
487         return this;
488     },
489
490     /**
491      * Calls {@link Ext#applyIf} for all field in this form with the passed object.
492      * @param {Object} values
493      * @return {BasicForm} this
494      */
495     applyIfToFields : function(o){
496         this.items.each(function(f){
497            Roo.applyIf(f, o);
498         });
499         return this;
500     }
501 });
502
503 // back compat
504 Roo.BasicForm = Roo.form.BasicForm;