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.Form
14 * @extends Roo.form.BasicForm
15 * Adds the ability to dynamically render forms with JavaScript to {@link Roo.form.BasicForm}.
17 * @param {Object} config Configuration options
19 Roo.form.Form = function(config){
22 xitems = config.items;
27 Roo.form.Form.superclass.constructor.call(this, null, config);
28 this.url = this.url || this.action;
30 this.root = new Roo.form.Layout(Roo.applyIf({
34 this.active = this.root;
36 * Array of all the buttons that have been added to this form via {@link addButton}
43 * @event clientvalidation
44 * If the monitorValid config option is true, this event fires repetitively to notify of valid state
46 * @param {Boolean} valid true if the form has passed client-side validation
48 clientvalidation: true,
51 * Fires when the form is rendered
52 * @param {Roo.form.Form} form
57 if (this.progressUrl) {
58 // push a hidden field onto the list of fields..
62 name : 'UPLOAD_IDENTIFIER'
67 Roo.each(xitems, this.addxtype, this);
69 Roo.form.Form.popover.apply();
73 Roo.extend(Roo.form.Form, Roo.form.BasicForm, {
75 * @cfg {Number} labelWidth The width of labels. This property cascades to child containers.
78 * @cfg {String} itemCls A css class to apply to the x-form-item of fields. This property cascades to child containers.
81 * @cfg {String} buttonAlign Valid values are "left," "center" and "right" (defaults to "center")
86 * @cfg {Number} minButtonWidth Minimum width of all buttons in pixels (defaults to 75)
91 * @cfg {String} labelAlign Valid values are "left," "top" and "right" (defaults to "left").
92 * This property cascades to child containers if not set.
97 * @cfg {Boolean} monitorValid If true the form monitors its valid state <b>client-side</b> and
98 * fires a looping event with that state. This is required to bind buttons to the valid
99 * state using the config value formBind:true on the button.
101 monitorValid : false,
104 * @cfg {Number} monitorPoll The milliseconds to poll valid state, ignored if monitorValid is not true (defaults to 200)
109 * @cfg {String} progressUrl - Url to return progress data
114 * @cfg {boolean|FormData} formData - true to use new 'FormData' post, or set to a new FormData({dom form}) Object, if
115 * sending a formdata with extra parameters - eg uploaded elements.
121 * Opens a new {@link Roo.form.Column} container in the layout stack. If fields are passed after the config, the
122 * fields are added and the column is closed. If no fields are passed the column remains open
123 * until end() is called.
124 * @param {Object} config The config to pass to the column
125 * @param {Field} field1 (optional)
126 * @param {Field} field2 (optional)
127 * @param {Field} etc (optional)
128 * @return Column The column container object
130 column : function(c){
131 var col = new Roo.form.Column(c);
133 if(arguments.length > 1){ // duplicate code required because of Opera
134 this.add.apply(this, Array.prototype.slice.call(arguments, 1));
141 * Opens a new {@link Roo.form.FieldSet} container in the layout stack. If fields are passed after the config, the
142 * fields are added and the fieldset is closed. If no fields are passed the fieldset remains open
143 * until end() is called.
144 * @param {Object} config The config to pass to the fieldset
145 * @param {Field} field1 (optional)
146 * @param {Field} field2 (optional)
147 * @param {Field} etc (optional)
148 * @return FieldSet The fieldset container object
150 fieldset : function(c){
151 var fs = new Roo.form.FieldSet(c);
153 if(arguments.length > 1){ // duplicate code required because of Opera
154 this.add.apply(this, Array.prototype.slice.call(arguments, 1));
161 * Opens a new {@link Roo.form.Layout} container in the layout stack. If fields are passed after the config, the
162 * fields are added and the container is closed. If no fields are passed the container remains open
163 * until end() is called.
164 * @param {Object} config The config to pass to the Layout
165 * @param {Field} field1 (optional)
166 * @param {Field} field2 (optional)
167 * @param {Field} etc (optional)
168 * @return Layout The container object
170 container : function(c){
171 var l = new Roo.form.Layout(c);
173 if(arguments.length > 1){ // duplicate code required because of Opera
174 this.add.apply(this, Array.prototype.slice.call(arguments, 1));
181 * Opens the passed container in the layout stack. The container can be any {@link Roo.form.Layout} or subclass.
182 * @param {Object} container A Roo.form.Layout or subclass of Layout
183 * @return {Form} this
186 // cascade label info
187 Roo.applyIf(c, {'labelAlign': this.active.labelAlign, 'labelWidth': this.active.labelWidth, 'itemCls': this.active.itemCls});
188 this.active.stack.push(c);
189 c.ownerCt = this.active;
195 * Closes the current open container
196 * @return {Form} this
199 if(this.active == this.root){
202 this.active = this.active.ownerCt;
207 * Add Roo.form components to the current open container (e.g. column, fieldset, etc.). Fields added via this method
208 * can also be passed with an additional property of fieldLabel, which if supplied, will provide the text to display
209 * as the label of the field.
210 * @param {Field} field1
211 * @param {Field} field2 (optional)
212 * @param {Field} etc. (optional)
213 * @return {Form} this
216 this.active.stack.push.apply(this.active.stack, arguments);
217 this.allItems.push.apply(this.allItems,arguments);
219 for(var i = 0, a = arguments, len = a.length; i < len; i++) {
220 if(a[i].isFormField){
225 Roo.form.Form.superclass.add.apply(this, r);
235 * Find any element that has been added to a form, using it's ID or name
236 * This can include framesets, columns etc. along with regular fields..
237 * @param {String} id - id or name to find.
239 * @return {Element} e - or false if nothing found.
241 findbyId : function(id)
247 Roo.each(this.allItems, function(f){
248 if (f.id == id || f.name == id ){
259 * Render this form into the passed container. This should only be called once!
260 * @param {String/HTMLElement/Element} container The element this component should be rendered into
261 * @return {Form} this
263 render : function(ct)
269 var o = this.autoCreate || {
271 method : this.method || 'POST',
272 id : this.id || Roo.id()
274 this.initEl(ct.createChild(o));
276 this.root.render(this.el);
280 this.items.each(function(f){
281 f.render('x-form-el-'+f.id);
284 if(this.buttons.length > 0){
285 // tables are required to maintain order and for correct IE layout
286 var tb = this.el.createChild({cls:'x-form-btns-ct', cn: {
287 cls:"x-form-btns x-form-btns-"+this.buttonAlign,
288 html:'<table cellspacing="0"><tbody><tr></tr></tbody></table><div class="x-clear"></div>'
290 var tr = tb.getElementsByTagName('tr')[0];
291 for(var i = 0, len = this.buttons.length; i < len; i++) {
292 var b = this.buttons[i];
293 var td = document.createElement('td');
294 td.className = 'x-form-btn-td';
295 b.render(tr.appendChild(td));
298 if(this.monitorValid){ // initialize after render
299 this.startMonitoring();
301 this.fireEvent('rendered', this);
306 * Adds a button to the footer of the form - this <b>must</b> be called before the form is rendered.
307 * @param {String/Object} config A string becomes the button text, an object can either be a Button config
308 * object or a valid Roo.DomHelper element config
309 * @param {Function} handler The function called when the button is clicked
310 * @param {Object} scope (optional) The scope of the handler function
311 * @return {Roo.Button}
313 addButton : function(config, handler, scope){
317 minWidth: this.minButtonWidth,
320 if(typeof config == "string"){
323 Roo.apply(bc, config);
325 var btn = new Roo.Button(null, bc);
326 this.buttons.push(btn);
331 * Adds a series of form elements (using the xtype property as the factory method.
332 * Valid xtypes are: TextField, TextArea .... Button, Layout, FieldSet, Column, (and 'end' to close a block)
333 * @param {Object} config
336 addxtype : function()
338 var ar = Array.prototype.slice.call(arguments, 0);
340 for(var i = 0; i < ar.length; i++) {
342 continue; // skip -- if this happends something invalid got sent, we
343 // should ignore it, as basically that interface element will not show up
344 // and that should be pretty obvious!!
347 if (Roo.form[ar[i].xtype]) {
349 var fe = Roo.factory(ar[i], Roo.form);
355 fe.store.form = this;
360 this.allItems.push(fe);
361 if (fe.items && fe.addxtype) {
362 fe.addxtype.apply(fe, fe.items);
372 // console.log('adding ' + ar[i].xtype);
374 if (ar[i].xtype == 'Button') {
375 //console.log('adding button');
376 //console.log(ar[i]);
377 this.addButton(ar[i]);
378 this.allItems.push(fe);
382 if (ar[i].xtype == 'end') { // so we can add fieldsets... / layout etc.
383 alert('end is not supported on xtype any more, use items');
385 // //console.log('adding end');
393 * Starts monitoring of the valid state of this form. Usually this is done by passing the config
394 * option "monitorValid"
396 startMonitoring : function(){
400 run : this.bindHandler,
401 interval : this.monitorPoll || 200,
408 * Stops monitoring of the valid state of this form
410 stopMonitoring : function(){
415 bindHandler : function(){
417 return false; // stops binding
420 this.items.each(function(f){
421 if(!f.isValid(true)){
426 for(var i = 0, len = this.buttons.length; i < len; i++){
427 var btn = this.buttons[i];
428 if(btn.formBind === true && btn.disabled === valid){
429 btn.setDisabled(!valid);
432 this.fireEvent('clientvalidation', this, valid);
446 Roo.Form = Roo.form.Form;
449 Roo.apply(Roo.form.Form, {
476 top : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-top-mask" }, true),
477 left : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-left-mask" }, true),
478 bottom : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-bottom-mask" }, true),
479 right : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-right-mask" }, true)
482 this.maskEl.top.enableDisplayMode("block");
483 this.maskEl.left.enableDisplayMode("block");
484 this.maskEl.bottom.enableDisplayMode("block");
485 this.maskEl.right.enableDisplayMode("block");
487 this.toolTip = new Roo.bootstrap.Tooltip({
488 cls : 'roo-form-error-popover',
490 'left' : ['r-l', [-2,0], 'right'],
491 'right' : ['l-r', [2,0], 'left'],
492 'bottom' : ['tl-bl', [0,2], 'top'],
493 'top' : [ 'bl-tl', [0,-2], 'bottom']
497 this.toolTip.render(Roo.get(document.body));
499 this.toolTip.el.enableDisplayMode("block");
501 Roo.get(document.body).on('click', function(){
505 Roo.get(document.body).on('touchstart', function(){
509 this.isApplied = true
512 mask : function(form, target)
516 this.target = target;
518 if(!this.form.errorMask || !target.el){
522 var scrollable = this.target.el.findScrollableParent() || this.target.el.findParent('div.modal', 100, true) || Roo.get(document.body);
526 var ot = this.target.el.calcOffsetsTo(scrollable);
528 var scrollTo = ot[1] - this.form.maskOffset;
530 scrollTo = Math.min(scrollTo, scrollable.dom.scrollHeight);
532 scrollable.scrollTo('top', scrollTo);
534 var box = this.target.el.getBox();
536 var zIndex = Roo.bootstrap.Modal.zIndex++;
539 this.maskEl.top.setStyle('position', 'absolute');
540 this.maskEl.top.setStyle('z-index', zIndex);
541 this.maskEl.top.setSize(Roo.lib.Dom.getDocumentWidth(), box.y - this.padding);
542 this.maskEl.top.setLeft(0);
543 this.maskEl.top.setTop(0);
544 this.maskEl.top.show();
546 this.maskEl.left.setStyle('position', 'absolute');
547 this.maskEl.left.setStyle('z-index', zIndex);
548 this.maskEl.left.setSize(box.x - this.padding, box.height + this.padding * 2);
549 this.maskEl.left.setLeft(0);
550 this.maskEl.left.setTop(box.y - this.padding);
551 this.maskEl.left.show();
553 this.maskEl.bottom.setStyle('position', 'absolute');
554 this.maskEl.bottom.setStyle('z-index', zIndex);
555 this.maskEl.bottom.setSize(Roo.lib.Dom.getDocumentWidth(), Roo.lib.Dom.getDocumentHeight() - box.bottom - this.padding);
556 this.maskEl.bottom.setLeft(0);
557 this.maskEl.bottom.setTop(box.bottom + this.padding);
558 this.maskEl.bottom.show();
560 this.maskEl.right.setStyle('position', 'absolute');
561 this.maskEl.right.setStyle('z-index', zIndex);
562 this.maskEl.right.setSize(Roo.lib.Dom.getDocumentWidth() - box.right - this.padding, box.height + this.padding * 2);
563 this.maskEl.right.setLeft(box.right + this.padding);
564 this.maskEl.right.setTop(box.y - this.padding);
565 this.maskEl.right.show();
567 this.toolTip.bindEl = this.target.el;
569 this.toolTip.el.setStyle('z-index', Roo.bootstrap.Modal.zIndex++);
571 var tip = this.target.blankText;
573 if(this.target.getValue() !== '' ) {
575 if (this.target.invalidText.length) {
576 tip = this.target.invalidText;
577 } else if (this.target.regexText.length){
578 tip = this.target.regexText;
582 this.toolTip.show(tip);
584 this.intervalID = window.setInterval(function() {
585 Roo.bootstrap.Form.popover.unmask();
588 window.onwheel = function(){ return false;};
590 (function(){ this.isMasked = true; }).defer(500, this);
596 if(!this.isApplied || !this.isMasked || !this.form || !this.target || !this.form.errorMask){
600 this.maskEl.top.setStyle('position', 'absolute');
601 this.maskEl.top.setSize(0, 0).setXY([0, 0]);
602 this.maskEl.top.hide();
604 this.maskEl.left.setStyle('position', 'absolute');
605 this.maskEl.left.setSize(0, 0).setXY([0, 0]);
606 this.maskEl.left.hide();
608 this.maskEl.bottom.setStyle('position', 'absolute');
609 this.maskEl.bottom.setSize(0, 0).setXY([0, 0]);
610 this.maskEl.bottom.hide();
612 this.maskEl.right.setStyle('position', 'absolute');
613 this.maskEl.right.setSize(0, 0).setXY([0, 0]);
614 this.maskEl.right.hide();
618 this.toolTip.el.hide();
620 window.onwheel = function(){ return true;};
623 window.clearInterval(this.intervalID);
624 this.intervalID = false;
627 this.isMasked = false;