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