@openui5/sap.m
Version:
OpenUI5 UI Library sap.m
1,794 lines (1,514 loc) • 94.6 kB
JavaScript
/*!
* OpenUI5
* (c) Copyright 2009-2023 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
sap.ui.define([
'sap/ui/core/Element',
'./Dialog',
'./Popover',
'./SelectList',
'./library',
'sap/ui/core/Control',
'sap/ui/core/EnabledPropagator',
'sap/ui/core/LabelEnablement',
'sap/ui/core/Icon',
'sap/ui/core/IconPool',
'./Button',
'./Bar',
'./Title',
'./delegate/ValueStateMessage',
'sap/ui/core/message/MessageMixin',
'sap/ui/core/library',
'sap/ui/core/Item',
'sap/ui/Device',
'sap/ui/core/InvisibleText',
'./SelectRenderer',
"sap/ui/dom/containsOrEquals",
"sap/ui/events/KeyCodes",
'./Text',
'sap/m/SimpleFixFlex',
'sap/base/Log',
'sap/ui/core/ValueStateSupport',
"sap/ui/core/InvisibleMessage",
"sap/ui/core/Lib"
],
function(
Element,
Dialog,
Popover,
SelectList,
library,
Control,
EnabledPropagator,
LabelEnablement,
Icon,
IconPool,
Button,
Bar,
Title,
ValueStateMessage,
MessageMixin,
coreLibrary,
Item,
Device,
InvisibleText,
SelectRenderer,
containsOrEquals,
KeyCodes,
Text,
SimpleFixFlex,
Log,
ValueStateSupport,
InvisibleMessage,
Library
) {
"use strict";
// shortcut for sap.m.SelectListKeyboardNavigationMode
var SelectListKeyboardNavigationMode = library.SelectListKeyboardNavigationMode;
// shortcut for sap.m.PlacementType
var PlacementType = library.PlacementType;
// shortcut for sap.ui.core.ValueState
var ValueState = coreLibrary.ValueState;
// shortcut for sap.ui.core.TextDirection
var TextDirection = coreLibrary.TextDirection;
// shortcut for sap.ui.core.TextAlign
var TextAlign = coreLibrary.TextAlign;
// shortcut for sap.ui.core.OpenState
var OpenState = coreLibrary.OpenState;
// shortcut for sap.m.SelectType
var SelectType = library.SelectType;
// shortcut for sap.ui.core.InvisibleMessageMode
var InvisibleMessageMode = coreLibrary.InvisibleMessageMode;
// shortcut for sap.ui.core.TitleLevel
var TitleLevel = coreLibrary.TitleLevel;
/**
* Constructor for a new <code>sap.m.Select</code>.
*
* @param {string} [sId] ID for the new control, generated automatically if no ID is given.
* @param {object} [mSettings] Initial settings for the new control.
*
* @class
* The <code>sap.m.Select</code> control provides a list of items that allows users to select an item.
*
* @see {@link fiori:https://experience.sap.com/fiori-design-web/select/ Select}
*
* @extends sap.ui.core.Control
* @implements sap.ui.core.IFormContent, sap.ui.core.ISemanticFormContent
*
* @author SAP SE
* @version 1.117.4
*
* @constructor
* @public
* @alias sap.m.Select
*/
var Select = Control.extend("sap.m.Select", /** @lends sap.m.Select.prototype */ {
metadata: {
interfaces: [
"sap.ui.core.IFormContent",
"sap.m.IOverflowToolbarContent",
"sap.m.IToolbarInteractiveControl",
"sap.f.IShellBar",
"sap.ui.core.ISemanticFormContent"
],
library: "sap.m",
properties: {
/**
* The name to be used in the HTML code (for example, for HTML forms that send data to the server via submit).
*/
name: {
type: "string",
group: "Misc",
defaultValue: ""
},
/**
* Determines whether the user can modify the selected item. When the property is set
* to <code>false</code>, the control appears as disabled and CANNOT be focused.
*
* <b>Note:</b> When both <code>enabled</code> and <code>editable</code> properties
* are set to <code>false</code>, <code>enabled</code> has priority over
* <code>editable</code>.
*/
enabled: {
type: "boolean",
group: "Behavior",
defaultValue: true
},
/**
* Determines whether the user can modify the selected item. When the property is set
* to <code>false</code>, the control appears as disabled but CAN still be focused.
*
* <b>Note:</b> When both <code>enabled</code> and <code>editable</code> properties
* are set to <code>false</code>, <code>enabled</code> has priority over
* <code>editable</code>.
*
* @since 1.66.0
*/
editable: {
type: "boolean",
group: "Behavior",
defaultValue: true
},
/**
* Sets the width of the field. By default, the field width is automatically adjusted to the size
* of its content and the default width of the field is calculated based on the widest list item
* in the dropdown list.
* If the width defined is smaller than its content, only the field width is changed whereas
* the dropdown list keeps the width of its content.
* If the dropdown list is wider than the visual viewport, it is truncated and an ellipsis is displayed
* for each item.
* For phones, the width of the dropdown list is always the same as the viewport.
*
* <b>Note:</b> This property is ignored if the <code>autoAdjustWidth</code> property is set to <code>true</code>.
*/
width: {
type: "sap.ui.core.CSSSize",
group: "Dimension",
defaultValue: "auto"
},
/**
* Sets the maximum width of the control.
*
* <b>Note:</b> This property is ignored if the <code>autoAdjustWidth</code> property is set to <code>true</code>.
*/
maxWidth: {
type: "sap.ui.core.CSSSize",
group: "Dimension",
defaultValue: "100%"
},
/**
* Key of the selected item.
*
* <b>Notes:</b>
* <ul>
* <li> If duplicate keys exist, the first item matching the key is used.</li>
* <li> If invalid or none <code>selectedKey</code> is used, the first item is
* being selected.</li>
* <li> Invalid or missing <code>selectedKey</code> leads to severe functional
* issues in <code>sap.ui.table.Table</code>, when the <code>sap.m.Select</code> is used inside a
* <code>sap.ui.table.Table</code> column.</li>
* <li> If an item with the default key exists and we try to select it, it happens only if the
* <code>forceSelection</code> property is set to <code>true</code>.</li>
* </ul>
*
* @since 1.11
*/
selectedKey: {
type: "string",
group: "Data",
defaultValue: ""
},
/**
* ID of the selected item.
* @since 1.12
*/
selectedItemId: {
type: "string",
group: "Misc",
defaultValue: ""
},
/**
* The URI to the icon that will be displayed only when using the <code>IconOnly</code> type.
* @since 1.16
*/
icon: {
type: "sap.ui.core.URI",
group: "Appearance",
defaultValue: ""
},
/**
* Type of a select. Possible values <code>Default</code>, <code>IconOnly</code>.
* @since 1.16
*/
type: {
type: "sap.m.SelectType",
group: "Appearance",
defaultValue: SelectType.Default
},
/**
* Indicates whether the width of the input field is determined by the selected item's content.
* @since 1.16
*/
autoAdjustWidth: {
type: "boolean",
group: "Appearance",
defaultValue: false
},
/**
* Sets the horizontal alignment of the text within the input field.
* @since 1.28
*/
textAlign: {
type: "sap.ui.core.TextAlign",
group: "Appearance",
defaultValue: TextAlign.Initial
},
/**
* Specifies the direction of the text within the input field with enumerated options.
* By default, the control inherits text direction from the DOM.
* @since 1.28
*/
textDirection: {
type: "sap.ui.core.TextDirection",
group: "Appearance",
defaultValue: TextDirection.Inherit
},
/**
* Visualizes the validation state of the control, e.g. <code>Error</code>, <code>Warning</code>,
* <code>Success</code>, <code>Information</code>.
* @since 1.40.2
*/
valueState: {
type: "sap.ui.core.ValueState",
group: "Appearance",
defaultValue: ValueState.None
},
/**
* Defines the text of the value state message popup.
* If this is not specified, a default text is shown from the resource bundle.
*
* @since 1.40.5
*/
valueStateText: {
type: "string",
group: "Misc",
defaultValue: ""
},
/**
* Indicates whether the text values of the <code>additionalText</code> property of a
* {@link sap.ui.core.ListItem} are shown.
* @since 1.40
*/
showSecondaryValues: {
type: "boolean",
group: "Misc",
defaultValue: false
},
/**
* Modifies the behavior of the <code>setSelectedKey</code> method so that
* the selected item is cleared when a provided selected key is missing.
* @since 1.77
*/
resetOnMissingKey: {
type: "boolean",
group: "Behavior",
defaultValue: false
},
/**
* Indicates whether the selection is restricted to one of the items in the list.
* <b>Note:</b> We strongly recommend that you always set this property to <code>false</code> and bind
* the <code>selectedKey</code> property to the desired value for better interoperability with data binding.
* @since 1.34
*/
forceSelection: {
type: "boolean",
group: "Behavior",
defaultValue: true
},
/**
* Determines whether the text in the items wraps on multiple lines when the available width is not enough.
* When the text is truncated (<code>wrapItemsText</code> property is set to <code>false</code>),
* the max width of the <code>SelectList</code> is 600px. When <code>wrapItemsText</code> is set to
* <code>true</code>, <code>SelectList</code> takes all of the available width.
* @since 1.69
*/
wrapItemsText: {
type: "boolean",
group: "Behavior",
defaultValue: false
},
/**
* Determines the ratio between the first and the second column when secondary values are displayed.
*
* <b>Note:</b> This property takes effect only when the <code>showSecondaryValues</code> property is set to <code>true</code>.
* @since 1.86
*/
columnRatio: {
type: "sap.m.SelectColumnRatio",
group: "Appearance",
defaultValue: "3:2"
},
/**
* Indicates that user input is required. This property is only needed for accessibility purposes when a single relationship between
* the field and a label (see aggregation <code>labelFor</code> of <code>sap.m.Label</code>) cannot be established
* (e.g. one label should label multiple fields).
* @since 1.74
*/
required : {type : "boolean", group : "Misc", defaultValue : false}
},
defaultAggregation : "items",
aggregations: {
/**
* Defines the items contained within this control.
*
* <b>Note:</b> For items with icons you can use {@link sap.ui.core.ListItem}.
*
* Example:
*
* <pre>
* <code> <ListItem text="Paper plane" icon="sap-icon://paper-plane"></ListItem> </code>
* </pre>
*/
items: {
type: "sap.ui.core.Item",
multiple: true,
singularName: "item",
bindable: "bindable",
forwarding: {
getter: "getList",
aggregation: "items"
}
},
/**
* Internal aggregation to hold the inner picker popup.
*/
picker: {
type: "sap.ui.core.PopupInterface",
multiple: false,
visibility: "hidden"
},
/**
* Icon, displayed in the left most area of the <code>Select</code> input.
*/
_valueIcon: {
type: "sap.ui.core.Icon",
multiple: false,
visibility: "hidden"
},
/**
* Internal aggregation to hold the picker's header
* @since 1.52
*/
_pickerHeader: {
type: "sap.m.Bar",
multiple: false,
visibility: "hidden"
},
/**
* Internal aggregation to hold the picker's subheader.
*/
_pickerValueStateContent: {
type: "sap.m.Text",
multiple: false,
visibility: "hidden"
}
},
associations: {
/**
* Sets or retrieves the selected item from the aggregation named items.
*/
selectedItem: {
type: "sap.ui.core.Item",
multiple: false
},
/**
* Association to controls / IDs which label this control (see WAI-ARIA attribute <code>aria-labelledby</code>).
* @since 1.27.0
*/
ariaLabelledBy: {
type: "sap.ui.core.Control",
multiple: true,
singularName: "ariaLabelledBy"
}
},
events: {
/**
* This event is fired when the value in the selection field is changed in combination with one of
* the following actions:
* <ul>
* <li>The focus leaves the selection field</li>
* <li>The <i>Enter</i> key is pressed</li>
* <li>The item is pressed</li>
* </ul>
*/
change: {
parameters: {
/**
* The selected item.
*/
selectedItem: {
type: "sap.ui.core.Item"
},
/**
* The previous selected item.
* @since 1.95
*/
previousSelectedItem: {
type: "sap.ui.core.Item"
}
}
},
/**
* Fires when the user navigates through the <code>Select</code> items.
* It's also fired on revert of the currently selected item.
*
* <b>Note:</b> Revert occurs in some of the following actions:
* <ul>
* <li>The user clicks outside of the <code>Select</code></li>
* <li>The <i>Escape</i> key is pressed</li>
* </ul>
*
* @since 1.100
*/
liveChange: {
parameters: {
/**
* The selected item.
*/
selectedItem: {
type: "sap.ui.core.Item"
}
}
}
},
designtime: "sap/m/designtime/Select.designtime"
},
renderer: SelectRenderer
});
IconPool.insertFontFaceStyle();
EnabledPropagator.apply(Select.prototype, [true]);
// apply the message mixin so all message on the input will get the associated label-texts injected
MessageMixin.call(Select.prototype);
/* =========================================================== */
/* Private methods and properties */
/* =========================================================== */
/* ----------------------------------------------------------- */
/* Private methods */
/* ----------------------------------------------------------- */
function fnHandleKeyboardNavigation(oItem) {
if (this._isIconOnly() && !this.isOpen()) {
return;
}
if (oItem) {
if (this.getSelectedItemId() !== oItem.getId()) {
this.fireEvent("liveChange", {selectedItem: oItem});
}
this.setSelection(oItem);
this.setValue(oItem.getText());
this.scrollToItem(oItem);
}
}
Select.prototype._attachHiddenSelectHandlers = function () {
var oSelect = this._getHiddenSelect(),
oInput = this._getHiddenInput();
oSelect.on("focus", this._addFocusClass.bind(this));
oSelect.on("blur", this._removeFocusClass.bind(this));
oInput.on("focus", this.focus.bind(this));
};
Select.prototype.focus = function() {
this._getHiddenSelect().trigger("focus");
Control.prototype.focus.call(this, arguments);
};
Select.prototype._addFocusClass = function () {
this.$().addClass("sapMSltFocused");
};
Select.prototype._removeFocusClass = function () {
this.$().removeClass("sapMSltFocused");
};
Select.prototype._detachHiddenSelectHandlers = function () {
var oSelect = this._getHiddenSelect(),
oInput = this._getHiddenInput();
if (oSelect) {
oSelect.off("focus");
oSelect.off("blur");
}
if (oInput) {
oInput.off("focus");
}
};
Select.prototype._getHiddenSelect = function () {
return this.$("hiddenSelect");
};
Select.prototype._getHiddenInput = function () {
return this.$("hiddenInput");
};
Select.prototype._announceValueStateText = function() {
var sValueStateText = this._getValueStateText();
if (this._oInvisibleMessage) {
this._oInvisibleMessage.announce(sValueStateText, InvisibleMessageMode.Polite);
}
};
Select.prototype._getValueStateText = function() {
var sValueState = this.getValueState(),
sValueStateTypeText,
sValueStateText;
if (sValueState === ValueState.None) {
return "";
}
sValueStateTypeText = Library.getResourceBundleFor("sap.m").getText("INPUTBASE_VALUE_STATE_" + sValueState.toUpperCase());
sValueStateText = sValueStateTypeText + " " + (this.getValueStateText() || ValueStateSupport.getAdditionalText(this));
return sValueStateText;
};
Select.prototype._isFocused = function () {
return this.getFocusDomRef() === document.activeElement;
};
Select.prototype._isIconOnly = function () {
return this.getType() === SelectType.IconOnly;
};
Select.prototype._handleFocusout = function(oEvent) {
this._bFocusoutDueRendering = this.bRenderingPhase;
if (this._bFocusoutDueRendering) {
this._bProcessChange = false;
return;
}
if (this._bProcessChange) {
// if the focus-out is outside of the picker we should revert the selection
if (!this.isOpen() || oEvent.target === this.getAggregation("picker")) {
this._checkSelectionChange();
} else {
this._revertSelection();
}
this._bProcessChange = false;
} else {
this._bProcessChange = true;
}
};
Select.prototype._checkSelectionChange = function() {
var oItem = this.getSelectedItem();
if (this._oSelectionOnFocus !== oItem) {
this.fireChange({ selectedItem: oItem, previousSelectedItem: this._oSelectionOnFocus });
}
};
Select.prototype._revertSelection = function() {
var oItem = this.getSelectedItem();
if (this._oSelectionOnFocus !== oItem) {
this.fireEvent("liveChange", {selectedItem: this._oSelectionOnFocus});
this.setSelection(this._oSelectionOnFocus);
this.setValue(this._getSelectedItemText());
}
};
Select.prototype._getSelectedItemText = function(vItem) {
vItem = vItem || this.getSelectedItem();
if (!vItem) {
vItem = this.getDefaultSelectedItem();
}
if (vItem) {
return vItem.getText();
}
return "";
};
/**
* Enables the <code>sap.m.Select</code> to move inside the sap.m.OverflowToolbar.
* Required by the {@link sap.m.IOverflowToolbarContent} interface.
*
* @public
* @returns {sap.m.OverflowToolbarConfig} Configuration information for the <code>sap.m.IOverflowToolbarContent</code> interface.
*/
Select.prototype.getOverflowToolbarConfig = function() {
var noInvalidationProps = ["enabled", "selectedKey"];
if (!this.getAutoAdjustWidth() || this._bIsInOverflow) {
noInvalidationProps.push("selectedItemId");
}
var oConfig = {
canOverflow: true,
autoCloseEvents: ["change"],
invalidationEvents: ["_itemTextChange"],
propsUnrelatedToSize: noInvalidationProps
};
oConfig.onBeforeEnterOverflow = function(oSelect) {
var oToolbar = oSelect.getParent();
if (!oToolbar.isA("sap.m.OverflowToolbar")) {
return;
}
oSelect._prevSelectType = oSelect.getType();
oSelect._bIsInOverflow = true;
if (oSelect.getType() !== SelectType.Default) {
oSelect.setProperty("type", SelectType.Default, true);
}
};
oConfig.onAfterExitOverflow = function(oSelect) {
var oToolbar = oSelect.getParent();
if (!oToolbar.isA("sap.m.OverflowToolbar")) {
return;
}
oSelect._bIsInOverflow = false;
if (oSelect.getType() !== oSelect._prevSelectType) {
oSelect.setProperty("type", oSelect._prevSelectType, true);
}
};
return oConfig;
};
/**
* Gets the Select's <code>list</code>.
*
* @returns {sap.m.List}
* @private
* @since 1.22.0
*/
Select.prototype.getList = function() {
if (this._bIsBeingDestroyed) {
return null;
}
return this._oList;
};
/**
* Retrieves the first enabled item from the aggregation named <code>items</code>.
*
* @param {array} [aItems]
* @returns {sap.ui.core.Item | null}
* @private
*/
Select.prototype.findFirstEnabledItem = function(aItems) {
var oList = this.getList();
return oList ? oList.findFirstEnabledItem(aItems) : null;
};
/**
* Retrieves the last enabled item from the aggregation named <code>items</code>.
*
* @param {array} [aItems]
* @returns {sap.ui.core.Item | null}
* @private
*/
Select.prototype.findLastEnabledItem = function(aItems) {
var oList = this.getList();
return oList ? oList.findLastEnabledItem(aItems) : null;
};
/**
* Sets the selected item by its index.
*
* @param {int} iIndex
* @private
*/
Select.prototype.setSelectedIndex = function(iIndex, _aItems /* only for internal usage */) {
var oItem;
_aItems = _aItems || this.getItems();
// constrain the new index
iIndex = (iIndex > _aItems.length - 1) ? _aItems.length - 1 : Math.max(0, iIndex);
oItem = _aItems[iIndex];
if (oItem) {
this.setSelection(oItem);
}
};
/**
* Scrolls an item into the visual viewport.
*
* @private
*/
Select.prototype.scrollToItem = function(oItem) {
var oPickerDomRef = this.getPicker().getDomRef(),
oItemDomRef = oItem && oItem.getDomRef();
if (!oPickerDomRef || !oItemDomRef) {
return;
}
var oPickerSelectListDomRef = oPickerDomRef.querySelector('.sapUiSimpleFixFlexFlexContent'),
oPickerValueStateContentDomRef = oPickerDomRef.querySelector('.sapMSltPickerValueState'),
iPickerValueStateContentHeight = oPickerValueStateContentDomRef ? oPickerValueStateContentDomRef.clientHeight : 0,
iPickerScrollTop = oPickerSelectListDomRef.scrollTop,
iItemOffsetTop = oItemDomRef.offsetTop - iPickerValueStateContentHeight,
iPickerHeight = oPickerSelectListDomRef.clientHeight,
iItemHeight = oItemDomRef.offsetHeight;
if (iPickerScrollTop > iItemOffsetTop) {
// scroll up
oPickerSelectListDomRef.scrollTop = iItemOffsetTop;
// bottom edge of item > bottom edge of viewport
} else if ((iItemOffsetTop + iItemHeight) > (iPickerScrollTop + iPickerHeight)) {
// scroll down, the item is partly below the viewport of the list
oPickerSelectListDomRef.scrollTop = Math.ceil(iItemOffsetTop + iItemHeight - iPickerHeight);
}
};
/**
* Sets the text value of the <code>Select</code> field.
*
* @param {string} sValue
* @private
*/
Select.prototype.setValue = function(sValue) {
var oDomRef = this.getDomRef(),
oTextPlaceholder = oDomRef && oDomRef.querySelector(".sapMSelectListItemText"),
bForceAnnounce = !this.isOpen() && this._isFocused() && this._oInvisibleMessage;
if (oTextPlaceholder) {
oTextPlaceholder.textContent = sValue;
}
this._setHiddenSelectValue();
this._getValueIcon();
if (bForceAnnounce) {
// InvisibleMessage is used in case the popover is closed.
// (aria-activedescendant is announcing the new selection when popover is opened).
this._oInvisibleMessage.announce(sValue, InvisibleMessageMode.Assertive);
}
};
Select.prototype._setHiddenSelectValue = function () {
var oSelect = this._getHiddenSelect(),
oInput = this._getHiddenInput(),
oSelectedKey = this.getSelectedKey(),
sSelectedItemText = this._getSelectedItemText();
// the hidden INPUT is only used when the select is submitted
// with a form so update its value in all cases
oInput.attr("value", oSelectedKey || "");
if (!this._isIconOnly()) {
oSelect.text(sSelectedItemText);
}
};
Select.prototype._getValueIcon = function() {
if (this._bIsBeingDestroyed) {
return null;
}
var oValueIcon = this.getAggregation("_valueIcon"),
oSelectedItem = this.getSelectedItem(),
bHaveIcon = !!(oSelectedItem && oSelectedItem.getIcon && oSelectedItem.getIcon()),
sIconSrc = bHaveIcon ? oSelectedItem.getIcon() : "sap-icon://pull-down";
if (!oValueIcon) {
oValueIcon = new Icon(this.getId() + "-labelIcon", {src: sIconSrc, visible: false});
this.setAggregation("_valueIcon", oValueIcon, true);
}
if (oValueIcon.getVisible() !== bHaveIcon) {
oValueIcon.setVisible(bHaveIcon);
oValueIcon.toggleStyleClass("sapMSelectListItemIcon", bHaveIcon);
}
if (bHaveIcon && oSelectedItem.getIcon() !== oValueIcon.getSrc()) {
oValueIcon.setSrc(sIconSrc);
}
return oValueIcon;
};
/**
* Whether a shadow list should be render inside the HTML content of the select's field, to
* automatically size it to fit its content.
*
* @returns {boolean}
* @private
*/
Select.prototype._isShadowListRequired = function() {
if (this.getAutoAdjustWidth()) {
return false;
} else if (this.getWidth() === "auto") {
return true;
}
return false;
};
/**
* Handles the virtual focus of items.
*
* @param {sap.ui.core.Item | null} vItem
* @private
* @since 1.30
*/
Select.prototype._handleAriaActiveDescendant = function(vItem) {
var oDomRef = this.getFocusDomRef(),
oItemDomRef = vItem && vItem.getDomRef(),
sActivedescendant = "aria-activedescendant";
if (!oDomRef) {
return;
}
// the aria-activedescendant attribute is set when the item is rendered
if (oItemDomRef && this.isOpen()) {
oDomRef.setAttribute(sActivedescendant, vItem.getId());
} else {
oDomRef.removeAttribute(sActivedescendant);
}
};
Select.prototype.updateItems = function(sReason) {
SelectList.prototype.updateItems.apply(this, arguments);
// note: after the items are recreated, the selected item association
// points to the new item
this._oSelectionOnFocus = this.getSelectedItem();
};
/**
* Called when the items aggregation needs to be refreshed.
*
* <b>Note:</b> This method has been overwritten to prevent <code>updateItems()</code>
* from being called when the bindings are refreshed.
* @see sap.ui.base.ManagedObject#bindAggregation
*/
Select.prototype.refreshItems = function() {
SelectList.prototype.refreshItems.apply(this, arguments);
};
/* ----------------------------------------------------------- */
/* Picker */
/* ----------------------------------------------------------- */
/**
* This event handler is called before the picker popup is opened.
*
* @private
*/
Select.prototype.onBeforeOpen = function(oControlEvent) {
var fnPickerTypeBeforeOpen = this["_onBeforeOpen" + this.getPickerType()],
CSS_CLASS = this.getRenderer().CSS_CLASS;
// add the active and expanded states to the field
this.addStyleClass(CSS_CLASS + "Pressed");
this.addStyleClass(CSS_CLASS + "Expanded");
// close value state message before opening the picker
this.closeValueStateMessage();
// call the hook to add additional content to the list
this.addContent();
this.addContentToFlex();
fnPickerTypeBeforeOpen && fnPickerTypeBeforeOpen.call(this);
};
/**
* This event handler will be called after the picker popup is opened.
*
* @private
*/
Select.prototype.onAfterOpen = function(oControlEvent) {
var oDomRef = this.getFocusDomRef(),
oItem = null;
if (!oDomRef) {
return;
}
oItem = this.getSelectedItem();
oDomRef.setAttribute("aria-expanded", "true");
// expose a parent/child contextual relationship to assistive technologies
// note: the "aria-controls" attribute is set when the list is visible and in view
oDomRef.setAttribute("aria-controls", this.getList().getId());
if (oItem) {
// note: the "aria-activedescendant" attribute is set
// when the currently active descendant is visible and in view
oDomRef.setAttribute("aria-activedescendant", oItem.getId());
this.scrollToItem(oItem);
}
};
/**
* This event handler is called before the picker popup is closed.
*
*/
Select.prototype.onBeforeClose = function(oControlEvent) {
var oDomRef = this.getFocusDomRef(),
CSS_CLASS = this.getRenderer().CSS_CLASS;
if (oDomRef) {
// note: the "aria-controls" attribute is removed when the list is not visible and in view
oDomRef.removeAttribute("aria-controls");
// the "aria-activedescendant" attribute is removed when the currently active descendant is not visible
oDomRef.removeAttribute("aria-activedescendant");
// if the focus is back to the input after closing the picker,
// the value state message should be reopened
if (this.shouldValueStateMessageBeOpened() && (document.activeElement === oDomRef)) {
this.openValueStateMessage();
}
}
// remove the expanded states of the field
this.removeStyleClass(CSS_CLASS + "Expanded");
};
/**
* This event handler is called after the picker popup is closed.
*
*/
Select.prototype.onAfterClose = function(oControlEvent) {
var oDomRef = this.getFocusDomRef(),
CSS_CLASS = this.getRenderer().CSS_CLASS,
sPressedCSSClass = CSS_CLASS + "Pressed";
if (oDomRef) {
oDomRef.setAttribute("aria-expanded", "false");
oDomRef.removeAttribute("aria-activedescendant");
}
// Remove the active state
this.removeStyleClass(sPressedCSSClass);
};
/**
* Gets the control's picker popup.
*
* @returns {sap.m.Dialog | sap.m.Popover | null} The picker instance, creating it if necessary by calling <code>createPicker()</code> method.
* @private
*/
Select.prototype.getPicker = function() {
if (this._bIsBeingDestroyed) {
return null;
}
// initialize the control's picker
return this.createPicker(this.getPickerType());
};
/**
* Gets the InvisibleText instance for the <code>valueStateText</code> property.
*
* @returns {sap.ui.core.InvisibleText | null} The <code>InvisibleText</code> instance, used to reference the text in aria-labelledby of interested controls.
* @private
*/
Select.prototype.getValueStateTextInvisibleText = function() {
if (this._bIsBeingDestroyed) {
return null;
}
if (!this._oValueStateTextInvisibleText) {
this._oValueStateTextInvisibleText = new InvisibleText({ id: this.getId() + "-valueStateText-InvisibleText" });
this._oValueStateTextInvisibleText.toStatic();
}
return this._oValueStateTextInvisibleText;
};
Select.prototype.getSimpleFixFlex = function() {
if (this._bIsBeingDestroyed) {
return null;
} else if (this.oSimpleFixFlex) {
return this.oSimpleFixFlex;
}
// initialize the SimpleFixFlex
this.oSimpleFixFlex = new SimpleFixFlex({
id: this.getPickerValueStateContentId(),
fixContent: this._getPickerValueStateContent()
.addStyleClass(this.getRenderer().CSS_CLASS + "PickerValueState"),
flexContent: this.createList()
});
return this.oSimpleFixFlex;
};
/**
* Setter for property <code>_sPickerType</code>.
*
* @private
*/
Select.prototype.setPickerType = function(sPickerType) {
this._sPickerType = sPickerType;
};
/**
* Getter for property <code>_sPickerType</code>
*
* @returns {string}
* @private
*/
Select.prototype.getPickerType = function() {
return this._sPickerType;
};
/**
* Get's the picker's subheader.
*
* @returns {sap.m.Bar} Picker's header
* @private
*/
Select.prototype._getPickerValueStateContent = function() {
if (!this.getAggregation("_pickerValueStateContent")) {
this.setAggregation("_pickerValueStateContent", new Text({
wrapping: true,
text: this._getTextForPickerValueStateContent()
}));
}
return this.getAggregation("_pickerValueStateContent");
};
/**
* Sets the <code>valueStateText</code> into the picker's subheader title.
* @returns {void}
* @private
*/
Select.prototype._updatePickerValueStateContentText = function() {
var oPicker = this.getPicker(),
oPickerValueStateContent = oPicker && oPicker.getContent()[0].getFixContent(),
sText;
if (oPickerValueStateContent) {
sText = this._getTextForPickerValueStateContent();
oPickerValueStateContent.setText(sText);
}
};
/**
* Gets the text for the picker's subheader title.
* In case <code>valueStateText</code> is not set, a default value is returned.
* @returns {string}
* @private
*/
Select.prototype._getTextForPickerValueStateContent = function() {
var sValueStateText = this.getValueStateText(),
sText;
if (sValueStateText) {
sText = sValueStateText;
} else {
sText = this._getDefaultTextForPickerValueStateContent();
}
return sText;
};
/**
* Gets the default text for the picker's subheader title.
* @returns {string}
* @private
*/
Select.prototype._getDefaultTextForPickerValueStateContent = function() {
var sValueState = this.getValueState(),
oResourceBundle,
sText;
if (sValueState === ValueState.None) {
sText = "";
} else {
oResourceBundle = Library.getResourceBundleFor("sap.ui.core");
sText = oResourceBundle.getText("VALUE_STATE_" + sValueState.toUpperCase());
}
return sText;
};
/**
* Updates CSS classes for the <code>valueStateText</code> in the picker's subheader.
* @private
*/
Select.prototype._updatePickerValueStateContentStyles = function() {
var sValueState = this.getValueState(),
mValueState = ValueState,
CSS_CLASS = this.getRenderer().CSS_CLASS,
PICKER_CSS_CLASS = CSS_CLASS + "Picker",
sCssClass = PICKER_CSS_CLASS + sValueState + "State",
sPickerWithSubHeader = PICKER_CSS_CLASS + "WithSubHeader",
oPicker = this.getPicker(),
oCustomHeader = oPicker && oPicker.getContent()[0].getFixContent();
if (oCustomHeader) {
this._removeValueStateClassesForPickerValueStateContent(oPicker);
oCustomHeader.addStyleClass(sCssClass);
if (sValueState !== mValueState.None) {
oPicker.addStyleClass(sPickerWithSubHeader);
} else {
oPicker.removeStyleClass(sPickerWithSubHeader);
}
}
};
/**
* Removes the picker's subheader value state classes for all available value states.
* @param {sap.m.Popover | sap.m.Dialog} oPicker
* @returns {void}
* @private
*/
Select.prototype._removeValueStateClassesForPickerValueStateContent = function(oPicker) {
var mValueState = ValueState,
CSS_CLASS = this.getRenderer().CSS_CLASS,
PICKER_CSS_CLASS = CSS_CLASS + "Picker",
subHeader = oPicker.getContent()[0].getFixContent();
Object.keys(mValueState).forEach(function (key) {
var sOldCssClass = PICKER_CSS_CLASS + key + "State";
subHeader.removeStyleClass(sOldCssClass);
});
};
/* ----------------------------------------------------------- */
/* Popover */
/* ----------------------------------------------------------- */
/**
* Creates an instance of <code>sap.m.Popover</code>.
*
* @returns {sap.m.Popover}
* @private
*/
Select.prototype._createPopover = function() {
var that = this;
var oPicker = new Popover({
showArrow: false,
showHeader: false,
placement: PlacementType.VerticalPreferredBottom,
offsetX: 0,
offsetY: 0,
initialFocus: this,
ariaLabelledBy: this._getPickerHiddenLabelId()
});
// detect when the scrollbar or an item is pressed
oPicker.addEventDelegate({
ontouchstart: function(oEvent) {
var oPickerDomRef = this.getDomRef("cont");
if ((oEvent.target === oPickerDomRef) || (oEvent.srcControl instanceof Item)) {
that._bProcessChange = false;
}
}
}, oPicker);
this._decoratePopover(oPicker);
return oPicker;
};
/**
* Decorates a <code>sap.m.Popover</code> instance.
*
* @param {sap.m.Popover} oPopover
* @private
*/
Select.prototype._decoratePopover = function(oPopover) {
var that = this;
oPopover.open = function() {
return this.openBy(that);
};
};
/**
* Required adaptations before rendering of the popover.
*
* @private
*/
Select.prototype._onBeforeRenderingPopover = function() {
var oPopover = this.getPicker(),
sWidth = this.$().outerWidth() + "px"; // set popover content min-width in px due to rendering issue in Chrome and small %
if (oPopover) {
oPopover.setContentMinWidth(sWidth);
}
};
/* ----------------------------------------------------------- */
/* Dialog */
/* ----------------------------------------------------------- */
/**
* Creates an instance of <code>sap.m.Dialog</code>.
*
* @returns {sap.m.Dialog}
* @private
*/
Select.prototype._createDialog = function() {
var that = this,
oHeader = this._getPickerHeader(),
oDialog = new Dialog({
stretch: true,
ariaLabelledBy: this._getPickerHiddenLabelId(),
customHeader: oHeader,
beforeOpen: function() {
that.updatePickerHeaderTitle();
}
});
return oDialog;
};
/**
* Gets the picker header title.
*
* @returns {sap.m.Title | null} The title instance of the Picker
* @private
* @since 1.52
*/
Select.prototype._getPickerTitle = function() {
var oPicker = this.getPicker(),
oHeader = oPicker && oPicker.getCustomHeader();
if (oHeader) {
return oHeader.getContentMiddle()[0];
}
return null;
};
/**
* Get's the Picker's header.
*
* @returns {sap.m.Bar} Picker's header
* @private
* @since 1.52
*/
Select.prototype._getPickerHeader = function() {
var sIconURI = IconPool.getIconURI("decline"),
oResourceBundle;
if (!this.getAggregation("_pickerHeader")) {
oResourceBundle = Library.getResourceBundleFor("sap.m");
this.setAggregation("_pickerHeader", new Bar({
titleAlignment: library.TitleAlignment.Auto,
contentMiddle: new Title({
text: oResourceBundle.getText("SELECT_PICKER_TITLE_TEXT"),
level: TitleLevel.H1
}),
contentRight: new Button({
icon: sIconURI,
press: this.close.bind(this)
})
}));
}
return this.getAggregation("_pickerHeader");
};
Select.prototype._getPickerHiddenLabelId = function() {
return InvisibleText.getStaticId("sap.m", "INPUT_AVALIABLE_VALUES");
};
Select.prototype.getPickerValueStateContentId = function() {
return this.getId() + "-valueStateText";
};
Select.prototype.updatePickerHeaderTitle = function() {
var oPicker = this.getPicker();
if (!oPicker) {
return;
}
var aLabels = this.getLabels();
if (aLabels.length) {
var oLabel = aLabels[0],
oPickerTitle = this._getPickerTitle();
if (oLabel && (typeof oLabel.getText === "function")) {
oPickerTitle && oPickerTitle.setText(oLabel.getText());
}
}
};
/**
* This event handler is called before the dialog is opened.
*
* @private
*/
Select.prototype._onBeforeOpenDialog = function() {};
/* =========================================================== */
/* Lifecycle methods */
/* =========================================================== */
Select.prototype.init = function() {
// set the picker type
this.setPickerType(Device.system.phone ? "Dialog" : "Popover");
// initialize composites
this.createPicker(this.getPickerType());
// selected item on focus
this._oSelectionOnFocus = null;
// to detect when the control is in the rendering phase
this.bRenderingPhase = false;
// to detect if the focusout event is triggered due a re-rendering
this._bFocusoutDueRendering = false;
// used to prevent the change event from firing when the user scrolls
// the picker popup (dropdown) list using the mouse
this._bProcessChange = false;
this.sTypedChars = "";
this.iTypingTimeoutID = -1;
// delegate object used to open/close value state message popups
this._oValueStateMessage = new ValueStateMessage(this);
this._bValueStateMessageOpened = false;
this._sAriaRoleDescription = Library.getResourceBundleFor("sap.m").getText("SELECT_ROLE_DESCRIPTION");
this._oInvisibleMessage = null;
this._referencingLabelsHandlers = [];
};
Select.prototype.onBeforeRendering = function() {
if (!this._oInvisibleMessage) {
this._oInvisibleMessage = InvisibleMessage.getInstance();
}
// rendering phase is started
this.bRenderingPhase = true;
this.synchronizeSelection({
forceSelection: this.getForceSelection()
});
this._updatePickerValueStateContentText();
this._updatePickerValueStateContentStyles();
this._detachHiddenSelectHandlers();
if (this._isIconOnly()) {
this.setAutoAdjustWidth(true);
}
};
Select.prototype.onAfterRendering = function() {
// rendering phase is finished
this.bRenderingPhase = false;
this._setHiddenSelectValue();
this._attachHiddenSelectHandlers();
this._clearReferencingLabelsHandlers();
this._handleReferencingLabels();
};
Select.prototype.exit = function() {
var oValueStateMessage = this.getValueStateMessage(),
oValueIcon = this._getValueIcon();
this._oSelectionOnFocus = null;
if (this._oValueStateTextInvisibleText) {
this._oValueStateTextInvisibleText.destroy();
this._oValueStateTextInvisibleText = null;
}
if (oValueStateMessage) {
this.closeValueStateMessage();
oValueStateMessage.destroy();
}
if (oValueIcon) {
oValueIcon.destroy();
}
this._oValueStateMessage = null;
this._bValueStateMessageOpened = false;
};
/* =========================================================== */
/* Event handlers */
/* =========================================================== */
/**
* Handle the touch start event on the Select.
*
* @param {jQuery.Event} oEvent The event object.
* @private
*/
Select.prototype.ontouchstart = function(oEvent) {
// mark the event for components that needs to know if the event was handled
oEvent.setMarked();
if (this.getEnabled() && this.getEditable()) {
// add the active state to the Select's field
this.addStyleClass(this.getRenderer().CSS_CLASS + "Pressed");
this.focus();
}
};
/**
* Handle the touch end event on the Select.
*
* @param {jQuery.Event} oEvent The event object.
* @private
*/
Select.prototype.ontouchend = function(oEvent) {
// mark the event for components that needs to know if the event was handled
oEvent.setMarked();
if (this.getEnabled() && this.getEditable() && !this.isOpen() && this.isOpenArea(oEvent.target)) {
// remove the active state of the Select HTMLDIVElement container
this.removeStyleClass(this.getRenderer().CSS_CLASS + "Pressed");
}
};
/**
* Handle the tap event on the Select.
*
* @param {jQuery.Event} oEvent The event object.
* @private
*/
Select.prototype.ontap = function(oEvent) {
var CSS_CLASS = this.getRenderer().CSS_CLASS;
// mark the event for components that needs to know if the event was handled
oEvent.setMarked();
if (!this.getEnabled() || !this.getEditable()) {
return;
}
if (this.isOpenArea(oEvent.target)) {
if (this.isOpen()) {
this.close();
this.removeStyleClass(CSS_CLASS + "Pressed");
return;
}
if (Device.system.phone) {
// focus has to be set to the native select DOM element in order to restore the focus back to it
// after the select dialog is closed
this.focus();
}
this.open();
}
if (this.isOpen()) {
// add the active state to the Select's field
this.addStyleClass(CSS_CLASS + "Pressed");
}
};
/**
* Handles the <code>liveChange</code> event on the <code>SelectList</code>.
*
* @param {sap.ui.base.Event} oControlEvent
* @private
*/
Select.prototype.onSelectionChange = function(oControlEvent) {
var oItem = oControlEvent.getParameter("selectedItem"),
oPreviousSelectedItem = this.getSelectedItem();
this.close();
this.setSelection(oItem);
this.fireChange({ selectedItem: oItem, previousSelectedItem: oPreviousSelectedItem });
// check and update icon
this.setValue(this._getSelectedItemText());
};
/* ----------------------------------------------------------- */
/* Keyboard handling */
/* ----------------------------------------------------------- */
/**
* Handles the <code>keypress</code> event.
*
* @param {jQuery.Event} oEvent The event object.
* @private
*/
Select.prototype.onkeypress = function(oEvent) {
// prevents actions from occurring when the control is non-editable
if (!this.getEditable()) {
return;
}
// mark the event for components that needs to know if the event was handled
oEvent.setMarked();
// note: jQuery oEvent.which normalizes oEvent.keyCode and oEvent.charCode
var sTypedCharacter = String.fromCharCode(oEvent.which),
sText;
this.sTypedChars += sTypedCharacter;
// We check if we have more than one characters and they are all duplicate, we set the
// text to be the last input character (sTypedCharacter). If not, we set the text to be
// the whole input string.
sText = (/^(.)\1+$/i).test(this.sTypedChars) ? sTypedCharacter : this.sTypedChars;
clearTimeout(this.iTypingTimeoutID);
this.iTypingTimeoutID = setTimeout(function() {
this.sTypedChars = "";
this.iTypingTimeoutID = -1;
}.bind(this), 1000);
fnHandleKeyboardNavigation.call(this, this.searchNextItemByText(sText));
};
/**
* Handle when F4 or Alt + DOWN arrow are pressed.
*
* @param {jQuery.Event} oEvent The event object.
* @private
*/
Select.prototype.onsapshow = function(oEvent) {
// prevents actions from occurring when the control is non-editable
if (!this.getEditable()) {
return;
}
// mark the event for components that needs to know if the event was handled
oEvent.setMarked();
// note: prevent browser address bar to be open in ie9, when F4 is pressed
if (oEvent.which === KeyCodes.F4) {
oEvent.preventDefault();
}
this.toggleOpenState();
};
/**
* Handle when Alt + UP arrow are pressed.
*
* @param {jQuery.Event} oEvent The event object.
* @private
* @function
*/
Select.prototype.onsaphide = Select.prototype.onsapshow;
/**
* Handle when mousedown event is fired on the select.
*
* Call preventDefault function on oEvent object to suppress
* the default browser behavior of opening a native dropdown
*
* @param {jQuery.Event} oEvent The event object.
* @private
* @function
*/
Select.prototype.onmousedown = function (oEvent) {
oEvent.preventDefault();
this._getHiddenSelect().trigger("focus");
};
/**
* Handle when escape is pressed.
*
* @param {jQuery.Event} oEvent The event object.
* @private
*/
Select.prototype.onsapescape = function(oEvent) {
// prevents actions from occurring when the control is non-editable
if (!this.getEditable() || this._bSpaceDown) {
return;
}
if (this.isOpen()) {
// mark the event for components that needs to know if the event was handled
oEvent.setMarked();
this.close();
this._revertSelection();
}
};
/**
* Handles the <code>sapenter</code> event when enter key is pressed.
*
* @param {jQuery.Event} oEvent The event object.
* @private
*/
Select.prototype.onsapenter = function(oEvent) {
// Prevent the opening of the native select with options
oEvent.preventDefault();
// prevents actions from occurring when the control is non-editable
if (!this.getEditable()) {
return;
}
// mark the event for components that needs to know if the event was handled
if (this.isOpen()) {
oEvent.setMarked();
}
this.close();
this._checkSelectionChange();
};
/**
* Handles the keydown events.
*
* @param {jQuery.Event} oEvent The event object.
* @private
*/
Select.prototype.onkeydown = function(oEvent) {
if (oEvent.which === KeyCodes.SPACE) {
this._bSpaceDown = true;
}
if ([KeyCodes.ARROW_DOWN, KeyCodes.ARROW_UP, KeyCodes.SPACE].indexOf(oEvent.which) > -1) {
// note: [KeyCodes.ARROW_DOWN, KeyCodes.ARROW_UP] prevent native dropdown to open when ALT/CMD and ARROW DOWN/UP keys are pressed
// note: [KeyCodes.SPACE] prevent document scrolling when the spacebar key is pressed
oEvent.preventDefault();
}
if (oEvent.which === KeyCodes.SHIFT || oEvent.which === KeyCodes.ESCAPE) {
this._bSupressNextAction = this._bSpaceDown;
}
};
/**
* Handles the keyup event for SPACE.
*
* @param {jQuery.Event} oEvent The event object.
* @private
*/
Select.prototype.onkeyup = function(oEvent) {
// prevents actions from occurring when the control is non-editable
if (!this.getEditable()) {
return;
}
if (oEvent.which === KeyCodes.SPACE) {
if (!oEvent.shiftKey && !this._bSupressNextAction) {
// mark the event for components that needs to know if the event was handled
oEvent.setMarked();
if (this.isOpen()) {
this._checkSelectionChange();
}
this.toggleOpenState();
}
this._bSpaceDown = false;
this._bSupressNextAction = false;
}
};
/**
* Handles the <code>sapdown</code> pseudo event when keyboard DOWN arrow key is pressed.
*
* @param {jQuery.Event} oEvent The event object.
* @private
*/
Select.prototype.onsapdown = function(oEvent) {
// prevents actions from occurring when the control is non-editable
if (!this.getEditable()) {
return;
}
// mark the event for components that needs to know if the event was handled
oEvent.