/**
* A {@link Ext.form.FieldContainer field container} which has a specialized layout for arranging
* {@link Ext.form.field.Radio} controls into columns, and provides convenience
* {@link Ext.form.field.Field} methods for {@link #getValue getting}, {@link #setValue setting},
* and {@link #validate validating} the group of radio buttons as a whole.
*
* ## Validation
*
* Individual radio buttons themselves have no default validation behavior, but
* sometimes you want to require a user to select one of a group of radios. RadioGroup
* allows this by setting the config `{@link #allowBlank}:false`; when the user does not check at
* one of the radio buttons, the entire group will be highlighted as invalid and the
* {@link #blankText error message} will be displayed according to the {@link #msgTarget} config.
*
* ## Layout
*
* The default layout for RadioGroup makes it easy to arrange the radio buttons into
* columns; see the {@link #columns} and {@link #vertical} config documentation for details.
* You may also use a completely different layout by setting the {@link #cfg-layout} to one of the
* other supported layout types; for instance you may wish to use a custom arrangement
* of hbox and vbox containers. In that case the Radio components at any depth will
* still be managed by the RadioGroup's validation.
*
* ## Example usage
*
* @example
* Ext.create('Ext.form.Panel', {
* title: 'RadioGroup Example',
* width: 300,
* bodyPadding: 10,
* renderTo: Ext.getBody(),
* items:[{
* xtype: 'radiogroup',
* fieldLabel: 'Two Columns',
* // Arrange radio buttons into two columns, distributed vertically
* columns: 2,
* vertical: true,
* items: [
* { boxLabel: 'Item 1', name: 'rb', inputValue: '1' },
* { boxLabel: 'Item 2', name: 'rb', inputValue: '2', checked: true},
* { boxLabel: 'Item 3', name: 'rb', inputValue: '3' },
* { boxLabel: 'Item 4', name: 'rb', inputValue: '4' },
* { boxLabel: 'Item 5', name: 'rb', inputValue: '5' },
* { boxLabel: 'Item 6', name: 'rb', inputValue: '6' }
* ]
* }]
* });
*
* ## Example with value binding to the RadioGroup. In the below example, "Item 2" will
* initially be checked using `myValue: '2'` from the ViewModel.
*
* @example
* Ext.define('MyApp.main.view.Main', {
* extend: 'Ext.app.ViewModel',
* alias: 'viewmodel.main',
* data: {
* myValue: '2'
* }
* });
*
* Ext.create('Ext.form.Panel', {
* title: 'RadioGroup Example',
* viewModel: {
* type: 'main'
* },
* width: 300,
* bodyPadding: 10,
* renderTo: Ext.getBody(),
* items:[{
* xtype: 'radiogroup',
* fieldLabel: 'Two Columns',
* // Arrange radio buttons into two columns, distributed vertically
* columns: 2,
* vertical: true,
* simpleValue: true, // set simpleValue to true to enable value binding
* bind: '{myValue}',
* items: [
* { boxLabel: 'Item 1', name: 'rb', inputValue: '1' },
* { boxLabel: 'Item 2', name: 'rb', inputValue: '2' },
* { boxLabel: 'Item 3', name: 'rb', inputValue: '3' },
* { boxLabel: 'Item 4', name: 'rb', inputValue: '4' },
* { boxLabel: 'Item 5', name: 'rb', inputValue: '5' },
* { boxLabel: 'Item 6', name: 'rb', inputValue: '6' }
* ]
* }]
* });
*
*/
Ext.define('Ext.form.RadioGroup', {
extend: 'Ext.form.CheckboxGroup',
xtype: 'radiogroup',
/**
* @property {Boolean} isRadioGroup
* The value `true` to identify an object as an instance of this or derived class.
* @readonly
* @since 6.2.0
*/
isRadioGroup: true,
requires: [
'Ext.form.field.Radio'
],
/**
* @cfg {Ext.form.field.Radio[]/Object[]} items
* An Array of {@link Ext.form.field.Radio Radio}s or Radio config objects to arrange
* in the group.
*/
/**
* @cfg {Boolean} allowBlank
* True to allow every item in the group to be blank.
* If allowBlank = false and no items are selected at validation time,
* {@link #blankText} will be used as the error text.
*/
allowBlank: true,
/**
* @cfg {String} blankText
* Error text to display if the {@link #allowBlank} validation fails
* @locale
*/
blankText: 'You must select one item in this group',
defaultType: 'radiofield',
/**
* @cfg {Boolean} [local=false]
* By default, child {@link Ext.form.field.Radio radio} `name`s are scoped to the
* encapsulating {@link Ext.form.Panel form panel} if any, of the document.
*
* If you are using multiple `RadioGroup`s each of which uses the same `name`
* configuration in child {@link Ext.form.field.Radio radio}s, configure this as
* `true` to scope the names to within this `RadioGroup`
*/
local: false,
/**
* @cfg {Boolean} simpleValue
* When set to `true` the `value` of this group of `radiofield` components will be
* mapped to the `inputValue` of the checked item. This is, the `getValue` method
* will return the `inputValue` of the checked item while `setValue` will check the
* `radiofield` whose `inputValue` matches the given value.
*
* This field allows the `radiogroup` to participate in binding an entire group of
* radio buttons to a single value.
*
* In the below example, "Item 2" will initially be checked using `myValue: '2'` from
* the ViewModel.
*
* @example
* Ext.define('MyApp.main.view.Main', {
* extend: 'Ext.app.ViewModel',
* alias: 'viewmodel.main',
* data: {
* myValue: '2'
* }
* });
*
* Ext.create('Ext.form.Panel', {
* title: 'RadioGroup Example',
* viewModel: {
* type: 'main'
* },
* width: 300,
* bodyPadding: 10,
* renderTo: Ext.getBody(),
* items:[{
* xtype: 'radiogroup',
* fieldLabel: 'Two Columns',
* // Arrange radio buttons into two columns, distributed vertically
* columns: 2,
* vertical: true,
* simpleValue: true, // set simpleValue to true to enable value binding
* bind: '{myValue}',
* items: [
* { boxLabel: 'Item 1', name: 'rb', inputValue: '1' },
* { boxLabel: 'Item 2', name: 'rb', inputValue: '2' },
* { boxLabel: 'Item 3', name: 'rb', inputValue: '3' },
* { boxLabel: 'Item 4', name: 'rb', inputValue: '4' },
* { boxLabel: 'Item 5', name: 'rb', inputValue: '5' },
* { boxLabel: 'Item 6', name: 'rb', inputValue: '6' }
* ]
* }]
* });
*
* @since 6.2.0
*/
simpleValue: false,
defaultBindProperty: 'value',
/**
* @private
*/
groupCls: Ext.baseCSSPrefix + 'form-radio-group',
ariaRole: 'radiogroup',
initRenderData: function() {
var me = this,
data, ariaAttr;
data = me.callParent();
ariaAttr = data.ariaAttributes;
if (ariaAttr) {
ariaAttr['aria-required'] = !me.allowBlank;
ariaAttr['aria-invalid'] = false;
}
return data;
},
lookupComponent: function(config) {
var result = this.callParent([config]);
// Local means that the exclusivity of checking by name is scoped to this RadioGroup.
// So multiple RadioGroups can be used which use the same Radio names.
// This enables their use as a grid widget.
if (this.local) {
result.formId = this.getId();
}
return result;
},
getBoxes: function(query, root) {
return (root || this).query('[isRadio]' + (query || ''));
},
checkChange: function() {
var me = this,
value, key;
value = me.getValue();
// Safari might throw an exception on trying to get the keys of a Number
key = typeof value === 'object' && Ext.Object.getKeys(value)[0];
// If the value is an array we skip out here because it's during a change
// between multiple items, so we never want to fire a change.
// Check if we have a radio group not using simpleValue that has been
// reset for which we want to fire a change event
if (me.simpleValue || ((key && !Ext.isArray(value[key])) ||
(Ext.isObject(value) && Ext.Object.isEmpty(value)))) {
me.callParent();
}
},
isEqual: function(value1, value2) {
if (this.simpleValue) {
return value1 === value2;
}
return this.callParent([ value1, value2 ]);
},
getValue: function() {
var me = this,
items = me.items.items,
i, item, ret;
if (me.simpleValue) {
for (i = items.length; i-- > 0;) {
item = items[i];
if (item.checked) {
ret = item.inputValue;
break;
}
}
}
else {
ret = me.callParent();
}
return ret;
},
/**
* Sets the value of the radio group. The radio with corresponding name and value will be set.
* This method is simpler than {@link Ext.form.CheckboxGroup#setValue} because only 1 value
* is allowed for each name. You can use the setValue method as:
*
* var form = Ext.create('Ext.form.Panel', {
* title : 'RadioGroup Example',
* width : 300,
* bodyPadding : 10,
* renderTo : Ext.getBody(),
* items : [
* {
* xtype : 'radiogroup',
* fieldLabel : 'Group',
* items : [
* { boxLabel : 'Item 1', name : 'rb', inputValue : 1 },
* { boxLabel : 'Item 2', name : 'rb', inputValue : 2 }
* ]
* }
* ],
* tbar : [
* {
* text : 'setValue on RadioGroup',
* handler : function() {
* form.child('radiogroup').setValue({
* rb : 2
* });
* }
* }
* ]
* });
*
* @param {Mixed} value An Object to map names to values to be set. If not an Object,
* this `radiofield` with a matching `inputValue` will be checked.
* @return {Ext.form.RadioGroup} this
*/
setValue: function(value) {
var items = this.items,
cbValue, cmp, formId, radios, i, len, name;
Ext.suspendLayouts();
if (this.simpleValue) {
for (i = 0, len = items.length; i < len; ++i) {
cmp = items.items[i];
cmp.$groupChange = true;
cmp.setValue(cmp.inputValue === value);
delete cmp.$groupChange;
}
}
else if (Ext.isObject(value)) {
cmp = items.first();
formId = cmp ? cmp.getFormId() : null;
for (name in value) {
cbValue = value[name];
radios = Ext.form.RadioManager.getWithValue(name, cbValue, formId).items;
len = radios.length;
for (i = 0; i < len; ++i) {
radios[i].setValue(true);
}
}
}
Ext.resumeLayouts(true);
return this;
},
markInvalid: function(errors) {
var ariaDom = this.ariaEl.dom;
this.callParent([errors]);
if (ariaDom) {
ariaDom.setAttribute('aria-invalid', true);
}
},
clearInvalid: function() {
var ariaDom = this.ariaEl.dom;
this.callParent();
if (ariaDom) {
ariaDom.setAttribute('aria-invalid', false);
}
}
}, function() {
// Firefox has a nasty bug, or a misfeature, with tabbing over radio buttons
// when there is no checked button in a group. In such case the first button
// in the group should be focused upon tabbing into the group, and subsequent
// tab key press should leave the group; however Firefox will tab over every
// radio button individually as if they were checkboxes.
// Fortunately for us this bugfeature only applies to tabbing; arrow key
// navigation is not affected. So the issue can be easily worked around
// by removing all group buttons from tab order upon focusing the first button
// in the group, and restoring their tabbable state upon focusleave.
// This works exactly the same way regardless of having or not a checked button
// in the group, so we keep the code simple.
// This condition should get more version specific when this bug is fixed:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1267488
if (Ext.isGecko) {
this.override({
onFocusEnter: function(e) {
var target = e.toComponent,
radios, i, len;
if (target.isRadio) {
radios = target.getManager().getByName(target.name, target.getFormId()).items;
for (i = 0, len = radios.length; i < len; i++) {
radios[i].disableTabbing();
}
}
},
onFocusLeave: function(e) {
var target = e.fromComponent,
radios, i, len;
if (target.isRadio) {
radios = target.getManager().getByName(target.name, target.getFormId()).items;
for (i = 0, len = radios.length; i < len; i++) {
radios[i].enableTabbing();
}
}
}
});
}
});