UNPKG

@openui5/sap.m

Version:

OpenUI5 UI Library sap.m

709 lines (610 loc) 22.4 kB
/*! * OpenUI5 * (c) Copyright 2009-2023 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ // Provides control sap.m.RadioButtonGroup. sap.ui.define([ './library', 'sap/ui/core/Control', 'sap/ui/core/delegate/ItemNavigation', 'sap/ui/core/library', 'sap/ui/base/ManagedObjectObserver', './RadioButton', './RadioButtonGroupRenderer', "sap/base/Log" ], function( library, Control, ItemNavigation, coreLibrary, ManagedObjectObserver, RadioButton, RadioButtonGroupRenderer, Log ) { "use strict"; // shortcut for sap.ui.core.TextDirection var TextDirection = coreLibrary.TextDirection; // shortcut for sap.ui.core.ValueState var ValueState = coreLibrary.ValueState; /** * Constructor for a new RadioButtonGroup. * * @param {string} [sId] ID for the new control, generated automatically if no ID is given * @param {object} [mSettings] Initial settings for the new control * A wrapper control for a group of radio buttons. * @class * This control is used as a wrapper for a group of {@link sap.m.RadioButton} controls, which can be used as a single UI element. * You can select only one of the grouped radio buttons at a time. * <h3>Structure</h3> * <ul> * <li>The radio buttons are stored in the <code>buttons</code> aggregation.</li> * <li>By setting the <code>columns</code> property, you can create layouts like a 'matrix', 'vertical' or 'horizontal'.</li> * <li><b>Note:</b>For proper display on all devices, we recommend creating radio button groups with only one row or only one column.</li> * </ul> * <h3>Usage</h3> * <h4>When to use:</h4> * <ul> * <li>You want to attach a single event handler on a group of buttons, rather than on each individual button.</li> * </ul> * <h4>When not to use:</h4> * <ul> * <li>Do not put two radio button groups right next to each other as it is difficult to determine which buttons belong to which group.</li> * </ul> * @extends sap.ui.core.Control * @implements sap.ui.core.IFormContent * * @author SAP SE * @version 1.117.4 * * @constructor * @public * @since 1.25.0 * @alias sap.m.RadioButtonGroup */ var RadioButtonGroup = Control.extend("sap.m.RadioButtonGroup", /** @lends sap.m.RadioButtonGroup.prototype */ { metadata : { interfaces : ["sap.ui.core.IFormContent"], library : "sap.m", designtime: "sap/m/designtime/RadioButtonGroup.designtime", properties : { /** * Specifies the width of the RadioButtonGroup. */ width : {type : "sap.ui.core.CSSSize", group : "Dimension", defaultValue : null}, /** * Specifies the maximum number of radio buttons displayed in one line. */ columns : {type : "int", group : "Appearance", defaultValue : 1}, /** * Specifies whether the user can change the selected value of the RadioButtonGroup. * When the property is set to false, the control obtains visual styles * different from its visual styles for the normal and the disabled state. * Additionally, the control is no longer interactive, but can receive focus. */ editable : {type : "boolean", group : "Behavior", defaultValue : true}, /** * Marker for the correctness of the current value e.g., Error, Success, etc. * Changing this property will also change the state of all radio buttons inside the group. * Note: Setting this attribute to sap.ui.core.ValueState.Error when the accessibility feature is enabled, * sets the value of the invalid property for the whole RadioButtonGroup to "true". */ valueState : {type : "sap.ui.core.ValueState", group : "Data", defaultValue : ValueState.None}, /** * Determines the index of the selected/checked RadioButton. Default is 0. * If no radio button is selected, the selectedIndex property will return -1. */ selectedIndex : {type : "int", group : "Data", defaultValue : 0}, /** * Switches the enabled state of the control. All radio buttons inside a disabled group are disabled. */ enabled : {type : "boolean", group : "Behavior", defaultValue : true}, /** * This property specifies the element's text directionality with enumerated options. By default, the control inherits text direction from the DOM. * @since 1.28.0 */ textDirection : {type : "sap.ui.core.TextDirection", group : "Appearance", defaultValue : TextDirection.Inherit} }, defaultAggregation : "buttons", aggregations : { /** * Returns a list of the RadioButtons in a RadioButtonGroup */ buttons : {type : "sap.m.RadioButton", multiple : true, singularName : "button", bindable : "bindable"} }, 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"} }, events : { /** * Fires when selection is changed by user interaction. */ select : { parameters : { /** * Index of the selected RadioButton. */ selectedIndex : {type : "int"} } } } }, renderer: RadioButtonGroupRenderer }); RadioButtonGroup.prototype.init = function() { this._iSelectionNumber = -1; this._oObserver = new ManagedObjectObserver(this._observeChanges.bind(this)); this._oObserver.observe(this, { aggregations: ["buttons"] }); }; /** * Exits the radio button group. * * @public */ RadioButtonGroup.prototype.exit = function() { this.destroyButtons(); if (this._oItemNavigation) { this.removeDelegate(this._oItemNavigation); this._oItemNavigation.destroy(); delete this._oItemNavigation; } if (this._oObserver) { this._oObserver.disconnect(); this._oObserver = null; } }; /** * Overwrites the onBeforeRendering method. * * @public */ RadioButtonGroup.prototype.onBeforeRendering = function() { var aButtons = this.getButtons(); var bEditable = this.getEditable(); var iCurrentSelectedButtonSelectionNumber = -1; aButtons.forEach(function (oRadioButton) { if (oRadioButton.getSelected()) { iCurrentSelectedButtonSelectionNumber = Math.max(iCurrentSelectedButtonSelectionNumber, oRadioButton._iSelectionNumber); } }); if (iCurrentSelectedButtonSelectionNumber === -1 && this._iSelectionNumber === -1) { this._iSelectionNumber = RadioButton.getNextSelectionNumber(); } aButtons.forEach(function (oRadioButton, i) { oRadioButton._setEditableParent(bEditable); if (i === this.getSelectedIndex() && this._iSelectionNumber > iCurrentSelectedButtonSelectionNumber && oRadioButton.isPropertyInitial("selected")) { oRadioButton.setSelected(true); } }, this); if (this.aRBs){ var oValueState = this.getValueState(); this.aRBs.forEach(function (oRB) { oRB.setValueState(oValueState); }); } }; /** * Overwrites the onAfterRendering * * @public */ RadioButtonGroup.prototype.onAfterRendering = function() { this._initItemNavigation(); }; /** * Initializes ItemNavigation, which is necessary for the keyboard handling of the group. * * @private */ RadioButtonGroup.prototype._initItemNavigation = function() { // Collect buttons for ItemNavigation var aRBs = this._getVisibleButtons(); var aDomRefs = []; var bHasEnabledRadios = false; var bRadioGroupEnabled = this.getEnabled(); for (var i = 0; i < aRBs.length; i++) { aDomRefs.push(aRBs[i].getDomRef()); // if the i-th radio button is enabled - set the flag to true bHasEnabledRadios = bHasEnabledRadios || aRBs[i].getEnabled(); } // if no radio buttons are enabled or the whole group is disabled if (!bHasEnabledRadios || !bRadioGroupEnabled) { // dismiss item navigation if (this._oItemNavigation) { this.removeDelegate(this._oItemNavigation); this._oItemNavigation.destroy(); delete this._oItemNavigation; } return; } // init ItemNavigation if (!this._oItemNavigation) { this._oItemNavigation = new ItemNavigation(); this._oItemNavigation.attachEvent(ItemNavigation.Events.AfterFocus, this._handleAfterFocus, this); this.addDelegate(this._oItemNavigation); } this._oItemNavigation.setRootDomRef(this.getDomRef()); this._oItemNavigation.setItemDomRefs(aDomRefs); this._oItemNavigation.setCycling(true); this._oItemNavigation.setColumns(this.getColumns()); this._oItemNavigation.setSelectedIndex(this._getSelectedIndexInRange()); this._oItemNavigation.setFocusedIndex(this._getSelectedIndexInRange()); this._oItemNavigation.setDisabledModifiers({ sapnext : ["alt", "meta"], sapprevious : ["alt", "meta"] }); }; RadioButtonGroup.prototype._getVisibleButtons = function() { return this.getButtons().filter(function (oRB) { return oRB.getVisible(); }); }; RadioButtonGroup.prototype._observeChanges = function (oChanges) { var oObject = oChanges.object, sChangeName = oChanges.name, sMutationName = oChanges.mutation, oChild = oChanges.child; if (oObject === this) { if (sMutationName === "insert") { this._observeVisibility(oChild); } else if (sMutationName === "remove") { this._unobserveVisibility(oChild); } } else if (sChangeName === "visible") { this._initItemNavigation(); } }; RadioButtonGroup.prototype._observeVisibility = function (oControl) { this._oObserver.observe(oControl, { properties: ["visible"] }); }; RadioButtonGroup.prototype._unobserveVisibility = function (oControl) { this._oObserver.unobserve(oControl, { properties: ["visible"] }); }; /** * Sets the selected sap.m.RadioButton using index. * * @public * @param {number} iSelectedIndex The index of the radio button which has to be selected. * @returns {this} Pointer to the control instance for chaining. */ RadioButtonGroup.prototype.setSelectedIndex = function(iSelectedIndex) { var iIndexOld = this.getSelectedIndex(); // if a radio button in the group is focused is true, otherwise - false var hasFocusedRadioButton = this.getDomRef() && this.getDomRef().contains(document.activeElement); // if radio button group has buttons and one of them is selected is true, otherwise - false var isRadioGroupSelected = !!(this.aRBs && this.aRBs[iSelectedIndex]); var iFocusedIndex; if (iSelectedIndex < -1) { // invalid negative index -> don't change index. Log.warning("Invalid index, will not be changed"); return this; } this.setProperty("selectedIndex", iSelectedIndex, true); // no re-rendering this._iSelectionNumber = RadioButton.getNextSelectionNumber(); iFocusedIndex = this._getSelectedIndexInRange(); // deselect old RadioButton if (!isNaN(iIndexOld) && this.aRBs && this.aRBs[iIndexOld]) { this.aRBs[iIndexOld].setSelected(false); this.aRBs[iIndexOld].setTabIndex(-1); } // select new one if (this.aRBs && this.aRBs[iSelectedIndex]) { this.aRBs[iSelectedIndex].setSelected(true); this.aRBs[iSelectedIndex].setTabIndex(0); } if (this._oItemNavigation) { this._oItemNavigation.setFocusedIndex(iFocusedIndex); this._oItemNavigation.setSelectedIndex(iFocusedIndex); } // if focus is in the group - focus the selected element if (isRadioGroupSelected && hasFocusedRadioButton) { this.aRBs[iSelectedIndex].getDomRef().focus(); } return this; }; /** * Sets the selected sap.m.RadioButton using sap.m.RadioButton. * * @public * @param {sap.m.RadioButton} oSelectedButton The item to be selected. * @returns {this} Pointer to the control instance for chaining. */ RadioButtonGroup.prototype.setSelectedButton = function (oSelectedButton) { if (!oSelectedButton) { return this.setSelectedIndex(-1); } var aButtons = this.getButtons(); for (var i = 0; i < aButtons.length; i++) { if (oSelectedButton.getId() == aButtons[i].getId()) { return this.setSelectedIndex(i); } } return this; }; /** * Returns the selected radio button. * * @public * @returns {sap.m.RadioButton} The selected radio button. */ RadioButtonGroup.prototype.getSelectedButton = function() { return this.getButtons()[this.getSelectedIndex()]; }; /** * Adds a new radio button to the group. * * @public * @param {sap.m.RadioButton} oButton The button which will be added to the group. * @returns {this} Pointer to the control instance for chaining. */ RadioButtonGroup.prototype.addButton = function(oButton) { if (!this.aRBs) { this.aRBs = []; } var iIndex = this.aRBs.length; this.aRBs[iIndex] = this._createRadioButton(oButton); this.addAggregation("buttons", this.aRBs[iIndex]); return this; }; /** * Adds a new radio button to the group at a specified index. * * @public * @param {sap.m.RadioButton} oButton The radio button which will be added to the group. * @param {number} iIndex The index, at which the radio button will be added. * @returns {this} Pointer to the control instance for chaining. */ RadioButtonGroup.prototype.insertButton = function(oButton, iIndex) { if (!this.aRBs) { this.aRBs = []; } var iLength = this.aRBs.length, iMaxLength = this.getButtons().length; iIndex = Math.max(Math.min(iIndex, iMaxLength), 0); if (!this._bUpdateButtons) { if (this.getSelectedIndex() === undefined || iLength == 0) { // if not defined -> select first one this.setSelectedIndex(0); } else if (this.getSelectedIndex() >= iIndex) { // If inserted before selected one, move selection index (only change parameter, not RadioButton) this.setProperty("selectedIndex", this.getSelectedIndex() + 1, true); // no re-rendering } } if (iIndex >= iLength) { this.aRBs[iIndex] = this._createRadioButton(oButton); } else { // Insert RadioButton: loop backwards over Array and shift everything for (var i = (iLength); i > iIndex; i--) { this.aRBs[i] = this.aRBs[i - 1]; if ((i - 1) == iIndex) { this.aRBs[i - 1] = this._createRadioButton(oButton); } } } this.insertAggregation("buttons", oButton, iIndex); return this; }; /** * Creates a copy of the sap.m.RadioButton passed as a first argument * * @private * @param {sap.m.RadioButton} oButton The button from which a radio button will be created. * @returns {sap.m.RadioButton} The created radio button. */ RadioButtonGroup.prototype._createRadioButton = function(oButton) { oButton.setValueState(this.getValueState()); oButton.setGroupName(this.getId()); oButton.attachEvent("select", this._handleRBSelect, this); return oButton; }; /** * Removes a radio button from the group. * * @public * @returns {sap.m.RadioButton} vElement The removed radio button. */ RadioButtonGroup.prototype.removeButton = function(vElement) { var iIndex = vElement; if (typeof (vElement) == "string") { // ID of the element is given vElement = sap.ui.getCore().byId(vElement); } if (typeof (vElement) == "object") { // the element itself is given or has just been retrieved iIndex = this.indexOfButton(vElement); } var oButton = this.removeAggregation("buttons", iIndex); if (!this.aRBs) { this.aRBs = []; } if (!this.aRBs[iIndex]) { // RadioButton not exists return null; } this.aRBs.splice(iIndex, 1); if (!this._bUpdateButtons) { if (this.aRBs.length == 0) { this.setSelectedIndex(-1); } else if (this.getSelectedIndex() == iIndex) { // selected one is removed -> select first one this.setSelectedIndex(0); } else { if (this.getSelectedIndex() > iIndex) { // If removed before selected one, move selection index (only change parameter, not RadioButton) this.setProperty("selectedIndex", this.getSelectedIndex() - 1, true); // no re-rendering } } } return oButton; }; /** * Removes all radio buttons. * * @public * @returns {sap.m.RadioButton[]} Array of removed buttons. */ RadioButtonGroup.prototype.removeAllButtons = function () { if (!this._bUpdateButtons) { this.setSelectedIndex(-1); } this.aRBs = []; return this.removeAllAggregation("buttons"); }; /** * Destroys all radio buttons. * * @public * @returns {this} Pointer to the control instance for chaining. */ RadioButtonGroup.prototype.destroyButtons = function() { this.destroyAggregation("buttons"); if (this.aRBs) { while (this.aRBs.length > 0) { this.aRBs[0].destroy(); this.aRBs.splice(0, 1); } } return this; }; /** * Updates the buttons in the group. * * @public */ RadioButtonGroup.prototype.updateButtons = function() { this._bUpdateButtons = true; this.updateAggregation("buttons"); this._bUpdateButtons = undefined; }; /** * Creates a new instance of RadioButtonGroup, with the same settings as the RadioButtonGroup * on which the method is called. * Event handlers are not cloned. * * @public * @returns {this} New instance of RadioButtonGroup */ RadioButtonGroup.prototype.clone = function(){ // on clone don't clone event handler var aButtons = this.getButtons(); var i = 0; for (i = 0; i < aButtons.length; i++) { aButtons[i].detachEvent("select", this._handleRBSelect, this); } var oClone = Control.prototype.clone.apply(this, arguments); for (i = 0; i < aButtons.length; i++) { aButtons[i].attachEvent("select", this._handleRBSelect, this); } return oClone; }; /** * Select event of single Radio Buttons fires Select Event for group. * * @private * @param {sap.ui.base.Event} oControlEvent Control event. */ RadioButtonGroup.prototype._handleRBSelect = function(oControlEvent) { // find RadioButton in Array to get Index for (var i = 0; i < this.aRBs.length; i++) { if (this.aRBs[i].getId() == oControlEvent.getParameter("id") && oControlEvent.getParameter("selected")) { this.setSelectedIndex(i); this.fireSelect({ selectedIndex : i }); break; } } }; /** * Sets the editable property of the RadioButtonGroup. Single buttons preserve the value of their editable property. * If the group is set to editable=false the buttons are also displayed and function as read only. * Non editable radio buttons can still obtain focus. * * @name sap.m.RadioButtonGroup.prototype.setEditable * @public * @function * @param {boolean} bEditable Defines whether the radio buttons should be interactive. * @returns {this} Pointer to the control instance for chaining. */ /** * Sets the enabled property of the RadioButtonGroup. Single buttons preserve internally the value of their enabled property. * If the group is set to enabled=false the buttons are also displayed as disabled and getEnabled returns false. * * @name sap.m.RadioButtonGroup.prototype.setEnabled * @public * @function * @param {boolean} bEnabled Defines whether the radio buttons should be interactive. * @returns {this} Pointer to the control instance for chaining. */ /** * Handles the event that gets fired by the {@link sap.ui.core.delegate.ItemNavigation} delegate. * Ensures that focused element is selected. * * @private * @param {sap.ui.base.Event} oControlEvent The event that gets fired by the {@link sap.ui.core.delegate.ItemNavigation} delegate. */ RadioButtonGroup.prototype._handleAfterFocus = function(oControlEvent) { var aRBs = this.getButtons(); var iIndex = oControlEvent.getParameter("index"); var oEvent = oControlEvent.getParameter("event"); // buttons that are not visible are not included in the HTML // adjust the index to include them for (var i = 0; i <= iIndex; i++) { if (!aRBs[i].getVisible()) { iIndex++; } } // handle only keyboard navigation here if (oEvent.keyCode === undefined) { return; } if (iIndex != this.getSelectedIndex() && !(oEvent.ctrlKey || oEvent.metaKey) && this.aRBs[iIndex].getEditable() && this.aRBs[iIndex].getEnabled() && this.getEditable() && this.getEnabled()) { // if CTRL key is used do not switch selection this.setSelectedIndex(iIndex); this.fireSelect({ selectedIndex : iIndex }); // update tabindex values for all buttons that are part of the item navigation this.aRBs .filter(function (oRB) { return oRB.getEditable() && oRB.getEnabled(); }) .forEach(function (oRB) { if (oRB === this.aRBs[iIndex]) { oRB.setTabIndex(0); return; } oRB.setTabIndex(-1); }.bind(this)); } }; RadioButtonGroup.prototype._getSelectedIndexInRange = function() { var aRBs = this.getButtons(), iLength = aRBs.length, iSelectedIndex = this.getSelectedIndex(), iFocusedIndex = -1; if (iSelectedIndex <= -1 || iSelectedIndex > iLength - 1) { return -1; } // include only the visible buttons to the focused index for (var i = 0; i <= iSelectedIndex; i++) { if (aRBs[i].getVisible()) { iFocusedIndex++; } } return iFocusedIndex; }; return RadioButtonGroup; });