/*
* Based on:
* Ext JS Library 1.1.1
* Copyright(c) 2006-2007, Ext JS, LLC.
*
* Originally Released Under LGPL - original licence link has changed is not relivant.
*
* Fork - LGPL
* <script type="text/javascript">
*/
/**
* @class Roo.form.Form
* @extends Roo.form.BasicForm
* Adds the ability to dynamically render forms with JavaScript to {@link Roo.form.BasicForm}.
* @constructor
* @param {Object} config Configuration options
*/
Roo.form.Form = function(config){
var xitems = [];
if (config.items) {
xitems = config.items;
delete config.items;
}
Roo.form.Form.superclass.constructor.call(this, null, config);
this.url = this.url || this.action;
if(!this.root){
this.root = new Roo.form.Layout(Roo.applyIf({
id: Roo.id()
}, config));
}
this.active = this.root;
/**
* Array of all the buttons that have been added to this form via {@link addButton}
* @type Array
*/
this.buttons = [];
this.allItems = [];
this.addEvents({
/**
* @event clientvalidation
* If the monitorValid config option is true, this event fires repetitively to notify of valid state
* @param {Form} this
* @param {Boolean} valid true if the form has passed client-side validation
*/
clientvalidation: true,
/**
* @event rendered
* Fires when the form is rendered
* @param {Roo.form.Form} form
*/
rendered : true
});
if (this.progressUrl) {
// push a hidden field onto the list of fields..
this.addxtype( {
xns: Roo.form,
xtype : 'Hidden',
name : 'UPLOAD_IDENTIFIER'
});
}
Roo.each(xitems, this.addxtype, this);
};
Roo.extend(Roo.form.Form, Roo.form.BasicForm, {
/**
* @cfg {Number} labelWidth The width of labels. This property cascades to child containers.
*/
/**
* @cfg {String} itemCls A css class to apply to the x-form-item of fields. This property cascades to child containers.
*/
/**
* @cfg {String} buttonAlign Valid values are "left," "center" and "right" (defaults to "center")
*/
buttonAlign:'center',
/**
* @cfg {Number} minButtonWidth Minimum width of all buttons in pixels (defaults to 75)
*/
minButtonWidth:75,
/**
* @cfg {String} labelAlign Valid values are "left," "top" and "right" (defaults to "left").
* This property cascades to child containers if not set.
*/
labelAlign:'left',
/**
* @cfg {Boolean} monitorValid If true the form monitors its valid state <b>client-side</b> and
* fires a looping event with that state. This is required to bind buttons to the valid
* state using the config value formBind:true on the button.
*/
monitorValid : false,
/**
* @cfg {Number} monitorPoll The milliseconds to poll valid state, ignored if monitorValid is not true (defaults to 200)
*/
monitorPoll : 200,
/**
* @cfg {String} progressUrl - Url to return progress data
*/
progressUrl : false,
/**
* @cfg {boolean|FormData} formData - true to use new 'FormData' post, or set to a new FormData({dom form}) Object, if
* sending a formdata with extra parameters - eg uploaded elements.
*/
formData : false,
/**
* Opens a new {@link Roo.form.Column} container in the layout stack. If fields are passed after the config, the
* fields are added and the column is closed. If no fields are passed the column remains open
* until end() is called.
* @param {Object} config The config to pass to the column
* @param {Field} field1 (optional)
* @param {Field} field2 (optional)
* @param {Field} etc (optional)
* @return Column The column container object
*/
column : function(c){
var col = new Roo.form.Column(c);
this.start(col);
if(arguments.length > 1){ // duplicate code required because of Opera
this.add.apply(this, Array.prototype.slice.call(arguments, 1));
this.end();
}
return col;
},
/**
* Opens a new {@link Roo.form.FieldSet} container in the layout stack. If fields are passed after the config, the
* fields are added and the fieldset is closed. If no fields are passed the fieldset remains open
* until end() is called.
* @param {Object} config The config to pass to the fieldset
* @param {Field} field1 (optional)
* @param {Field} field2 (optional)
* @param {Field} etc (optional)
* @return FieldSet The fieldset container object
*/
fieldset : function(c){
var fs = new Roo.form.FieldSet(c);
this.start(fs);
if(arguments.length > 1){ // duplicate code required because of Opera
this.add.apply(this, Array.prototype.slice.call(arguments, 1));
this.end();
}
return fs;
},
/**
* Opens a new {@link Roo.form.Layout} container in the layout stack. If fields are passed after the config, the
* fields are added and the container is closed. If no fields are passed the container remains open
* until end() is called.
* @param {Object} config The config to pass to the Layout
* @param {Field} field1 (optional)
* @param {Field} field2 (optional)
* @param {Field} etc (optional)
* @return Layout The container object
*/
container : function(c){
var l = new Roo.form.Layout(c);
this.start(l);
if(arguments.length > 1){ // duplicate code required because of Opera
this.add.apply(this, Array.prototype.slice.call(arguments, 1));
this.end();
}
return l;
},
/**
* Opens the passed container in the layout stack. The container can be any {@link Roo.form.Layout} or subclass.
* @param {Object} container A Roo.form.Layout or subclass of Layout
* @return {Form} this
*/
start : function(c){
// cascade label info
Roo.applyIf(c, {'labelAlign': this.active.labelAlign, 'labelWidth': this.active.labelWidth, 'itemCls': this.active.itemCls});
this.active.stack.push(c);
c.ownerCt = this.active;
this.active = c;
return this;
},
/**
* Closes the current open container
* @return {Form} this
*/
end : function(){
if(this.active == this.root){
return this;
}
this.active = this.active.ownerCt;
return this;
},
/**
* Add Roo.form components to the current open container (e.g. column, fieldset, etc.). Fields added via this method
* can also be passed with an additional property of fieldLabel, which if supplied, will provide the text to display
* as the label of the field.
* @param {Field} field1
* @param {Field} field2 (optional)
* @param {Field} etc. (optional)
* @return {Form} this
*/
add : function(){
this.active.stack.push.apply(this.active.stack, arguments);
this.allItems.push.apply(this.allItems,arguments);
var r = [];
for(var i = 0, a = arguments, len = a.length; i < len; i++) {
if(a[i].isFormField){
r.push(a[i]);
}
}
if(r.length > 0){
Roo.form.Form.superclass.add.apply(this, r);
}
return this;
},
/**
* Find any element that has been added to a form, using it's ID or name
* This can include framesets, columns etc. along with regular fields..
* @param {String} id - id or name to find.
* @return {Element} e - or false if nothing found.
*/
findbyId : function(id)
{
var ret = false;
if (!id) {
return ret;
}
Roo.each(this.allItems, function(f){
if (f.id == id || f.name == id ){
ret = f;
return false;
}
});
return ret;
},
/**
* Render this form into the passed container. This should only be called once!
* @param {String/HTMLElement/Element} container The element this component should be rendered into
* @return {Form} this
*/
render : function(ct)
{
ct = Roo.get(ct);
var o = this.autoCreate || {
tag: 'form',
method : this.method || 'POST',
id : this.id || Roo.id()
};
this.initEl(ct.createChild(o));
this.root.render(this.el);
this.items.each(function(f){
f.render('x-form-el-'+f.id);
});
if(this.buttons.length > 0){
// tables are required to maintain order and for correct IE layout
var tb = this.el.createChild({cls:'x-form-btns-ct', cn: {
cls:"x-form-btns x-form-btns-"+this.buttonAlign,
html:'<table cellspacing="0"><tbody><tr></tr></tbody></table><div class="x-clear"></div>'
}}, null, true);
var tr = tb.getElementsByTagName('tr')[0];
for(var i = 0, len = this.buttons.length; i < len; i++) {
var b = this.buttons[i];
var td = document.createElement('td');
td.className = 'x-form-btn-td';
b.render(tr.appendChild(td));
}
}
if(this.monitorValid){ // initialize after render
this.startMonitoring();
}
this.fireEvent('rendered', this);
return this;
},
/**
* Adds a button to the footer of the form - this <b>must</b> be called before the form is rendered.
* @param {String/Object} config A string becomes the button text, an object can either be a Button config
* object or a valid Roo.DomHelper element config
* @param {Function} handler The function called when the button is clicked
* @param {Object} scope (optional) The scope of the handler function
* @return {Roo.Button}
*/
addButton : function(config, handler, scope){
var bc = {
handler: handler,
scope: scope,
minWidth: this.minButtonWidth,
hideParent:true
};
if(typeof config == "string"){
bc.text = config;
}else{
Roo.apply(bc, config);
}
var btn = new Roo.Button(null, bc);
this.buttons.push(btn);
return btn;
},
/**
* Adds a series of form elements (using the xtype property as the factory method.
* Valid xtypes are: TextField, TextArea .... Button, Layout, FieldSet, Column, (and 'end' to close a block)
* @param {Object} config
*/
addxtype : function()
{
var ar = Array.prototype.slice.call(arguments, 0);
var ret = false;
for(var i = 0; i < ar.length; i++) {
if (!ar[i]) {
continue; // skip -- if this happends something invalid got sent, we
// should ignore it, as basically that interface element will not show up
// and that should be pretty obvious!!
}
if (Roo.form[ar[i].xtype]) {
ar[i].form = this;
var fe = Roo.factory(ar[i], Roo.form);
if (!ret) {
ret = fe;
}
fe.form = this;
if (fe.store) {
fe.store.form = this;
}
if (fe.isLayout) {
this.start(fe);
this.allItems.push(fe);
if (fe.items && fe.addxtype) {
fe.addxtype.apply(fe, fe.items);
delete fe.items;
}
this.end();
continue;
}
this.add(fe);
// console.log('adding ' + ar[i].xtype);
}
if (ar[i].xtype == 'Button') {
//console.log('adding button');
//console.log(ar[i]);
this.addButton(ar[i]);
this.allItems.push(fe);
continue;
}
if (ar[i].xtype == 'end') { // so we can add fieldsets... / layout etc.
alert('end is not supported on xtype any more, use items');
// this.end();
// //console.log('adding end');
}
}
return ret;
},
/**
* Starts monitoring of the valid state of this form. Usually this is done by passing the config
* option "monitorValid"
*/
startMonitoring : function(){
if(!this.bound){
this.bound = true;
Roo.TaskMgr.start({
run : this.bindHandler,
interval : this.monitorPoll || 200,
scope: this
});
}
},
/**
* Stops monitoring of the valid state of this form
*/
stopMonitoring : function(){
this.bound = false;
},
// private
bindHandler : function(){
if(!this.bound){
return false; // stops binding
}
var valid = true;
this.items.each(function(f){
if(!f.isValid(true)){
valid = false;
return false;
}
});
for(var i = 0, len = this.buttons.length; i < len; i++){
var btn = this.buttons[i];
if(btn.formBind === true && btn.disabled === valid){
btn.setDisabled(!valid);
}
}
this.fireEvent('clientvalidation', this, valid);
}
});
// back compat
Roo.Form = Roo.form.Form;