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);
61 Roo.form.BasicForm.popover.apply();
64 Roo.extend(Roo.form.BasicForm, Roo.util.Observable, {
66 * @cfg {String} method
67 * The request method to use (GET or POST) for form actions if one isn't supplied in the action options.
70 * @cfg {DataReader} reader
71 * An Roo.data.DataReader (e.g. {@link Roo.data.XmlReader}) to be used to read data when executing "load" actions.
72 * This is optional as there is built-in support for processing JSON.
75 * @cfg {DataReader} errorReader
76 * An Roo.data.DataReader (e.g. {@link Roo.data.XmlReader}) to be used to read data when reading validation errors on "submit" actions.
77 * This is completely optional as there is built-in support for processing JSON.
81 * The URL to use for form actions if one isn't supplied in the action options.
84 * @cfg {Boolean} fileUpload
85 * Set to true if this form is a file upload.
89 * @cfg {Object} baseParams
90 * Parameters to pass with all requests. e.g. baseParams: {id: '123', foo: 'bar'}.
95 * @cfg {Number} timeout Timeout for form actions in seconds (default is 30 seconds).
103 * @cfg {Boolean} trackResetOnLoad If set to true, form.reset() resets to the last loaded
104 * or setValues() data instead of when the form was first created.
106 trackResetOnLoad : false,
110 * childForms - used for multi-tab forms
116 * allItems - full list of fields.
122 * By default wait messages are displayed with Roo.MessageBox.wait. You can target a specific
123 * element by passing it or its id or mask the form itself by passing in true.
126 waitMsgTarget : false,
134 * @cfg {Boolean} errorMask (true|false) default false
139 * @cfg {Number} maskOffset Default 100
144 initEl : function(el){
145 this.el = Roo.get(el);
146 this.id = this.el.id || Roo.id();
147 this.el.on('submit', this.onSubmit, this);
148 this.el.addClass('x-form');
152 onSubmit : function(e){
157 * Returns true if client-side validation on the form is successful.
160 isValid : function(){
163 this.items.each(function(f){
170 if(!target && f.el.isVisible(true)){
175 if(this.errorMask && !valid){
176 Roo.form.BasicForm.popover.mask(this, target);
183 * DEPRICATED Returns true if any fields in this form have changed since their original load.
186 isDirty : function(){
188 this.items.each(function(f){
198 * Returns true if any fields in this form have changed since their original load. (New version)
202 hasChanged : function()
205 this.items.each(function(f){
215 * Resets all hasChanged to 'false' -
216 * The old 'isDirty' used 'original value..' however this breaks reset() and a few other things.
217 * So hasChanged storage is only to be used for this purpose
220 resetHasChanged : function()
222 this.items.each(function(f){
230 * Performs a predefined action (submit or load) or custom actions you define on this form.
231 * @param {String} actionName The name of the action type
232 * @param {Object} options (optional) The options to pass to the action. All of the config options listed
233 * below are supported by both the submit and load actions unless otherwise noted (custom actions could also
234 * accept other config options):
236 Property Type Description
237 ---------------- --------------- ----------------------------------------------------------------------------------
238 url String The url for the action (defaults to the form's url)
239 method String The form method to use (defaults to the form's method, or POST if not defined)
240 params String/Object The params to pass (defaults to the form's baseParams, or none if not defined)
241 clientValidation Boolean Applies to submit only. Pass true to call form.isValid() prior to posting to
242 validate the form on the client (defaults to false)
244 * @return {BasicForm} this
246 doAction : function(action, options){
247 if(typeof action == 'string'){
248 action = new Roo.form.Action.ACTION_TYPES[action](this, options);
250 if(this.fireEvent('beforeaction', this, action) !== false){
251 this.beforeAction(action);
252 action.run.defer(100, action);
258 * Shortcut to do a submit action.
259 * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
260 * @return {BasicForm} this
262 submit : function(options){
263 this.doAction('submit', options);
268 * Shortcut to do a load action.
269 * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
270 * @return {BasicForm} this
272 load : function(options){
273 this.doAction('load', options);
278 * Persists the values in this form into the passed Roo.data.Record object in a beginEdit/endEdit block.
279 * @param {Record} record The record to edit
280 * @return {BasicForm} this
282 updateRecord : function(record){
284 var fs = record.fields;
286 var field = this.findField(f.name);
288 record.set(f.name, field.getValue());
296 * Loads an Roo.data.Record into this form.
297 * @param {Record} record The record to load
298 * @return {BasicForm} this
300 loadRecord : function(record){
301 this.setValues(record.data);
306 beforeAction : function(action){
307 var o = action.options;
309 if(!this.disableMask) {
310 if(this.waitMsgTarget === true){
311 this.el.mask(o.waitMsg || "Sending", 'x-mask-loading');
312 }else if(this.waitMsgTarget){
313 this.waitMsgTarget = Roo.get(this.waitMsgTarget);
314 this.waitMsgTarget.mask(o.waitMsg || "Sending", 'x-mask-loading');
316 Roo.MessageBox.wait(o.waitMsg || "Sending", o.waitTitle || this.waitTitle || 'Please Wait...');
324 afterAction : function(action, success){
325 this.activeAction = null;
326 var o = action.options;
328 if(!this.disableMask) {
329 if(this.waitMsgTarget === true){
331 }else if(this.waitMsgTarget){
332 this.waitMsgTarget.unmask();
334 Roo.MessageBox.updateProgress(1);
335 Roo.MessageBox.hide();
343 Roo.callback(o.success, o.scope, [this, action]);
344 this.fireEvent('actioncomplete', this, action);
348 // failure condition..
349 // we have a scenario where updates need confirming.
350 // eg. if a locking scenario exists..
351 // we look for { errors : { needs_confirm : true }} in the response.
353 (typeof(action.result) != 'undefined') &&
354 (typeof(action.result.errors) != 'undefined') &&
355 (typeof(action.result.errors.needs_confirm) != 'undefined')
358 Roo.MessageBox.confirm(
359 "Change requires confirmation",
360 action.result.errorMsg,
365 _t.doAction('submit', { params : { _submit_confirmed : 1 } } );
375 Roo.callback(o.failure, o.scope, [this, action]);
376 // show an error message if no failed handler is set..
377 if (!this.hasListener('actionfailed')) {
378 Roo.MessageBox.alert("Error",
379 (typeof(action.result) != 'undefined' && typeof(action.result.errorMsg) != 'undefined') ?
380 action.result.errorMsg :
381 "Saving Failed, please check your entries or try again"
385 this.fireEvent('actionfailed', this, action);
391 * Find a Roo.form.Field in this form by id, dataIndex, name or hiddenName
392 * @param {String} id The value to search for
395 findField : function(id){
396 var field = this.items.get(id);
398 this.items.each(function(f){
399 if(f.isFormField && (f.dataIndex == id || f.id == id || f.getName() == id)){
405 return field || null;
409 * Add a secondary form to this one,
410 * Used to provide tabbed forms. One form is primary, with hidden values
411 * which mirror the elements from the other forms.
413 * @param {Roo.form.Form} form to add.
416 addForm : function(form)
419 if (this.childForms.indexOf(form) > -1) {
423 this.childForms.push(form);
425 Roo.each(form.allItems, function (fe) {
427 n = typeof(fe.getName) == 'undefined' ? fe.name : fe.getName();
428 if (this.findField(n)) { // already added..
431 var add = new Roo.form.Hidden({
441 * Mark fields in this form invalid in bulk.
442 * @param {Array/Object} errors Either an array in the form [{id:'fieldId', msg:'The message'},...] or an object hash of {id: msg, id2: msg2}
443 * @return {BasicForm} this
445 markInvalid : function(errors){
446 if(errors instanceof Array){
447 for(var i = 0, len = errors.length; i < len; i++){
448 var fieldError = errors[i];
449 var f = this.findField(fieldError.id);
451 f.markInvalid(fieldError.msg);
457 if(typeof errors[id] != 'function' && (field = this.findField(id))){
458 field.markInvalid(errors[id]);
462 Roo.each(this.childForms || [], function (f) {
463 f.markInvalid(errors);
470 * Set values for fields in this form in bulk.
471 * @param {Array/Object} values Either an array in the form [{id:'fieldId', value:'foo'},...] or an object hash of {id: value, id2: value2}
472 * @return {BasicForm} this
474 setValues : function(values){
475 if(values instanceof Array){ // array of objects
476 for(var i = 0, len = values.length; i < len; i++){
478 var f = this.findField(v.id);
481 if(this.trackResetOnLoad){
482 f.originalValue = f.getValue();
486 }else{ // object hash
489 if(typeof values[id] != 'function' && (field = this.findField(id))){
491 if (field.setFromData &&
493 field.displayField &&
494 // combos' with local stores can
495 // be queried via setValue()
496 // to set their value..
497 (field.store && !field.store.isLocal)
501 sd[field.valueField] = typeof(values[field.hiddenName]) == 'undefined' ? '' : values[field.hiddenName];
502 sd[field.displayField] = typeof(values[field.name]) == 'undefined' ? '' : values[field.name];
503 field.setFromData(sd);
506 field.setValue(values[id]);
510 if(this.trackResetOnLoad){
511 field.originalValue = field.getValue();
516 this.resetHasChanged();
519 Roo.each(this.childForms || [], function (f) {
528 * Returns the fields in this form as an object with key/value pairs. If multiple fields exist with the same name
529 * they are returned as an array.
530 * @param {Boolean} asString
533 getValues : function(asString){
534 if (this.childForms) {
535 // copy values from the child forms
536 Roo.each(this.childForms, function (f) {
537 this.setValues(f.getValues());
542 if (typeof(FormData) != 'undefined' && asString !== true) {
543 // this relies on a 'recent' version of chrome apparently...
545 var fd = (new FormData(this.el.dom)).entries();
549 ret[ent.value[0]] = ent.value[1]; // not sure how this will handle duplicates..
560 var fs = Roo.lib.Ajax.serializeForm(this.el.dom);
561 if(asString === true){
564 return Roo.urlDecode(fs);
568 * Returns the fields in this form as an object with key/value pairs.
569 * This differs from getValues as it calls getValue on each child item, rather than using dom data.
572 getFieldValues : function(with_hidden)
574 if (this.childForms) {
575 // copy values from the child forms
576 // should this call getFieldValues - probably not as we do not currently copy
577 // hidden fields when we generate..
578 Roo.each(this.childForms, function (f) {
579 this.setValues(f.getValues());
584 this.items.each(function(f){
588 var v = f.getValue();
589 if (f.inputType =='radio') {
590 if (typeof(ret[f.getName()]) == 'undefined') {
591 ret[f.getName()] = ''; // empty..
594 if (!f.el.dom.checked) {
602 // not sure if this supported any more..
603 if ((typeof(v) == 'object') && f.getRawValue) {
604 v = f.getRawValue() ; // dates..
606 // combo boxes where name != hiddenName...
607 if (f.name != f.getName()) {
608 ret[f.name] = f.getRawValue();
610 ret[f.getName()] = v;
617 * Clears all invalid messages in this form.
618 * @return {BasicForm} this
620 clearInvalid : function(){
621 this.items.each(function(f){
625 Roo.each(this.childForms || [], function (f) {
635 * @return {BasicForm} this
638 this.items.each(function(f){
642 Roo.each(this.childForms || [], function (f) {
645 this.resetHasChanged();
651 * Add Roo.form components to this form.
652 * @param {Field} field1
653 * @param {Field} field2 (optional)
654 * @param {Field} etc (optional)
655 * @return {BasicForm} this
658 this.items.addAll(Array.prototype.slice.call(arguments, 0));
664 * Removes a field from the items collection (does NOT remove its markup).
665 * @param {Field} field
666 * @return {BasicForm} this
668 remove : function(field){
669 this.items.remove(field);
674 * Looks at the fields in this form, checks them for an id attribute,
675 * and calls applyTo on the existing dom element with that id.
676 * @return {BasicForm} this
679 this.items.each(function(f){
680 if(f.isFormField && !f.rendered && document.getElementById(f.id)){ // if the element exists
688 * Calls {@link Ext#apply} for all fields in this form with the passed object.
689 * @param {Object} values
690 * @return {BasicForm} this
692 applyToFields : function(o){
693 this.items.each(function(f){
700 * Calls {@link Ext#applyIf} for all field in this form with the passed object.
701 * @param {Object} values
702 * @return {BasicForm} this
704 applyIfToFields : function(o){
705 this.items.each(function(f){
713 Roo.BasicForm = Roo.form.BasicForm;
715 Roo.apply(Roo.form.BasicForm, {
740 top : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-top-mask" }, true),
741 left : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-left-mask" }, true),
742 bottom : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-bottom-mask" }, true),
743 right : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-right-mask" }, true)
746 this.maskEl.top.enableDisplayMode("block");
747 this.maskEl.left.enableDisplayMode("block");
748 this.maskEl.bottom.enableDisplayMode("block");
749 this.maskEl.right.enableDisplayMode("block");
751 Roo.get(document.body).on('click', function(){
755 Roo.get(document.body).on('touchstart', function(){
759 this.isApplied = true
762 mask : function(form, target)
766 this.target = target;
768 if(!this.form.errorMask || !target.el){
772 var scrollable = this.target.el.findScrollableParent() || this.target.el.findParent('div.x-layout-active-content', 100, true) || Roo.get(document.body);
774 var ot = this.target.el.calcOffsetsTo(scrollable);
776 var scrollTo = ot[1] - this.form.maskOffset;
778 scrollTo = Math.min(scrollTo, scrollable.dom.scrollHeight);
780 scrollable.scrollTo('top', scrollTo);
782 var el = this.target.wrap || this.target.el;
784 var box = el.getBox();
786 this.maskEl.top.setStyle('position', 'absolute');
787 this.maskEl.top.setStyle('z-index', 10000);
788 this.maskEl.top.setSize(Roo.lib.Dom.getDocumentWidth(), box.y - this.padding);
789 this.maskEl.top.setLeft(0);
790 this.maskEl.top.setTop(0);
791 this.maskEl.top.show();
793 this.maskEl.left.setStyle('position', 'absolute');
794 this.maskEl.left.setStyle('z-index', 10000);
795 this.maskEl.left.setSize(box.x - this.padding, box.height + this.padding * 2);
796 this.maskEl.left.setLeft(0);
797 this.maskEl.left.setTop(box.y - this.padding);
798 this.maskEl.left.show();
800 this.maskEl.bottom.setStyle('position', 'absolute');
801 this.maskEl.bottom.setStyle('z-index', 10000);
802 this.maskEl.bottom.setSize(Roo.lib.Dom.getDocumentWidth(), Roo.lib.Dom.getDocumentHeight() - box.bottom - this.padding);
803 this.maskEl.bottom.setLeft(0);
804 this.maskEl.bottom.setTop(box.bottom + this.padding);
805 this.maskEl.bottom.show();
807 this.maskEl.right.setStyle('position', 'absolute');
808 this.maskEl.right.setStyle('z-index', 10000);
809 this.maskEl.right.setSize(Roo.lib.Dom.getDocumentWidth() - box.right - this.padding, box.height + this.padding * 2);
810 this.maskEl.right.setLeft(box.right + this.padding);
811 this.maskEl.right.setTop(box.y - this.padding);
812 this.maskEl.right.show();
814 this.intervalID = window.setInterval(function() {
815 Roo.form.BasicForm.popover.unmask();
818 window.onwheel = function(){ return false;};
820 (function(){ this.isMasked = true; }).defer(500, this);
826 if(!this.isApplied || !this.isMasked || !this.form || !this.target || !this.form.errorMask){
830 this.maskEl.top.setStyle('position', 'absolute');
831 this.maskEl.top.setSize(0, 0).setXY([0, 0]);
832 this.maskEl.top.hide();
834 this.maskEl.left.setStyle('position', 'absolute');
835 this.maskEl.left.setSize(0, 0).setXY([0, 0]);
836 this.maskEl.left.hide();
838 this.maskEl.bottom.setStyle('position', 'absolute');
839 this.maskEl.bottom.setSize(0, 0).setXY([0, 0]);
840 this.maskEl.bottom.hide();
842 this.maskEl.right.setStyle('position', 'absolute');
843 this.maskEl.right.setSize(0, 0).setXY([0, 0]);
844 this.maskEl.right.hide();
846 window.onwheel = function(){ return true;};
849 window.clearInterval(this.intervalID);
850 this.intervalID = false;
853 this.isMasked = false;