/*!
* Ext JS Library 3.3.0
*/
/**
* @class Ext.form.CompositeField
* @extends Ext.form.Field
* Composite field allowing a number of form Fields to be rendered on the same row. The fields are rendered
* using an hbox(水平horizontal布局) layout internally(内部地), so all of the normal HBox layout config items are available.
* Example usage:
{
xtype: 'compositefield',
labelWidth: 120
items: [
{
xtype : 'textfield',
fieldLabel: 'Title',
width : 20
},
{
xtype : 'textfield',
fieldLabel: 'First',
flex : 1
//说明:一行中,除了分给Title的20px外,剩余的被分成两等份,First和Last个占一份;
},
{
xtype : 'textfield',
fieldLabel: 'Last',
flex : 1
}
]
}
*
* In the example above the composite's fieldLabel will be set to 'Title, First, Last' as it groups the fieldLabels
* of each of its children. This can be overridden by setting a fieldLabel on the compositefield itself:
{
xtype: 'compositefield',
fieldLabel: 'Custom label',
items: [...]
}
*
* Any Ext.form.* component can be placed inside a composite field(所有的表单元素,都可以放进composite组件中);
*/
Ext.form.CompositeField = Ext.extend(Ext.form.Field, {
//属性:
/**
* @property defaultMargins
* @type String
* The margins to apply by default to each field in the composite;
* 默认子组件之间分隔5像素;
*/
defaultMargins: '0 5 0 0',
/**
* @property skipLastItemMargin
* @type Boolean
* If true, the defaultMargins are not applied to the last item in the composite field set (defaults to true);
* 当此属性为真时,将略过最后子组件,也就是说,defaultMargins属性不会影响最后子组件;
*/
skipLastItemMargin: true,
/**
* @property isComposite
* @type Boolean
* Signifies(意味着) that this is a Composite field
*/
isComposite: true,
/**
* @property combineErrors
* @type Boolean
* True to combine errors from the individual(单独的) fields into a single error message at the CompositeField
* level (defaults to true)
*/
combineErrors: true,
/**
* @cfg {String} labelConnector The string to use when joining segments(部分) of the built label
* together (defaults to ', ');(如果子组件配置有各自label属性)合并各个子组件的label,以“,”符号隔开;
*/
labelConnector: ', ',
/**
* @cfg {Object} defaults Any default properties to assign to the child fields.
*/
//初始化:
initComponent: function() {
var labels = [],
items = this.items || [],
item;
for (var i=0, j = items.length; i < j; i++) {
item = items[i];
labels.push(item.fieldLabel);
//apply any defaults
Ext.applyIf(item, this.defaults);
//apply default margins to each item except the last
if (!(i == j - 1 && this.skipLastItemMargin)) {
Ext.applyIf(item, {margins: this.defaultMargins});
}
}
//composite fieldLabel:
this.fieldLabel = this.fieldLabel || this.buildLabel(labels);
/**
* @property fieldErrors
* @type Ext.util.MixedCollection
* MixedCollection of current errors on the Composite's subfields. This is used internally to track when
* to show and hide error messages at the Composite level. Listeners are attached to the MixedCollection's
* add, remove and replace events to update the error icon in the UI as errors are added or removed.
*/
this.fieldErrors = new Ext.util.MixedCollection(true, function(item) {
return item.field;
});
this.fieldErrors.on({
scope : this,
add : this.updateInvalidMark,
remove : this.updateInvalidMark,
replace: this.updateInvalidMark
});
Ext.form.CompositeField.superclass.initComponent.apply(this, arguments);
this.innerCt = new Ext.Container({
layout : 'hbox',
items : this.items,
cls : 'x-form-composite',
defaultMargins: '0 3 0 0'
});
var fields = this.innerCt.findBy(function(c) {
return c.isFormField;
}, this);
/**
* @property items
* @type Ext.util.MixedCollection
* Internal collection of all of the subfields in this Composite
*/
this.items = new Ext.util.MixedCollection();
this.items.addAll(fields);
},
/**
* @private
* Creates an internal container using hbox and renders the fields to it
*/
onRender: function(ct, position) {
if (!this.el) {
/**
* @property innerCt
* @type Ext.Container
* A container configured with hbox layout which is responsible(负责) for laying out the subfields
*/
var innerCt = this.innerCt;
innerCt.render(ct);
this.el = innerCt.getEl();
//if we're combining subfield errors into a single message, override the markInvalid and clearInvalid
//methods of each subfield and show them at the Composite level instead
if (this.combineErrors) {
this.eachItem(function(field) {
Ext.apply(field, {
markInvalid : this.onFieldMarkInvalid.createDelegate(this, [field], 0),
clearInvalid: this.onFieldClearInvalid.createDelegate(this, [field], 0)
});
});
}
//set the label 'for' to the first item
var l = this.el.parent().parent().child('label', true);
if (l) {
l.setAttribute('for', this.items.items[0].id);
}
}
Ext.form.CompositeField.superclass.onRender.apply(this, arguments);
},
/**
* Called if combineErrors is true and a subfield's markInvalid method is called.
* By default this just adds the subfield's error to the internal fieldErrors MixedCollection
* @param {Ext.form.Field} field The field that was marked invalid
* @param {String} message The error message
*/
onFieldMarkInvalid: function(field, message) {
var name = field.getName(),
error = {
field: name,
errorName: field.fieldLabel || name,
error: message
};
this.fieldErrors.replace(name, error);
field.el.addClass(field.invalidClass);
},
/**
* Called if combineErrors is true and a subfield's clearInvalid method is called.
* By default this just updates the internal fieldErrors MixedCollection.
* @param {Ext.form.Field} field The field that was marked invalid
*/
onFieldClearInvalid: function(field) {
this.fieldErrors.removeKey(field.getName());
field.el.removeClass(field.invalidClass);
},
/**
* @private
* Called after a subfield is marked valid or invalid, this checks to see if any of the subfields are
* currently invalid. If any subfields are invalid it builds a combined error message marks the composite
* invalid, otherwise clearInvalid is called
*/
updateInvalidMark: function() {
var ieStrict = Ext.isIE6 && Ext.isStrict;
if (this.fieldErrors.length == 0) {
this.clearInvalid();
//IE6 in strict mode has a layout bug when using 'under' as the error message target. This fixes it
if (ieStrict) {
this.clearInvalid.defer(50, this);
}
} else {
var message = this.buildCombinedErrorMessage(this.fieldErrors.items);
this.sortErrors();
this.markInvalid(message);
//IE6 in strict mode has a layout bug when using 'under' as the error message target. This fixes it
if (ieStrict) {
this.markInvalid(message);
}
}
},
/**
* Performs(执行) validation checks on each subfield and returns false if any of them fail validation.
* @return {Boolean} False if any subfield failed validation
*/
validateValue: function() {
var valid = true;
this.eachItem(function(field) {
if (!field.isValid()) valid = false;
});
return valid;
},
/**
* Takes an object containing error messages for contained fields, returning a combined error
* string (defaults to just placing each item on a new line). This can be overridden to provide
* custom combined error message handling.
* @param {Array} errors Array of errors in format: [{field: 'title', error: 'some error'}]
* @return {String} The combined error message
*/
buildCombinedErrorMessage: function(errors) {
var combined = [],
error;
for (var i = 0, j = errors.length; i < j; i++) {
error = errors[i];
combined.push(String.format("{0}: {1}", error.errorName, error.error));
}
return combined.join("");
},
/**
* Sorts the internal fieldErrors MixedCollection by the order in which the fields are defined.
* This is called before displaying errors to ensure that the errors are presented in the expected order.
* This function can be overridden to provide a custom sorting order if needed.
*/
sortErrors: function() {
var fields = this.items;
this.fieldErrors.sort("ASC", function(a, b) {
var findByName = function(key) {
return function(field) {
return field.getName() == key;
};
};
var aIndex = fields.findIndexBy(findByName(a.field)),
bIndex = fields.findIndexBy(findByName(b.field));
return aIndex < bIndex ? -1 : 1;
});
},
/**
* Resets each field in the composite to their previous value
*/
reset: function() {
this.eachItem(function(item) {
item.reset();
});
// Defer(延迟) the clearInvalid so if BaseForm's collection is being iterated(重复)
// it will be called AFTER it is complete.
// Important because reset is being called on both the group and the individual(单独的) items.
(function() {
this.clearInvalid();
}).defer(50, this);
},
/**
* Calls clearInvalid on all child fields. This is a convenience(有用的) function and should not often need to be called
* as fields usually take care of clearing themselves
*/
clearInvalidChildren: function() {
this.eachItem(function(item) {
item.clearInvalid();
});
},
/**
* Builds a label string from an array of subfield labels.
* By default this just joins the labels together with a comma(逗号)
* @param {Array} segments Array of each of the labels in the composite field's subfields
* @return {String} The built label
*/
buildLabel: function(segments) {
//Ext.clean()_为传递进来的数组创建一个副本,该副本剔除了所有错误的值;
return Ext.clean(segments).join(this.labelConnector);
},
/**
* Checks each field in the composite and returns true if any is dirty
* @return {Boolean} True if any field is dirty
*/
isDirty: function(){
//override the behaviour to check sub items.
if (this.disabled || !this.rendered) {
return false;
}
var dirty = false;
this.eachItem(function(item){
if(item.isDirty()){
dirty = true;
return false;
}
});
return dirty;
},
/**
* @private
* Convenience function which passes the given function to every item in the composite
* @param {Function} fn The function to call
* @param {Object} scope Optional scope object
*/
eachItem: function(fn, scope) {
if(this.items && this.items.each){
this.items.each(fn, scope || this);
}
},
/**
* @private
* Passes the resize call through to the inner panel
*/
onResize: function(adjWidth, adjHeight, rawWidth, rawHeight) {
var innerCt = this.innerCt;
if (this.rendered && innerCt.rendered) {
innerCt.setSize(adjWidth, adjHeight);
}
Ext.form.CompositeField.superclass.onResize.apply(this, arguments);
},
/**
* @private
* Forces the internal container to be laid out again
*/
doLayout: function(shallow, force) {
if (this.rendered) {
var innerCt = this.innerCt;
innerCt.forceLayout = this.ownerCt.forceLayout;
innerCt.doLayout(shallow, force);
}
},
/**
* @private
*/
beforeDestroy: function(){
Ext.destroy(this.innerCt);
Ext.form.CompositeField.superclass.beforeDestroy.call(this);
},
//override the behaviour to check sub items.
setReadOnly : function(readOnly) {
if (readOnly == undefined) {
readOnly = true;
}
readOnly = !!readOnly;
if(this.rendered){
this.eachItem(function(item){
item.setReadOnly(readOnly);
});
}
this.readOnly = readOnly;
},
onShow : function() {
Ext.form.CompositeField.superclass.onShow.call(this);
this.doLayout();
},
//override the behaviour to check sub items.
onDisable : function(){
this.eachItem(function(item){
item.disable();
});
},
//override the behaviour to check sub items.
onEnable : function(){
this.eachItem(function(item){
item.enable();
});
}
});
Ext.reg('compositefield', Ext.form.CompositeField);