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