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