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 Should the form be masked (and the active element highlighted on error - default false
139 * @cfg {Number} maskOffset space around form element to mask if there is an error 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))){
513 if (field.setFromData &&
515 field.displayField &&
516 // combos' with local stores can
517 // be queried via setValue()
518 // to set their value..
519 (field.store && !field.store.isLocal)
523 sd[field.valueField] = typeof(values[field.hiddenName]) == 'undefined' ? '' : values[field.hiddenName];
524 sd[field.displayField] = typeof(values[field.name]) == 'undefined' ? '' : values[field.name];
525 field.setFromData(sd);
527 } else if (field.inputType && field.inputType == 'radio') {
529 field.setValue(values[id]);
531 field.setValue(values[id]);
535 if(this.trackResetOnLoad){
536 field.originalValue = field.getValue();
541 this.resetHasChanged();
544 Roo.each(this.childForms || [], function (f) {
553 * Returns the fields in this form as an object with key/value pairs. If multiple fields exist with the same name
554 * they are returned as an array.
555 * @param {Boolean} asString (def)
558 getValues : function(asString)
560 if (this.childForms) {
561 // copy values from the child forms
562 Roo.each(this.childForms, function (f) {
563 this.setValues(f.getFieldValues()); // get the full set of data, as we might be copying comboboxes from external into this one.
568 if (typeof(FormData) != 'undefined' && asString !== true) {
569 // this relies on a 'recent' version of chrome apparently...
571 var fd = (new FormData(this.el.dom)).entries();
575 ret[ent.value[0]] = ent.value[1]; // not sure how this will handle duplicates..
586 var fs = Roo.lib.Ajax.serializeForm(this.el.dom);
587 if(asString === true){
590 return Roo.urlDecode(fs);
594 * Returns the fields in this form as an object with key/value pairs.
595 * This differs from getValues as it calls getValue on each child item, rather than using dom data.
596 * Normally this will not return readOnly data
597 * @param {Boolean} with_readonly return readonly field data.
600 getFieldValues : function(with_readonly)
602 if (this.childForms) {
603 // copy values from the child forms
604 // should this call getFieldValues - probably not as we do not currently copy
605 // hidden fields when we generate..
606 Roo.each(this.childForms, function (f) {
607 this.setValues(f.getFieldValues());
612 this.items.each(function(f){
614 if (f.readOnly && with_readonly !== true) {
615 return; // skip read only values. - this is in theory to stop 'old' values being copied over new ones
616 // if a subform contains a copy of them.
617 // if you have subforms with the same editable data, you will need to copy the data back
624 var v = f.getValue();
625 if (f.inputType =='radio') {
626 if (typeof(ret[f.getName()]) == 'undefined') {
627 ret[f.getName()] = ''; // empty..
630 if (!f.el.dom.checked) {
638 // not sure if this supported any more..
639 if ((typeof(v) == 'object') && f.getRawValue) {
640 v = f.getRawValue() ; // dates..
642 // combo boxes where name != hiddenName...
643 if (f.name != f.getName()) {
644 ret[f.name] = f.getRawValue();
646 ret[f.getName()] = v;
653 * Clears all invalid messages in this form.
654 * @return {BasicForm} this
656 clearInvalid : function(){
657 this.items.each(function(f){
661 Roo.each(this.childForms || [], function (f) {
671 * @return {BasicForm} this
674 this.items.each(function(f){
678 Roo.each(this.childForms || [], function (f) {
681 this.resetHasChanged();
687 * Add Roo.form components to this form.
688 * @param {Field} field1
689 * @param {Field} field2 (optional)
690 * @param {Field} etc (optional)
691 * @return {BasicForm} this
694 this.items.addAll(Array.prototype.slice.call(arguments, 0));
700 * Removes a field from the items collection (does NOT remove its markup).
701 * @param {Field} field
702 * @return {BasicForm} this
704 remove : function(field){
705 this.items.remove(field);
710 * Looks at the fields in this form, checks them for an id attribute,
711 * and calls applyTo on the existing dom element with that id.
712 * @return {BasicForm} this
715 this.items.each(function(f){
716 if(f.isFormField && !f.rendered && document.getElementById(f.id)){ // if the element exists
724 * Calls {@link Ext#apply} for all fields in this form with the passed object.
725 * @param {Object} values
726 * @return {BasicForm} this
728 applyToFields : function(o){
729 this.items.each(function(f){
736 * Calls {@link Ext#applyIf} for all field in this form with the passed object.
737 * @param {Object} values
738 * @return {BasicForm} this
740 applyIfToFields : function(o){
741 this.items.each(function(f){
749 Roo.BasicForm = Roo.form.BasicForm;
751 Roo.apply(Roo.form.BasicForm, {
776 top : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-top-mask" }, true),
777 left : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-left-mask" }, true),
778 bottom : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-bottom-mask" }, true),
779 right : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-right-mask" }, true)
782 this.maskEl.top.enableDisplayMode("block");
783 this.maskEl.left.enableDisplayMode("block");
784 this.maskEl.bottom.enableDisplayMode("block");
785 this.maskEl.right.enableDisplayMode("block");
787 Roo.get(document.body).on('click', function(){
791 Roo.get(document.body).on('touchstart', function(){
795 this.isApplied = true
798 mask : function(form, target)
802 this.target = target;
804 if(!this.form.errorMask || !target.el){
808 var scrollable = this.target.el.findScrollableParent() || this.target.el.findParent('div.x-layout-active-content', 100, true) || Roo.get(document.body);
810 var ot = this.target.el.calcOffsetsTo(scrollable);
812 var scrollTo = ot[1] - this.form.maskOffset;
814 scrollTo = Math.min(scrollTo, scrollable.dom.scrollHeight);
816 scrollable.scrollTo('top', scrollTo);
818 var el = this.target.wrap || this.target.el;
820 var box = el.getBox();
822 this.maskEl.top.setStyle('position', 'absolute');
823 this.maskEl.top.setStyle('z-index', 10000);
824 this.maskEl.top.setSize(Roo.lib.Dom.getDocumentWidth(), box.y - this.padding);
825 this.maskEl.top.setLeft(0);
826 this.maskEl.top.setTop(0);
827 this.maskEl.top.show();
829 this.maskEl.left.setStyle('position', 'absolute');
830 this.maskEl.left.setStyle('z-index', 10000);
831 this.maskEl.left.setSize(box.x - this.padding, box.height + this.padding * 2);
832 this.maskEl.left.setLeft(0);
833 this.maskEl.left.setTop(box.y - this.padding);
834 this.maskEl.left.show();
836 this.maskEl.bottom.setStyle('position', 'absolute');
837 this.maskEl.bottom.setStyle('z-index', 10000);
838 this.maskEl.bottom.setSize(Roo.lib.Dom.getDocumentWidth(), Roo.lib.Dom.getDocumentHeight() - box.bottom - this.padding);
839 this.maskEl.bottom.setLeft(0);
840 this.maskEl.bottom.setTop(box.bottom + this.padding);
841 this.maskEl.bottom.show();
843 this.maskEl.right.setStyle('position', 'absolute');
844 this.maskEl.right.setStyle('z-index', 10000);
845 this.maskEl.right.setSize(Roo.lib.Dom.getDocumentWidth() - box.right - this.padding, box.height + this.padding * 2);
846 this.maskEl.right.setLeft(box.right + this.padding);
847 this.maskEl.right.setTop(box.y - this.padding);
848 this.maskEl.right.show();
850 this.intervalID = window.setInterval(function() {
851 Roo.form.BasicForm.popover.unmask();
854 window.onwheel = function(){ return false;};
856 (function(){ this.isMasked = true; }).defer(500, this);
862 if(!this.isApplied || !this.isMasked || !this.form || !this.target || !this.form.errorMask){
866 this.maskEl.top.setStyle('position', 'absolute');
867 this.maskEl.top.setSize(0, 0).setXY([0, 0]);
868 this.maskEl.top.hide();
870 this.maskEl.left.setStyle('position', 'absolute');
871 this.maskEl.left.setSize(0, 0).setXY([0, 0]);
872 this.maskEl.left.hide();
874 this.maskEl.bottom.setStyle('position', 'absolute');
875 this.maskEl.bottom.setSize(0, 0).setXY([0, 0]);
876 this.maskEl.bottom.hide();
878 this.maskEl.right.setStyle('position', 'absolute');
879 this.maskEl.right.setSize(0, 0).setXY([0, 0]);
880 this.maskEl.right.hide();
882 window.onwheel = function(){ return true;};
885 window.clearInterval(this.intervalID);
886 this.intervalID = false;
889 this.isMasked = false;