4 * Copyright(c) 2006-2007, Ext JS, LLC.
6 * Originally Released Under LGPL - original licence link has changed is not relivant.
9 * <script type="text/javascript">
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.
17 * @param {String/HTMLElement/Roo.Element} el The form element or its id
18 * @param {Object} config Configuration options
20 Roo.form.BasicForm = function(el, config){
23 Roo.apply(this, config);
25 * The Roo.form.Field items in this form.
26 * @type MixedCollection
30 this.items = new Roo.util.MixedCollection(false, function(o){
31 return o.id || (o.id = Roo.id());
36 * Fires before any action is performed. Return false to cancel the action.
38 * @param {Action} action The action to be performed
43 * Fires when an action fails.
45 * @param {Action} action The action that failed
49 * @event actioncomplete
50 * Fires when an action is completed.
52 * @param {Action} action The action that completed
59 Roo.form.BasicForm.superclass.constructor.call(this);
62 Roo.extend(Roo.form.BasicForm, Roo.util.Observable, {
64 * @cfg {String} method
65 * The request method to use (GET or POST) for form actions if one isn't supplied in the action options.
68 * @cfg {DataReader} reader
69 * An Roo.data.DataReader (e.g. {@link Roo.data.XmlReader}) to be used to read data when executing "load" actions.
70 * This is optional as there is built-in support for processing JSON.
73 * @cfg {DataReader} errorReader
74 * An Roo.data.DataReader (e.g. {@link Roo.data.XmlReader}) to be used to read data when reading validation errors on "submit" actions.
75 * This is completely optional as there is built-in support for processing JSON.
79 * The URL to use for form actions if one isn't supplied in the action options.
82 * @cfg {Boolean} fileUpload
83 * Set to true if this form is a file upload.
87 * @cfg {Object} baseParams
88 * Parameters to pass with all requests. e.g. baseParams: {id: '123', foo: 'bar'}.
93 * @cfg {Number} timeout Timeout for form actions in seconds (default is 30 seconds).
101 * @cfg {Boolean} trackResetOnLoad If set to true, form.reset() resets to the last loaded
102 * or setValues() data instead of when the form was first created.
104 trackResetOnLoad : false,
108 * childForms - used for multi-tab forms
114 * allItems - full list of fields.
120 * By default wait messages are displayed with Roo.MessageBox.wait. You can target a specific
121 * element by passing it or its id or mask the form itself by passing in true.
124 waitMsgTarget : false,
127 initEl : function(el){
128 this.el = Roo.get(el);
129 this.id = this.el.id || Roo.id();
130 this.el.on('submit', this.onSubmit, this);
131 this.el.addClass('x-form');
135 onSubmit : function(e){
140 * Returns true if client-side validation on the form is successful.
143 isValid : function(){
145 this.items.each(function(f){
154 * DEPRICATED Returns true if any fields in this form have changed since their original load.
157 isDirty : function(){
159 this.items.each(function(f){
169 * Returns true if any fields in this form have changed since their original load. (New version)
173 hasChanged : function()
176 this.items.each(function(f){
186 * Resets all hasChanged to 'false' -
187 * The old 'isDirty' used 'original value..' however this breaks reset() and a few other things.
188 * So hasChanged storage is only to be used for this purpose
191 resetHasChanged : function()
193 this.items.each(function(f){
201 * Performs a predefined action (submit or load) or custom actions you define on this form.
202 * @param {String} actionName The name of the action type
203 * @param {Object} options (optional) The options to pass to the action. All of the config options listed
204 * below are supported by both the submit and load actions unless otherwise noted (custom actions could also
205 * accept other config options):
207 Property Type Description
208 ---------------- --------------- ----------------------------------------------------------------------------------
209 url String The url for the action (defaults to the form's url)
210 method String The form method to use (defaults to the form's method, or POST if not defined)
211 params String/Object The params to pass (defaults to the form's baseParams, or none if not defined)
212 clientValidation Boolean Applies to submit only. Pass true to call form.isValid() prior to posting to
213 validate the form on the client (defaults to false)
215 * @return {BasicForm} this
217 doAction : function(action, options){
218 if(typeof action == 'string'){
219 action = new Roo.form.Action.ACTION_TYPES[action](this, options);
221 if(this.fireEvent('beforeaction', this, action) !== false){
222 this.beforeAction(action);
223 action.run.defer(100, action);
229 * Shortcut to do a submit action.
230 * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
231 * @return {BasicForm} this
233 submit : function(options){
234 this.doAction('submit', options);
239 * Shortcut to do a load action.
240 * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
241 * @return {BasicForm} this
243 load : function(options){
244 this.doAction('load', options);
249 * Persists the values in this form into the passed Roo.data.Record object in a beginEdit/endEdit block.
250 * @param {Record} record The record to edit
251 * @return {BasicForm} this
253 updateRecord : function(record){
255 var fs = record.fields;
257 var field = this.findField(f.name);
259 record.set(f.name, field.getValue());
267 * Loads an Roo.data.Record into this form.
268 * @param {Record} record The record to load
269 * @return {BasicForm} this
271 loadRecord : function(record){
272 this.setValues(record.data);
277 beforeAction : function(action){
278 var o = action.options;
281 if(this.waitMsgTarget === true){
282 this.el.mask(o.waitMsg || "Sending", 'x-mask-loading');
283 }else if(this.waitMsgTarget){
284 this.waitMsgTarget = Roo.get(this.waitMsgTarget);
285 this.waitMsgTarget.mask(o.waitMsg || "Sending", 'x-mask-loading');
287 Roo.MessageBox.wait(o.waitMsg || "Sending", o.waitTitle || this.waitTitle || 'Please Wait...');
293 afterAction : function(action, success){
294 this.activeAction = null;
295 var o = action.options;
297 if(this.waitMsgTarget === true){
299 }else if(this.waitMsgTarget){
300 this.waitMsgTarget.unmask();
302 Roo.MessageBox.updateProgress(1);
303 Roo.MessageBox.hide();
310 Roo.callback(o.success, o.scope, [this, action]);
311 this.fireEvent('actioncomplete', this, action);
315 // failure condition..
316 // we have a scenario where updates need confirming.
317 // eg. if a locking scenario exists..
318 // we look for { errors : { needs_confirm : true }} in the response.
320 (typeof(action.result) != 'undefined') &&
321 (typeof(action.result.errors) != 'undefined') &&
322 (typeof(action.result.errors.needs_confirm) != 'undefined')
325 Roo.MessageBox.confirm(
326 "Change requires confirmation",
327 action.result.errorMsg,
332 _t.doAction('submit', { params : { _submit_confirmed : 1 } } );
342 Roo.callback(o.failure, o.scope, [this, action]);
343 // show an error message if no failed handler is set..
344 if (!this.hasListener('actionfailed')) {
345 Roo.MessageBox.alert("Error",
346 (typeof(action.result) != 'undefined' && typeof(action.result.errorMsg) != 'undefined') ?
347 action.result.errorMsg :
348 "Saving Failed, please check your entries or try again"
352 this.fireEvent('actionfailed', this, action);
358 * Find a Roo.form.Field in this form by id, dataIndex, name or hiddenName
359 * @param {String} id The value to search for
362 findField : function(id){
363 var field = this.items.get(id);
365 this.items.each(function(f){
366 if(f.isFormField && (f.dataIndex == id || f.id == id || f.getName() == id)){
372 return field || null;
376 * Add a secondary form to this one,
377 * Used to provide tabbed forms. One form is primary, with hidden values
378 * which mirror the elements from the other forms.
380 * @param {Roo.form.Form} form to add.
383 addForm : function(form)
386 if (this.childForms.indexOf(form) > -1) {
390 this.childForms.push(form);
392 Roo.each(form.allItems, function (fe) {
394 n = typeof(fe.getName) == 'undefined' ? fe.name : fe.getName();
395 if (this.findField(n)) { // already added..
398 var add = new Roo.form.Hidden({
408 * Mark fields in this form invalid in bulk.
409 * @param {Array/Object} errors Either an array in the form [{id:'fieldId', msg:'The message'},...] or an object hash of {id: msg, id2: msg2}
410 * @return {BasicForm} this
412 markInvalid : function(errors){
413 if(errors instanceof Array){
414 for(var i = 0, len = errors.length; i < len; i++){
415 var fieldError = errors[i];
416 var f = this.findField(fieldError.id);
418 f.markInvalid(fieldError.msg);
424 if(typeof errors[id] != 'function' && (field = this.findField(id))){
425 field.markInvalid(errors[id]);
429 Roo.each(this.childForms || [], function (f) {
430 f.markInvalid(errors);
437 * Set values for fields in this form in bulk.
438 * @param {Array/Object} values Either an array in the form [{id:'fieldId', value:'foo'},...] or an object hash of {id: value, id2: value2}
439 * @return {BasicForm} this
441 setValues : function(values){
442 if(values instanceof Array){ // array of objects
443 for(var i = 0, len = values.length; i < len; i++){
445 var f = this.findField(v.id);
448 if(this.trackResetOnLoad){
449 f.originalValue = f.getValue();
453 }else{ // object hash
456 if(typeof values[id] != 'function' && (field = this.findField(id))){
458 if (field.setFromData &&
460 field.displayField &&
461 // combos' with local stores can
462 // be queried via setValue()
463 // to set their value..
464 (field.store && !field.store.isLocal)
468 sd[field.valueField] = typeof(values[field.hiddenName]) == 'undefined' ? '' : values[field.hiddenName];
469 sd[field.displayField] = typeof(values[field.name]) == 'undefined' ? '' : values[field.name];
470 field.setFromData(sd);
473 field.setValue(values[id]);
477 if(this.trackResetOnLoad){
478 field.originalValue = field.getValue();
483 this.resetHasChanged();
486 Roo.each(this.childForms || [], function (f) {
495 * Returns the fields in this form as an object with key/value pairs. If multiple fields exist with the same name
496 * they are returned as an array.
497 * @param {Boolean} asString
500 getValues : function(asString){
501 if (this.childForms) {
502 // copy values from the child forms
503 Roo.each(this.childForms, function (f) {
504 this.setValues(f.getValues());
510 var fs = Roo.lib.Ajax.serializeForm(this.el.dom);
511 if(asString === true){
514 return Roo.urlDecode(fs);
518 * Returns the fields in this form as an object with key/value pairs.
519 * This differs from getValues as it calls getValue on each child item, rather than using dom data.
522 getFieldValues : function(with_hidden)
524 if (this.childForms) {
525 // copy values from the child forms
526 // should this call getFieldValues - probably not as we do not currently copy
527 // hidden fields when we generate..
528 Roo.each(this.childForms, function (f) {
529 this.setValues(f.getValues());
534 this.items.each(function(f){
538 var v = f.getValue();
539 if (f.inputType =='radio') {
540 if (typeof(ret[f.getName()]) == 'undefined') {
541 ret[f.getName()] = ''; // empty..
544 if (!f.el.dom.checked) {
552 // not sure if this supported any more..
553 if ((typeof(v) == 'object') && f.getRawValue) {
554 v = f.getRawValue() ; // dates..
556 // combo boxes where name != hiddenName...
557 if (f.name != f.getName()) {
558 ret[f.name] = f.getRawValue();
560 ret[f.getName()] = v;
567 * Clears all invalid messages in this form.
568 * @return {BasicForm} this
570 clearInvalid : function(){
571 this.items.each(function(f){
575 Roo.each(this.childForms || [], function (f) {
585 * @return {BasicForm} this
588 this.items.each(function(f){
592 Roo.each(this.childForms || [], function (f) {
595 this.resetHasChanged();
601 * Add Roo.form components to this form.
602 * @param {Field} field1
603 * @param {Field} field2 (optional)
604 * @param {Field} etc (optional)
605 * @return {BasicForm} this
608 this.items.addAll(Array.prototype.slice.call(arguments, 0));
614 * Removes a field from the items collection (does NOT remove its markup).
615 * @param {Field} field
616 * @return {BasicForm} this
618 remove : function(field){
619 this.items.remove(field);
624 * Looks at the fields in this form, checks them for an id attribute,
625 * and calls applyTo on the existing dom element with that id.
626 * @return {BasicForm} this
629 this.items.each(function(f){
630 if(f.isFormField && !f.rendered && document.getElementById(f.id)){ // if the element exists
638 * Calls {@link Ext#apply} for all fields in this form with the passed object.
639 * @param {Object} values
640 * @return {BasicForm} this
642 applyToFields : function(o){
643 this.items.each(function(f){
650 * Calls {@link Ext#applyIf} for all field in this form with the passed object.
651 * @param {Object} values
652 * @return {BasicForm} this
654 applyIfToFields : function(o){
655 this.items.each(function(f){
663 Roo.BasicForm = Roo.form.BasicForm;