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);
182 * Returns array of invalid form fields.
186 invalidFields : function()
189 this.items.each(function(f){
202 * DEPRICATED Returns true if any fields in this form have changed since their original load.
205 isDirty : function(){
207 this.items.each(function(f){
217 * Returns true if any fields in this form have changed since their original load. (New version)
221 hasChanged : function()
224 this.items.each(function(f){
234 * Resets all hasChanged to 'false' -
235 * The old 'isDirty' used 'original value..' however this breaks reset() and a few other things.
236 * So hasChanged storage is only to be used for this purpose
239 resetHasChanged : function()
241 this.items.each(function(f){
249 * Performs a predefined action (submit or load) or custom actions you define on this form.
250 * @param {String} actionName The name of the action type
251 * @param {Object} options (optional) The options to pass to the action. All of the config options listed
252 * below are supported by both the submit and load actions unless otherwise noted (custom actions could also
253 * accept other config options):
255 Property Type Description
256 ---------------- --------------- ----------------------------------------------------------------------------------
257 url String The url for the action (defaults to the form's url)
258 method String The form method to use (defaults to the form's method, or POST if not defined)
259 params String/Object The params to pass (defaults to the form's baseParams, or none if not defined)
260 clientValidation Boolean Applies to submit only. Pass true to call form.isValid() prior to posting to
261 validate the form on the client (defaults to false)
263 * @return {BasicForm} this
265 doAction : function(action, options){
266 if(typeof action == 'string'){
267 action = new Roo.form.Action.ACTION_TYPES[action](this, options);
269 if(this.fireEvent('beforeaction', this, action) !== false){
270 this.beforeAction(action);
271 action.run.defer(100, action);
277 * Shortcut to do a submit action.
278 * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
279 * @return {BasicForm} this
281 submit : function(options){
282 this.doAction('submit', options);
287 * Shortcut to do a load action.
288 * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
289 * @return {BasicForm} this
291 load : function(options){
292 this.doAction('load', options);
297 * Persists the values in this form into the passed Roo.data.Record object in a beginEdit/endEdit block.
298 * @param {Record} record The record to edit
299 * @return {BasicForm} this
301 updateRecord : function(record){
303 var fs = record.fields;
305 var field = this.findField(f.name);
307 record.set(f.name, field.getValue());
315 * Loads an Roo.data.Record into this form.
316 * @param {Record} record The record to load
317 * @return {BasicForm} this
319 loadRecord : function(record){
320 this.setValues(record.data);
325 beforeAction : function(action){
326 var o = action.options;
328 if(!this.disableMask) {
329 if(this.waitMsgTarget === true){
330 this.el.mask(o.waitMsg || "Sending", 'x-mask-loading');
331 }else if(this.waitMsgTarget){
332 this.waitMsgTarget = Roo.get(this.waitMsgTarget);
333 this.waitMsgTarget.mask(o.waitMsg || "Sending", 'x-mask-loading');
335 Roo.MessageBox.wait(o.waitMsg || "Sending", o.waitTitle || this.waitTitle || 'Please Wait...');
343 afterAction : function(action, success){
344 this.activeAction = null;
345 var o = action.options;
347 if(!this.disableMask) {
348 if(this.waitMsgTarget === true){
350 }else if(this.waitMsgTarget){
351 this.waitMsgTarget.unmask();
353 Roo.MessageBox.updateProgress(1);
354 Roo.MessageBox.hide();
362 Roo.callback(o.success, o.scope, [this, action]);
363 this.fireEvent('actioncomplete', this, action);
367 // failure condition..
368 // we have a scenario where updates need confirming.
369 // eg. if a locking scenario exists..
370 // we look for { errors : { needs_confirm : true }} in the response.
372 (typeof(action.result) != 'undefined') &&
373 (typeof(action.result.errors) != 'undefined') &&
374 (typeof(action.result.errors.needs_confirm) != 'undefined')
377 Roo.MessageBox.confirm(
378 "Change requires confirmation",
379 action.result.errorMsg,
384 _t.doAction('submit', { params : { _submit_confirmed : 1 } } );
394 Roo.callback(o.failure, o.scope, [this, action]);
395 // show an error message if no failed handler is set..
396 if (!this.hasListener('actionfailed')) {
397 Roo.MessageBox.alert("Error",
398 (typeof(action.result) != 'undefined' && typeof(action.result.errorMsg) != 'undefined') ?
399 action.result.errorMsg :
400 "Saving Failed, please check your entries or try again"
404 this.fireEvent('actionfailed', this, action);
410 * Find a Roo.form.Field in this form by id, dataIndex, name or hiddenName
411 * @param {String} id The value to search for
414 findField : function(id){
415 var field = this.items.get(id);
417 this.items.each(function(f){
418 if(f.isFormField && (f.dataIndex == id || f.id == id || f.getName() == id)){
424 return field || null;
428 * Add a secondary form to this one,
429 * Used to provide tabbed forms. One form is primary, with hidden values
430 * which mirror the elements from the other forms.
432 * @param {Roo.form.Form} form to add.
435 addForm : function(form)
438 if (this.childForms.indexOf(form) > -1) {
442 this.childForms.push(form);
444 Roo.each(form.allItems, function (fe) {
446 n = typeof(fe.getName) == 'undefined' ? fe.name : fe.getName();
447 if (this.findField(n)) { // already added..
450 var add = new Roo.form.Hidden({
460 * Mark fields in this form invalid in bulk.
461 * @param {Array/Object} errors Either an array in the form [{id:'fieldId', msg:'The message'},...] or an object hash of {id: msg, id2: msg2}
462 * @return {BasicForm} this
464 markInvalid : function(errors){
465 if(errors instanceof Array){
466 for(var i = 0, len = errors.length; i < len; i++){
467 var fieldError = errors[i];
468 var f = this.findField(fieldError.id);
470 f.markInvalid(fieldError.msg);
476 if(typeof errors[id] != 'function' && (field = this.findField(id))){
477 field.markInvalid(errors[id]);
481 Roo.each(this.childForms || [], function (f) {
482 f.markInvalid(errors);
489 * Set values for fields in this form in bulk.
490 * @param {Array/Object} values Either an array in the form [{id:'fieldId', value:'foo'},...] or an object hash of {id: value, id2: value2}
491 * @return {BasicForm} this
493 setValues : function(values){
494 if(values instanceof Array){ // array of objects
495 for(var i = 0, len = values.length; i < len; i++){
497 var f = this.findField(v.id);
500 if(this.trackResetOnLoad){
501 f.originalValue = f.getValue();
505 }else{ // object hash
508 if(typeof values[id] != 'function' && (field = this.findField(id))){
510 if (field.setFromData &&
512 field.displayField &&
513 // combos' with local stores can
514 // be queried via setValue()
515 // to set their value..
516 (field.store && !field.store.isLocal)
520 sd[field.valueField] = typeof(values[field.hiddenName]) == 'undefined' ? '' : values[field.hiddenName];
521 sd[field.displayField] = typeof(values[field.name]) == 'undefined' ? '' : values[field.name];
522 field.setFromData(sd);
525 field.setValue(values[id]);
529 if(this.trackResetOnLoad){
530 field.originalValue = field.getValue();
535 this.resetHasChanged();
538 Roo.each(this.childForms || [], function (f) {
547 * Returns the fields in this form as an object with key/value pairs. If multiple fields exist with the same name
548 * they are returned as an array.
549 * @param {Boolean} asString
552 getValues : function(asString){
553 if (this.childForms) {
554 // copy values from the child forms
555 Roo.each(this.childForms, function (f) {
556 this.setValues(f.getValues());
561 if (typeof(FormData) != 'undefined' && asString !== true) {
562 // this relies on a 'recent' version of chrome apparently...
564 var fd = (new FormData(this.el.dom)).entries();
568 ret[ent.value[0]] = ent.value[1]; // not sure how this will handle duplicates..
579 var fs = Roo.lib.Ajax.serializeForm(this.el.dom);
580 if(asString === true){
583 return Roo.urlDecode(fs);
587 * Returns the fields in this form as an object with key/value pairs.
588 * This differs from getValues as it calls getValue on each child item, rather than using dom data.
591 getFieldValues : function(with_hidden)
593 if (this.childForms) {
594 // copy values from the child forms
595 // should this call getFieldValues - probably not as we do not currently copy
596 // hidden fields when we generate..
597 Roo.each(this.childForms, function (f) {
598 this.setValues(f.getValues());
603 this.items.each(function(f){
607 var v = f.getValue();
608 if (f.inputType =='radio') {
609 if (typeof(ret[f.getName()]) == 'undefined') {
610 ret[f.getName()] = ''; // empty..
613 if (!f.el.dom.checked) {
621 // not sure if this supported any more..
622 if ((typeof(v) == 'object') && f.getRawValue) {
623 v = f.getRawValue() ; // dates..
625 // combo boxes where name != hiddenName...
626 if (f.name != f.getName()) {
627 ret[f.name] = f.getRawValue();
629 ret[f.getName()] = v;
636 * Clears all invalid messages in this form.
637 * @return {BasicForm} this
639 clearInvalid : function(){
640 this.items.each(function(f){
644 Roo.each(this.childForms || [], function (f) {
654 * @return {BasicForm} this
657 this.items.each(function(f){
661 Roo.each(this.childForms || [], function (f) {
664 this.resetHasChanged();
670 * Add Roo.form components to this form.
671 * @param {Field} field1
672 * @param {Field} field2 (optional)
673 * @param {Field} etc (optional)
674 * @return {BasicForm} this
677 this.items.addAll(Array.prototype.slice.call(arguments, 0));
683 * Removes a field from the items collection (does NOT remove its markup).
684 * @param {Field} field
685 * @return {BasicForm} this
687 remove : function(field){
688 this.items.remove(field);
693 * Looks at the fields in this form, checks them for an id attribute,
694 * and calls applyTo on the existing dom element with that id.
695 * @return {BasicForm} this
698 this.items.each(function(f){
699 if(f.isFormField && !f.rendered && document.getElementById(f.id)){ // if the element exists
707 * Calls {@link Ext#apply} for all fields in this form with the passed object.
708 * @param {Object} values
709 * @return {BasicForm} this
711 applyToFields : function(o){
712 this.items.each(function(f){
719 * Calls {@link Ext#applyIf} for all field in this form with the passed object.
720 * @param {Object} values
721 * @return {BasicForm} this
723 applyIfToFields : function(o){
724 this.items.each(function(f){
732 Roo.BasicForm = Roo.form.BasicForm;
734 Roo.apply(Roo.form.BasicForm, {
759 top : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-top-mask" }, true),
760 left : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-left-mask" }, true),
761 bottom : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-bottom-mask" }, true),
762 right : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-right-mask" }, true)
765 this.maskEl.top.enableDisplayMode("block");
766 this.maskEl.left.enableDisplayMode("block");
767 this.maskEl.bottom.enableDisplayMode("block");
768 this.maskEl.right.enableDisplayMode("block");
770 Roo.get(document.body).on('click', function(){
774 Roo.get(document.body).on('touchstart', function(){
778 this.isApplied = true
781 mask : function(form, target)
785 this.target = target;
787 if(!this.form.errorMask || !target.el){
791 var scrollable = this.target.el.findScrollableParent() || this.target.el.findParent('div.x-layout-active-content', 100, true) || Roo.get(document.body);
793 var ot = this.target.el.calcOffsetsTo(scrollable);
795 var scrollTo = ot[1] - this.form.maskOffset;
797 scrollTo = Math.min(scrollTo, scrollable.dom.scrollHeight);
799 scrollable.scrollTo('top', scrollTo);
801 var el = this.target.wrap || this.target.el;
803 var box = el.getBox();
805 this.maskEl.top.setStyle('position', 'absolute');
806 this.maskEl.top.setStyle('z-index', 10000);
807 this.maskEl.top.setSize(Roo.lib.Dom.getDocumentWidth(), box.y - this.padding);
808 this.maskEl.top.setLeft(0);
809 this.maskEl.top.setTop(0);
810 this.maskEl.top.show();
812 this.maskEl.left.setStyle('position', 'absolute');
813 this.maskEl.left.setStyle('z-index', 10000);
814 this.maskEl.left.setSize(box.x - this.padding, box.height + this.padding * 2);
815 this.maskEl.left.setLeft(0);
816 this.maskEl.left.setTop(box.y - this.padding);
817 this.maskEl.left.show();
819 this.maskEl.bottom.setStyle('position', 'absolute');
820 this.maskEl.bottom.setStyle('z-index', 10000);
821 this.maskEl.bottom.setSize(Roo.lib.Dom.getDocumentWidth(), Roo.lib.Dom.getDocumentHeight() - box.bottom - this.padding);
822 this.maskEl.bottom.setLeft(0);
823 this.maskEl.bottom.setTop(box.bottom + this.padding);
824 this.maskEl.bottom.show();
826 this.maskEl.right.setStyle('position', 'absolute');
827 this.maskEl.right.setStyle('z-index', 10000);
828 this.maskEl.right.setSize(Roo.lib.Dom.getDocumentWidth() - box.right - this.padding, box.height + this.padding * 2);
829 this.maskEl.right.setLeft(box.right + this.padding);
830 this.maskEl.right.setTop(box.y - this.padding);
831 this.maskEl.right.show();
833 this.intervalID = window.setInterval(function() {
834 Roo.form.BasicForm.popover.unmask();
837 window.onwheel = function(){ return false;};
839 (function(){ this.isMasked = true; }).defer(500, this);
845 if(!this.isApplied || !this.isMasked || !this.form || !this.target || !this.form.errorMask){
849 this.maskEl.top.setStyle('position', 'absolute');
850 this.maskEl.top.setSize(0, 0).setXY([0, 0]);
851 this.maskEl.top.hide();
853 this.maskEl.left.setStyle('position', 'absolute');
854 this.maskEl.left.setSize(0, 0).setXY([0, 0]);
855 this.maskEl.left.hide();
857 this.maskEl.bottom.setStyle('position', 'absolute');
858 this.maskEl.bottom.setSize(0, 0).setXY([0, 0]);
859 this.maskEl.bottom.hide();
861 this.maskEl.right.setStyle('position', 'absolute');
862 this.maskEl.right.setSize(0, 0).setXY([0, 0]);
863 this.maskEl.right.hide();
865 window.onwheel = function(){ return true;};
868 window.clearInterval(this.intervalID);
869 this.intervalID = false;
872 this.isMasked = false;