/*!
* UI development toolkit for HTML5 (OpenUI5)
* (c) Copyright 2009-2018 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
// Provides control sap.m.RadioButton.
sap.ui.define([
'./library',
'sap/ui/core/Control',
'sap/ui/core/EnabledPropagator',
'./RadioButtonGroup',
'sap/ui/core/library',
'./RadioButtonRenderer',
'sap/ui/core/message/MessageMixin'
],
function(
library,
Control,
EnabledPropagator,
RadioButtonGroup,
coreLibrary,
RadioButtonRenderer,
MessageMixin
) {
"use strict";
// shortcut for sap.ui.core.TextAlign
var TextAlign = coreLibrary.TextAlign;
// shortcut for sap.ui.core.ValueState
var ValueState = coreLibrary.ValueState;
// shortcut for sap.ui.core.TextDirection
var TextDirection = coreLibrary.TextDirection;
/**
* Constructor for a new RadioButton.
*
* @param {string} [sId] ID for the new control, generated automatically if no ID is given
* @param {object} [mSettings] Initial settings for the new control
* Enables users to select a single option from a set of options.
* @class
* RadioButton is a control similar to a {@link sap.m.CheckBox checkbox}, but it allows you to choose only one of the predefined set of options.
* Multiple radio buttons have to belong to the same group (have the same value for groupName
) in order to be mutually exclusive.
* A wrapper control {@link sap.m.RadioButtonGroup RadioButtonGroup} can be used instead of individual radio buttons.
*
column
to a number higher than 1.button1, button2, button3
) in consecutive order, initially they will have the same positions
* and TAB order. However if after that button1
and button3
are moved to a new group and then button2
is added to the
* same group, their TAB order and position in this group will be button1, button3, button2
.
*
* @extends sap.ui.core.Control
* @implements sap.ui.core.IFormContent
*
* @author SAP SE
* @version 1.60.23
*
* @constructor
* @public
* @alias sap.m.RadioButton
* @see {@link fiori:https://experience.sap.com/fiori-design-web/radio-button/ Radio Button}
* @ui5-metamodel This control/element also will be described in the UI5 (legacy) designtime metamodel
*/
var RadioButton = Control.extend("sap.m.RadioButton", /** @lends sap.m.RadioButton.prototype */ { metadata : {
interfaces : ["sap.ui.core.IFormContent"],
library : "sap.m",
properties : {
/**
* Specifies if the radio button is disabled.
*/
enabled : {type : "boolean", group : "Behavior", defaultValue : true},
/**
* Specifies the select state of the radio button
*/
selected : {type : "boolean", group : "Data", defaultValue : false},
/**
* Name of the radio button group the current radio button belongs to. You can define a new name for the group.
* If no new name is specified, this radio button belongs to the sapMRbDefaultGroup per default. Default behavior of a radio button in a group is that when one of the radio buttons in a group is selected, all others are unselected.
*/
groupName : {type : "string", group : "Behavior", defaultValue : 'sapMRbDefaultGroup'},
/**
* Specifies the text displayed next to the RadioButton
*/
text : {type : "string", group : "Appearance", defaultValue : null},
/**
* Options for the text direction are RTL and LTR. Alternatively, the control can inherit the text direction from its parent container.
*/
textDirection : {type : "sap.ui.core.TextDirection", group : "Appearance", defaultValue : TextDirection.Inherit},
/**
* Width of the RadioButton or it's label depending on the useEntireWidth property.
* By Default width is set only for the label.
* @see {sap.m.RadioButton#useEntireWidth}
*/
width : {type : "sap.ui.core.CSSSize", group : "Dimension", defaultValue : ''},
/**
* Indicates if the given width will be applied for the whole RadioButton or only it's label.
* By Default width is set only for the label.
* @since 1.42
*/
useEntireWidth : {type : "boolean", group: "Appearance", defaultValue : false },
/**
* This is a flag to switch on activeHandling. When it is switched off,
* there will not be visual changes on active state. Default value is 'true'
*/
activeHandling : {type : "boolean", group : "Appearance", defaultValue : true},
/**
* Specifies whether the user can select the radio button.
* @since 1.25
*/
editable : {type : "boolean", group : "Behavior", defaultValue : true},
/**
*
* Enumeration sap.ui.core.ValueState provides state values Error, Success, Warning, None
* @since 1.25
*/
valueState : {type : "sap.ui.core.ValueState", group : "Data", defaultValue : ValueState.None},
/**
* Specifies the alignment of the radio button. Available alignment settings are "Begin", "Center", "End", "Left", and "Right".
* @since 1.28
*/
textAlign : {type : "sap.ui.core.TextAlign", group : "Appearance", defaultValue : TextAlign.Begin},
/**
* Defines the text that appears in the tooltip of the RadioButton
. If this is not specified, a default text is shown from the resource bundle.
* @private
*/
valueStateText: { type: "string", group: "Misc", defaultValue: null, visibility: "hidden" }
},
events : {
/**
* Event is triggered when the user makes a change on the radio button (selecting or unselecting it).
*/
select : {
parameters : {
/**
* Checks whether the RadioButton is active or not.
*/
selected : {type : "boolean"}
}
}
},
associations : {
/**
* Association to controls / IDs which describe this control (see WAI-ARIA attribute aria-describedby).
*/
ariaDescribedBy : {type : "sap.ui.core.Control", multiple : true, singularName : "ariaDescribedBy"},
/**
* Association to controls / IDs which label this control (see WAI-ARIA attribute aria-labelledby).
*/
ariaLabelledBy : {type : "sap.ui.core.Control", multiple : true, singularName : "ariaLabelledBy"}
},
designtime: "sap/m/designtime/RadioButton.designtime"
}});
/**
* Method to set a RadioButton's state to active or inactive.
*
* @name sap.m.RadioButton#setActiveState
* @function
* @param {boolean} bActive - Sets the active state to true or false
* @type void
* @public
* @ui5-metamodel This method also will be described in the UI5 (legacy) designtime metamodel
*/
/**
* This file defines behavior for the control,
*/
EnabledPropagator.call(RadioButton.prototype);
// Apply the message mixin so all Messages on the RadioButton will have additionalText property set to ariaLabelledBy's text of the RadioButton
// and have valueState property of the RadioButton set to the message type.
MessageMixin.call(RadioButton.prototype);
RadioButton.prototype._groupNames = {};
// Keyboard navigation variants
var KH_NAVIGATION = {
HOME: "first",
END: "last",
NEXT: "next",
PREV: "prev"
};
/**
* Function is called when the radio button is tapped.
* @param {jQuery.Event} oEvent The event object
* @private
*/
RadioButton.prototype.ontap = function(oEvent) {
if (!this.getEnabled() || !this.getEditable()) {
return;
}
var oParent = this.getParent();
// check if the RadioButton is part of a RadioButtonGroup which is disabled/readonly
if (oParent instanceof RadioButtonGroup && (!oParent.getEnabled() || !oParent.getEditable())) {
return;
}
// mark the event that it is handled by the control
oEvent && oEvent.setMarked();
this.applyFocusInfo();
if (!this.getSelected()) {
this.setSelected(true);
var that = this;
setTimeout(function() {
that.fireSelect({selected: true});
}, 0);
}
};
/**
* Function is called when radiobutton is being touched. Only necessary for Android/Blackberry.
* @param {jQuery.Event} oEvent The event object
* @private
*/
RadioButton.prototype.ontouchstart = function(oEvent) {
//for control who need to know if they should handle events from the CheckBox control
oEvent.originalEvent._sapui_handledByControl = true;
if (this.getEnabled() && this.getActiveHandling()) {
this.$().toggleClass("sapMRbBTouched", true);
}
};
RadioButton.prototype.ontouchend = function(oEvent) {
this.$().toggleClass("sapMRbBTouched", false);
};
RadioButton.prototype.onsapnext = function(oEvent) {
this._keyboardHandler(KH_NAVIGATION.NEXT, true);
// mark the event that it is handled by the control
oEvent.setMarked();
return this;
};
RadioButton.prototype.onsapnextmodifiers = function(oEvent) {
this._keyboardHandler(KH_NAVIGATION.NEXT, !oEvent.ctrlKey);
// mark the event that it is handled by the control
oEvent.setMarked();
return this;
};
RadioButton.prototype.onsapprevious = function(oEvent) {
this._keyboardHandler(KH_NAVIGATION.PREV, true);
// mark the event that it is handled by the control
oEvent.setMarked();
return this;
};
RadioButton.prototype.onsappreviousmodifiers = function(oEvent) {
this._keyboardHandler(KH_NAVIGATION.PREV, !oEvent.ctrlKey);
// mark the event that it is handled by the control
oEvent.setMarked();
return this;
};
RadioButton.prototype.onsaphome = function(oEvent) {
this._keyboardHandler(KH_NAVIGATION.HOME, true);
// mark the event that it is handled by the control
oEvent.setMarked();
return this;
};
RadioButton.prototype.onsaphomemodifiers = function(oEvent) {
this._keyboardHandler(KH_NAVIGATION.HOME, !oEvent.ctrlKey);
// mark the event that it is handled by the control
oEvent.setMarked();
return this;
};
RadioButton.prototype.onsapend = function(oEvent) {
this._keyboardHandler(KH_NAVIGATION.END, true);
// mark the event that it is handled by the control
oEvent.setMarked();
return this;
};
RadioButton.prototype.onsapendmodifiers = function(oEvent) {
this._keyboardHandler(KH_NAVIGATION.END, !oEvent.ctrlKey);
// mark the event that it is handled by the control
oEvent.setMarked();
return this;
};
/**
* Determines which button becomes focused after an arrow key is pressed.
* @param {string} sPosition Button to be focused
* @param {boolean} bSelect Determines if the button should be selected
* @private
*/
RadioButton.prototype._keyboardHandler = function(sPosition, bSelect) {
if (this.getParent() instanceof RadioButtonGroup) {
return;
}
var oNextItem = this._getNextFocusItem(sPosition);
oNextItem.focus();
if (bSelect && !oNextItem.getSelected() && oNextItem.getEditable() && oNextItem.getEnabled()) {
oNextItem.setSelected(true);
setTimeout(function() {
oNextItem.fireSelect({selected: true});
}, 0);
}
};
/**
* @see sap.ui.core.Control#getAccessibilityInfo
* @protected
* @returns {Object} The sap.m.RadioButton
accessibility information
*/
RadioButton.prototype.getAccessibilityInfo = function() {
var oBundle = sap.ui.getCore().getLibraryResourceBundle("sap.m");
return {
role: "radio",
type: oBundle.getText("ACC_CTR_TYPE_RADIO"),
description: (this.getText() || "") + (this.getSelected() ? (" " + oBundle.getText("ACC_CTR_STATE_CHECKED")) : ""),
enabled: this.getEnabled(),
editable: this.getEditable()
};
};
/*
* RadioButton without label must not be stretched in Form.
*/
RadioButton.prototype.getFormDoNotAdjustWidth = function() {
return this.getText() ? false : true;
};
/**
* Determines next focusable item
*
* @param {enum} sNavigation any item from KH_NAVIGATION
* @returns {RadioButton} Control instance for method chaining
* @private
*/
RadioButton.prototype._getNextFocusItem = function(sNavigation) {
var aVisibleBtnsGroup = this._groupNames[this.getGroupName()].filter(function (oRB) {
return (oRB.getDomRef() && oRB.getEnabled());
});
var iButtonIndex = aVisibleBtnsGroup.indexOf(this),
iIndex = iButtonIndex,
iVisibleBtnsLength = aVisibleBtnsGroup.length;
switch (sNavigation) {
case KH_NAVIGATION.NEXT:
iIndex = iButtonIndex === iVisibleBtnsLength - 1 ? iButtonIndex : iButtonIndex + 1;
break;
case KH_NAVIGATION.PREV:
iIndex = iButtonIndex === 0 ? 0 : iIndex - 1;
break;
case KH_NAVIGATION.HOME:
iIndex = 0;
break;
case KH_NAVIGATION.END:
iIndex = iVisibleBtnsLength - 1;
break;
}
return aVisibleBtnsGroup[iIndex] || this;
};
// #############################################################################
// Keyboard Handling Events
// #############################################################################
/**
* Pseudo event for pseudo 'select' event... space, enter, ... without modifiers (Ctrl, Alt or Shift)
* @param {object} oEvent - provides information for the event
* @public
*/
RadioButton.prototype.onsapselect = function(oEvent) {
oEvent.preventDefault();
this.ontap(oEvent);
};
RadioButton.prototype.setEnabled = function(bEnabled) {
this.setProperty("enabled", bEnabled, false);
return this;
};
// #############################################################################
// Overwritten methods that are also generated in RadioButton.API.js
// #############################################################################
/**
* Sets the state of the RadioButton to selected.
* @param {boolean} bSelected - defines if the radio button is selected
* @returns {sap.m.RadioButton} Reference to the control instance for chaining
* @public
*/
RadioButton.prototype.setSelected = function(bSelected) {
var oControl,
bSelectedOld = this.getSelected(),
sGroupName = this.getGroupName(),
aControlsInGroup = this._groupNames[sGroupName],
iLength = aControlsInGroup && aControlsInGroup.length;
this.setProperty("selected", bSelected, true); // No re-rendering
this._changeGroupName(this.getGroupName());
if (!!bSelected && sGroupName && sGroupName !== "") { // If this radio button is selected and groupName is set, explicitly deselect the other radio buttons of the same group
for (var i = 0; i < iLength; i++) {
oControl = aControlsInGroup[i];
if (oControl instanceof RadioButton && oControl !== this && oControl.getSelected()) {
oControl.fireSelect({ selected: false });
oControl.setSelected(false);
}
}
}
if ((bSelectedOld !== !!bSelected) && this.getDomRef()) {
this.$().toggleClass("sapMRbSel", bSelected);
if (bSelected) {
this.getDomRef().setAttribute("aria-checked", "true");
this.getDomRef("RB").checked = true;
this.getDomRef("RB").setAttribute("checked", "checked");
} else {
this.getDomRef().removeAttribute("aria-checked"); // aria-checked=false is default value and need not be set explicitly
this.getDomRef("RB").checked = false;
this.getDomRef("RB").removeAttribute("checked");
}
}
return this;
};
/**
* Sets the text for the RadioButton's label.
* @param {string} sText - The text to be set
* @returns {sap.m.RadioButton} Reference to the control instance for chaining
* @public
*/
RadioButton.prototype.setText = function(sText) {
this.setProperty("text", sText, true);
if (this._oLabel) {
this._oLabel.setText(this.getText());
} else {
this._createLabel("text", this.getText());
}
this.toggleStyleClass("sapMRbHasLabel", !!sText);
return this;
};
/**
* Depeding on useEntireWidth sets the width to the RadioButton's label or the whole RadioButton
* @param {boolean} bUserEntireWidth - Determines if the width will be set to the label only or to the whole RadioButton
* @private
*/
RadioButton.prototype._setWidth = function(bUserEntireWidth) {
if (!bUserEntireWidth) {
this._setLableWidth();
} else {
this._setLableWidth("auto");
}
};
/**
* Sets the width for the RadioButton's label.
* @param {string} sWidth - CSS size to be set as width
* @private
*/
RadioButton.prototype._setLableWidth = function(sWidth) {
sWidth = sWidth || this.getWidth();
if (this._oLabel) {
this._oLabel.setWidth(sWidth);
} else {
this._createLabel("width", sWidth);
}
};
/**
* Sets the text direction for the RadioButton's label.
* @param {string} sDirection - Text direction to be set to RadioButton's label
* @returns {sap.m.RadioButton} Reference to the control instance for chaining
* @public
*/
RadioButton.prototype.setTextDirection = function(sDirection) {
this.setProperty("textDirection", sDirection, true);
if (this._oLabel) {
this._oLabel.setTextDirection(this.getTextDirection());
} else {
this._createLabel("textDirection", this.getTextDirection());
}
return this;
};
/**
* Sets RadioButton's groupName. Only one radioButton from the same group can be selected
* @param {string} sGroupName - Name of the group to which the RadioButton will belong.
* @returns {sap.m.RadioButton} Reference to the control instance for chaining
* @public
*/
RadioButton.prototype.setGroupName = function(sGroupName) {
this._changeGroupName(sGroupName, this.getGroupName());
return this.setProperty("groupName", sGroupName, true);
};
RadioButton.prototype.onBeforeRendering = function() {
// Set the width before rendering as both width and useEntireWidth are dependent
this._setWidth(this.getUseEntireWidth());
return this._changeGroupName(this.getGroupName());
};
RadioButton.prototype.onAfterRendering = function() {
var sGroupName = this.getGroupName();
this._setAriaPositionAttributes(sGroupName);
};
/**
* Destroys all related objects to the RadioButton
* @public
*/
RadioButton.prototype.exit = function() {
var sGroupName = this.getGroupName(),
aControlsInGroup = this._groupNames[sGroupName],
iGroupNameIndex = aControlsInGroup && aControlsInGroup.indexOf(this);
this._iTabIndex = null;
if (this._oLabel) {
this._oLabel.destroy();
}
if (iGroupNameIndex >= -1) {
aControlsInGroup.splice(iGroupNameIndex, 1);
}
};
/**
* Creates label and sets a property to it.
* @param {string} prop - Property to be set to the new label.
* @param {string} value - Value of the property which will be set.
* @private
*/
RadioButton.prototype._createLabel = function(prop, value) {
this._oLabel = new sap.m.Label(this.getId() + "-label").addStyleClass("sapMRbBLabel").setParent(this, null, true);
this._oLabel.setProperty(prop, value, false);
};
/*
* Sets the tab index of the control
*
* @param {int} iTabIndex - Greater than or equal to -1
* @return {sap.m.RadioButton}
* @since 1.16
* @protected
*/
RadioButton.prototype.setTabIndex = function(iTabIndex) {
var oFocusDomRef = this.getFocusDomRef();
this._iTabIndex = iTabIndex;
if (oFocusDomRef) {
oFocusDomRef.setAttribute("tabindex", iTabIndex);
}
return this;
};
/*
* Sets the textAlign to the internal label
* @param {string} sAlign
* @return {sap.m.RadioButton}
* @since 1.28
* @public
*/
RadioButton.prototype.setTextAlign = function(sAlign) {
this.setProperty("textAlign", sAlign, true);
if (this._oLabel) {
this._oLabel.setTextAlign(this.getTextAlign());
} else {
this._createLabel("textAlign", this.getTextAlign());
}
return this;
};
/**
* Sets the private valueStateText property. Required, in order to make the MessageMixin work.
*
* @private
* @param {string} sText The new value of the property.
* @returns {sap.m.RadioButton} Reference to the control instance for chaining.
*/
RadioButton.prototype.setValueStateText = function(sText) {
return this.setProperty("valueStateText", sText, true);
};
/**
* Changes the groupname of a RadioButton.
* @param {string} sNewGroupName - Name of the new group.
* @param {string} sOldGroupName - Name of the old group.
* @private
*/
RadioButton.prototype._changeGroupName = function(sNewGroupName, sOldGroupName) {
var aNewGroup = this._groupNames[sNewGroupName],
aOldGroup = this._groupNames[sOldGroupName];
if (aOldGroup && aOldGroup.indexOf(this) !== -1) {
aOldGroup.splice(aOldGroup.indexOf(this), 1);
this._setAriaPositionAttributes(sOldGroupName);
}
if (!aNewGroup) {
aNewGroup = this._groupNames[sNewGroupName] = [];
}
if (aNewGroup.indexOf(this) === -1) {
aNewGroup.push(this);
this._setAriaPositionAttributes(sNewGroupName);
}
};
/**
* Recalculates and sets the correct aria-posinset and aria-setsize attribute values
* This is done based on the rendered in the DOM radio buttons which are in the provided group.
*
* @param {string} [sGroupName] The name of the group for which the ARIA attributes should be recalculated
* @private
*/
RadioButton.prototype._setAriaPositionAttributes = function (sGroupName) {
var aGroup = this._groupNames[sGroupName],
iRenderedIndex = 0,
iRenderedInGroupCount;
if (!aGroup.length || !this.getDomRef()) {
return;
}
// Find how many buttons are rendered in the group
iRenderedInGroupCount = aGroup.reduce(function (iRenderedInGroupCount, oRadioButton) {
return oRadioButton.getDomRef() ? ++iRenderedInGroupCount : iRenderedInGroupCount;
}, 0);
// For every radio button in the group - recalculate its index and set its properties
aGroup.forEach(function(oRadioButton) {
var oRadioDom = oRadioButton.getDomRef();
if (oRadioDom) {
oRadioDom.setAttribute("aria-posinset", ++iRenderedIndex);
oRadioDom.setAttribute("aria-setsize", iRenderedInGroupCount);
}
});
};
return RadioButton;
});