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){
167 if(!target && f.el.isVisible(true)){
173 if(this.errorMask && !valid){
174 Roo.form.BasicForm.popover.mask(this, target);
181 * DEPRICATED Returns true if any fields in this form have changed since their original load.
184 isDirty : function(){
186 this.items.each(function(f){
196 * Returns true if any fields in this form have changed since their original load. (New version)
200 hasChanged : function()
203 this.items.each(function(f){
213 * Resets all hasChanged to 'false' -
214 * The old 'isDirty' used 'original value..' however this breaks reset() and a few other things.
215 * So hasChanged storage is only to be used for this purpose
218 resetHasChanged : function()
220 this.items.each(function(f){
228 * Performs a predefined action (submit or load) or custom actions you define on this form.
229 * @param {String} actionName The name of the action type
230 * @param {Object} options (optional) The options to pass to the action. All of the config options listed
231 * below are supported by both the submit and load actions unless otherwise noted (custom actions could also
232 * accept other config options):
234 Property Type Description
235 ---------------- --------------- ----------------------------------------------------------------------------------
236 url String The url for the action (defaults to the form's url)
237 method String The form method to use (defaults to the form's method, or POST if not defined)
238 params String/Object The params to pass (defaults to the form's baseParams, or none if not defined)
239 clientValidation Boolean Applies to submit only. Pass true to call form.isValid() prior to posting to
240 validate the form on the client (defaults to false)
242 * @return {BasicForm} this
244 doAction : function(action, options){
245 if(typeof action == 'string'){
246 action = new Roo.form.Action.ACTION_TYPES[action](this, options);
248 if(this.fireEvent('beforeaction', this, action) !== false){
249 this.beforeAction(action);
250 action.run.defer(100, action);
256 * Shortcut to do a submit action.
257 * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
258 * @return {BasicForm} this
260 submit : function(options){
261 this.doAction('submit', options);
266 * Shortcut to do a load action.
267 * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
268 * @return {BasicForm} this
270 load : function(options){
271 this.doAction('load', options);
276 * Persists the values in this form into the passed Roo.data.Record object in a beginEdit/endEdit block.
277 * @param {Record} record The record to edit
278 * @return {BasicForm} this
280 updateRecord : function(record){
282 var fs = record.fields;
284 var field = this.findField(f.name);
286 record.set(f.name, field.getValue());
294 * Loads an Roo.data.Record into this form.
295 * @param {Record} record The record to load
296 * @return {BasicForm} this
298 loadRecord : function(record){
299 this.setValues(record.data);
304 beforeAction : function(action){
305 var o = action.options;
307 if(!this.disableMask) {
308 if(this.waitMsgTarget === true){
309 this.el.mask(o.waitMsg || "Sending", 'x-mask-loading');
310 }else if(this.waitMsgTarget){
311 this.waitMsgTarget = Roo.get(this.waitMsgTarget);
312 this.waitMsgTarget.mask(o.waitMsg || "Sending", 'x-mask-loading');
314 Roo.MessageBox.wait(o.waitMsg || "Sending", o.waitTitle || this.waitTitle || 'Please Wait...');
322 afterAction : function(action, success){
323 this.activeAction = null;
324 var o = action.options;
326 if(!this.disableMask) {
327 if(this.waitMsgTarget === true){
329 }else if(this.waitMsgTarget){
330 this.waitMsgTarget.unmask();
332 Roo.MessageBox.updateProgress(1);
333 Roo.MessageBox.hide();
341 Roo.callback(o.success, o.scope, [this, action]);
342 this.fireEvent('actioncomplete', this, action);
346 // failure condition..
347 // we have a scenario where updates need confirming.
348 // eg. if a locking scenario exists..
349 // we look for { errors : { needs_confirm : true }} in the response.
351 (typeof(action.result) != 'undefined') &&
352 (typeof(action.result.errors) != 'undefined') &&
353 (typeof(action.result.errors.needs_confirm) != 'undefined')
356 Roo.MessageBox.confirm(
357 "Change requires confirmation",
358 action.result.errorMsg,
363 _t.doAction('submit', { params : { _submit_confirmed : 1 } } );
373 Roo.callback(o.failure, o.scope, [this, action]);
374 // show an error message if no failed handler is set..
375 if (!this.hasListener('actionfailed')) {
376 Roo.MessageBox.alert("Error",
377 (typeof(action.result) != 'undefined' && typeof(action.result.errorMsg) != 'undefined') ?
378 action.result.errorMsg :
379 "Saving Failed, please check your entries or try again"
383 this.fireEvent('actionfailed', this, action);
389 * Find a Roo.form.Field in this form by id, dataIndex, name or hiddenName
390 * @param {String} id The value to search for
393 findField : function(id){
394 var field = this.items.get(id);
396 this.items.each(function(f){
397 if(f.isFormField && (f.dataIndex == id || f.id == id || f.getName() == id)){
403 return field || null;
407 * Add a secondary form to this one,
408 * Used to provide tabbed forms. One form is primary, with hidden values
409 * which mirror the elements from the other forms.
411 * @param {Roo.form.Form} form to add.
414 addForm : function(form)
417 if (this.childForms.indexOf(form) > -1) {
421 this.childForms.push(form);
423 Roo.each(form.allItems, function (fe) {
425 n = typeof(fe.getName) == 'undefined' ? fe.name : fe.getName();
426 if (this.findField(n)) { // already added..
429 var add = new Roo.form.Hidden({
439 * Mark fields in this form invalid in bulk.
440 * @param {Array/Object} errors Either an array in the form [{id:'fieldId', msg:'The message'},...] or an object hash of {id: msg, id2: msg2}
441 * @return {BasicForm} this
443 markInvalid : function(errors){
444 if(errors instanceof Array){
445 for(var i = 0, len = errors.length; i < len; i++){
446 var fieldError = errors[i];
447 var f = this.findField(fieldError.id);
449 f.markInvalid(fieldError.msg);
455 if(typeof errors[id] != 'function' && (field = this.findField(id))){
456 field.markInvalid(errors[id]);
460 Roo.each(this.childForms || [], function (f) {
461 f.markInvalid(errors);
468 * Set values for fields in this form in bulk.
469 * @param {Array/Object} values Either an array in the form [{id:'fieldId', value:'foo'},...] or an object hash of {id: value, id2: value2}
470 * @return {BasicForm} this
472 setValues : function(values){
473 if(values instanceof Array){ // array of objects
474 for(var i = 0, len = values.length; i < len; i++){
476 var f = this.findField(v.id);
479 if(this.trackResetOnLoad){
480 f.originalValue = f.getValue();
484 }else{ // object hash
487 if(typeof values[id] != 'function' && (field = this.findField(id))){
489 if (field.setFromData &&
491 field.displayField &&
492 // combos' with local stores can
493 // be queried via setValue()
494 // to set their value..
495 (field.store && !field.store.isLocal)
499 sd[field.valueField] = typeof(values[field.hiddenName]) == 'undefined' ? '' : values[field.hiddenName];
500 sd[field.displayField] = typeof(values[field.name]) == 'undefined' ? '' : values[field.name];
501 field.setFromData(sd);
504 field.setValue(values[id]);
508 if(this.trackResetOnLoad){
509 field.originalValue = field.getValue();
514 this.resetHasChanged();
517 Roo.each(this.childForms || [], function (f) {
526 * Returns the fields in this form as an object with key/value pairs. If multiple fields exist with the same name
527 * they are returned as an array.
528 * @param {Boolean} asString
531 getValues : function(asString){
532 if (this.childForms) {
533 // copy values from the child forms
534 Roo.each(this.childForms, function (f) {
535 this.setValues(f.getValues());
541 var fs = Roo.lib.Ajax.serializeForm(this.el.dom);
542 if(asString === true){
545 return Roo.urlDecode(fs);
549 * Returns the fields in this form as an object with key/value pairs.
550 * This differs from getValues as it calls getValue on each child item, rather than using dom data.
553 getFieldValues : function(with_hidden)
555 if (this.childForms) {
556 // copy values from the child forms
557 // should this call getFieldValues - probably not as we do not currently copy
558 // hidden fields when we generate..
559 Roo.each(this.childForms, function (f) {
560 this.setValues(f.getValues());
565 this.items.each(function(f){
569 var v = f.getValue();
570 if (f.inputType =='radio') {
571 if (typeof(ret[f.getName()]) == 'undefined') {
572 ret[f.getName()] = ''; // empty..
575 if (!f.el.dom.checked) {
583 // not sure if this supported any more..
584 if ((typeof(v) == 'object') && f.getRawValue) {
585 v = f.getRawValue() ; // dates..
587 // combo boxes where name != hiddenName...
588 if (f.name != f.getName()) {
589 ret[f.name] = f.getRawValue();
591 ret[f.getName()] = v;
598 * Clears all invalid messages in this form.
599 * @return {BasicForm} this
601 clearInvalid : function(){
602 this.items.each(function(f){
606 Roo.each(this.childForms || [], function (f) {
616 * @return {BasicForm} this
619 this.items.each(function(f){
623 Roo.each(this.childForms || [], function (f) {
626 this.resetHasChanged();
632 * Add Roo.form components to this form.
633 * @param {Field} field1
634 * @param {Field} field2 (optional)
635 * @param {Field} etc (optional)
636 * @return {BasicForm} this
639 this.items.addAll(Array.prototype.slice.call(arguments, 0));
645 * Removes a field from the items collection (does NOT remove its markup).
646 * @param {Field} field
647 * @return {BasicForm} this
649 remove : function(field){
650 this.items.remove(field);
655 * Looks at the fields in this form, checks them for an id attribute,
656 * and calls applyTo on the existing dom element with that id.
657 * @return {BasicForm} this
660 this.items.each(function(f){
661 if(f.isFormField && !f.rendered && document.getElementById(f.id)){ // if the element exists
669 * Calls {@link Ext#apply} for all fields in this form with the passed object.
670 * @param {Object} values
671 * @return {BasicForm} this
673 applyToFields : function(o){
674 this.items.each(function(f){
681 * Calls {@link Ext#applyIf} for all field in this form with the passed object.
682 * @param {Object} values
683 * @return {BasicForm} this
685 applyIfToFields : function(o){
686 this.items.each(function(f){
694 Roo.BasicForm = Roo.form.BasicForm;
696 Roo.apply(Roo.form.BasicForm, {
723 top : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-top-mask" }, true),
724 left : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-left-mask" }, true),
725 bottom : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-bottom-mask" }, true),
726 right : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-right-mask" }, true)
729 this.maskEl.top.enableDisplayMode("block");
730 this.maskEl.left.enableDisplayMode("block");
731 this.maskEl.bottom.enableDisplayMode("block");
732 this.maskEl.right.enableDisplayMode("block");
734 // this.toolTip = new Roo.bootstrap.Tooltip({
735 // cls : 'roo-form-error-popover',
737 // 'left' : ['r-l', [-2,0], 'right'],
738 // 'right' : ['l-r', [2,0], 'left'],
739 // 'bottom' : ['tl-bl', [0,2], 'top'],
740 // 'top' : [ 'bl-tl', [0,-2], 'bottom']
744 // this.toolTip.render(Roo.get(document.body));
746 // this.toolTip.el.enableDisplayMode("block");
748 Roo.get(document.body).on('click', function(){
752 Roo.get(document.body).on('touchstart', function(){
756 this.isApplied = true
759 mask : function(form, target)
763 this.target = target;
765 if(!this.form.errorMask || !target.el){
769 var scrollable = this.target.el.findScrollableParent() || this.target.el.findParent('div.x-layout-active-content', 100, true) || Roo.get(document.body);
773 var ot = this.target.el.calcOffsetsTo(scrollable);
775 var scrollTo = ot[1] - this.form.maskOffset;
777 scrollTo = Math.min(scrollTo, scrollable.dom.scrollHeight);
779 scrollable.scrollTo('top', scrollTo);
781 var box = this.target.el.getBox();
783 var zIndex = Roo.bootstrap.Modal.zIndex++;
786 this.maskEl.top.setStyle('position', 'absolute');
787 this.maskEl.top.setStyle('z-index', zIndex);
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', zIndex);
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', zIndex);
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', zIndex);
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.toolTip.bindEl = this.target.el;
816 this.toolTip.el.setStyle('z-index', Roo.bootstrap.Modal.zIndex++);
818 var tip = this.target.blankText;
820 if(this.target.getValue() !== '' ) {
822 if (this.target.invalidText.length) {
823 tip = this.target.invalidText;
824 } else if (this.target.regexText.length){
825 tip = this.target.regexText;
829 this.toolTip.show(tip);
831 this.intervalID = window.setInterval(function() {
832 Roo.bootstrap.Form.popover.unmask();
835 window.onwheel = function(){ return false;};
837 (function(){ this.isMasked = true; }).defer(500, this);
843 if(!this.isApplied || !this.isMasked || !this.form || !this.target || !this.form.errorMask){
847 this.maskEl.top.setStyle('position', 'absolute');
848 this.maskEl.top.setSize(0, 0).setXY([0, 0]);
849 this.maskEl.top.hide();
851 this.maskEl.left.setStyle('position', 'absolute');
852 this.maskEl.left.setSize(0, 0).setXY([0, 0]);
853 this.maskEl.left.hide();
855 this.maskEl.bottom.setStyle('position', 'absolute');
856 this.maskEl.bottom.setSize(0, 0).setXY([0, 0]);
857 this.maskEl.bottom.hide();
859 this.maskEl.right.setStyle('position', 'absolute');
860 this.maskEl.right.setSize(0, 0).setXY([0, 0]);
861 this.maskEl.right.hide();
863 // this.toolTip.hide();
865 // this.toolTip.el.hide();
867 window.onwheel = function(){ return true;};
870 window.clearInterval(this.intervalID);
871 this.intervalID = false;
874 this.isMasked = false;