@openui5/sap.m
Version:
OpenUI5 UI Library sap.m
1,829 lines (1,526 loc) • 71.9 kB
JavaScript
/*!
* UI development toolkit for HTML5 (OpenUI5)
* (c) Copyright 2009-2022 SAP SE or an SAP affiliate company.
* Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
*/
sap.ui.define([
'./Dialog',
'./Popover',
'./SelectList',
'./library',
'sap/ui/core/Control',
'sap/ui/core/EnabledPropagator',
'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"
],
function(
Dialog,
Popover,
SelectList,
library,
Control,
EnabledPropagator,
IconPool,
Button,
Bar,
Title,
ValueStateMessage,
MessageMixin,
coreLibrary,
Item,
Device,
InvisibleText,
SelectRenderer,
containsOrEquals,
KeyCodes
) {
"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.m.SelectType
var SelectType = library.SelectType;
/**
* 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
*
* @author SAP SE
* @version 1.60.39
*
* @constructor
* @public
* @alias sap.m.Select
* @ui5-metamodel This control will also be described in the UI5 (legacy) design time meta model.
*/
var Select = Control.extend("sap.m.Select", /** @lends sap.m.Select.prototype */ {
metadata: {
interfaces: [
"sap.ui.core.IFormContent",
"sap.m.IOverflowToolbarContent"
],
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: ""
},
/**
* Indicates whether the user can change the selection.
*/
enabled: {
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.m.Table</code>, when the <code>sap.m.Select</code> is used inside a
* <code>sap.m.Table</code> column.</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
},
/**
* 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
}
},
defaultAggregation : "items",
aggregations: {
/**
* Defines the items contained within this control.
*/
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"
},
/**
* Internal aggregation to hold the picker's header
* @since 1.52
*/
_pickerHeader: {
type: "sap.m.Bar",
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"
}
}
}
},
designtime: "sap/m/designtime/Select.designtime"
}
});
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 (oItem) {
this.setSelection(oItem);
this.setValue(oItem.getText());
this.scrollToItem(oItem);
}
}
var oAnnounceInstance = null;
/**
* @return {string} Returns the span to be rendered for the assertive instance.
* @private
* @function
*/
function fnGetAssertiveInstance() {
var sId = "_sapMSltAnnounceMessage-assertive";
return '<span id="' + sId + '" data-sap-ui="' + sId +
'" class="sapUiAnnounceMessageAssertive" role="status" aria-live="assertive">' +
'</span>';
}
function fnGetAnnounceInstance() {
var oCore = sap.ui.getCore(),
oStatic = oCore.getStaticAreaRef(),
oNode;
if (!oAnnounceInstance) {
oStatic.insertAdjacentHTML("beforeend", fnGetAssertiveInstance());
oAnnounceInstance = {
announce: function (sText) {
oNode = oStatic.querySelector(".sapUiAnnounceMessageAssertive");
oNode.textContent = "";
oNode.textContent = sText;
}
};
}
return oAnnounceInstance;
}
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 });
}
};
Select.prototype._revertSelection = function() {
var oItem = this.getSelectedItem();
if (this._oSelectionOnFocus !== oItem) {
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 {object} 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.bIsDestroyed) {
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 oPicker = this.getPicker(),
oPickerDomRef = oPicker.getDomRef("cont"),
oItemDomRef = oItem && oItem.getDomRef();
if (!oPicker || !oPickerDomRef || !oItemDomRef) {
return;
}
var iPickerScrollTop = oPickerDomRef.scrollTop,
iItemOffsetTop = oItemDomRef.offsetTop,
iPickerHeight = oPickerDomRef.clientHeight,
iItemHeight = oItemDomRef.offsetHeight;
if (iPickerScrollTop > iItemOffsetTop) {
// scroll up
oPickerDomRef.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
oPickerDomRef.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) {
this.$("label").text(sValue);
if (this._bFocused && !this.isOpen()) {
this._oAnnounceInstance.announce(sValue);
}
};
/**
* 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.getDomRef(),
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();
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-owns" attribute is set when the list is visible and in view
oDomRef.setAttribute("aria-owns", 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-owns" attribute is removed when the list is not visible and in view
oDomRef.removeAttribute("aria-owns");
// 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.bIsDestroyed) {
return null;
}
// initialize the control's picker
return this.createPicker(this.getPickerType());
};
/**
* 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;
};
/* ----------------------------------------------------------- */
/* 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,
bounce: false,
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;
return new Dialog({
stretch: true,
ariaLabelledBy: this._getPickerHiddenLabelId(),
customHeader: this._getPickerHeader(),
beforeOpen: function() {
that.updatePickerHeaderTitle();
}
});
};
/**
* 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 = sap.ui.getCore().getLibraryResourceBundle("sap.m");
this.setAggregation("_pickerHeader", new Bar({
contentMiddle: new Title({
text: oResourceBundle.getText("SELECT_PICKER_TITLE_TEXT")
}),
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.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._bFocused = false;
};
Select.prototype.onBeforeRendering = function() {
if (!this._oAnnounceInstance) {
this._oAnnounceInstance = fnGetAnnounceInstance();
}
// rendering phase is started
this.bRenderingPhase = true;
// note: in Firefox 38, the focusout event is not fired when the select is removed
if (Device.browser.firefox && (this.getFocusDomRef() === document.activeElement)) {
this._handleFocusout();
}
this.synchronizeSelection();
};
Select.prototype.onAfterRendering = function() {
// rendering phase is finished
this.bRenderingPhase = false;
};
Select.prototype.exit = function() {
var oValueStateMessage = this.getValueStateMessage();
this._oSelectionOnFocus = null;
if (oValueStateMessage) {
this.closeValueStateMessage();
oValueStateMessage.destroy();
}
this._oValueStateMessage = null;
};
/* =========================================================== */
/* 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.isOpenArea(oEvent.target)) {
// add the active state to the Select's field
this.addStyleClass(this.getRenderer().CSS_CLASS + "Pressed");
}
};
/**
* 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.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()) {
return;
}
if (this.isOpenArea(oEvent.target)) {
if (this.isOpen()) {
this.close();
this.removeStyleClass(CSS_CLASS + "Pressed");
return;
}
if (Device.system.phone) {
this.focus();
}
this.open();
}
if (this.isOpen()) {
// add the active state to the Select's field
this.addStyleClass(CSS_CLASS + "Pressed");
}
};
/**
* Handles the <code>selectionChange</code> event on the <code>SelectList</code>.
*
* @param {sap.ui.base.Event} oControlEvent
* @private
*/
Select.prototype.onSelectionChange = function(oControlEvent) {
var oItem = oControlEvent.getParameter("selectedItem");
this.close();
this.setSelection(oItem);
this.fireChange({ selectedItem: oItem });
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 disabled,
// IE11 browser focus non-focusable elements
if (!this.getEnabled()) {
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),
oSelectedItem = this.getSelectedItem(),
sText = sTypedCharacter,
oItem = null;
this.sTypedChars += sTypedCharacter;
var bStartsWithTypedChars = typeof this.sTypedChars === "string" &&
this.sTypedChars !== "" &&
oSelectedItem &&
oSelectedItem.getText().toLowerCase().startsWith(this.sTypedChars.toLowerCase());
// the typed characters match the text of the selected item
if (bStartsWithTypedChars ||
// one or more characters have been typed (excluding patterns such as "aa" or "bb")
((this.sTypedChars.length === 1) ||
((this.sTypedChars.length > 1) &&
(this.sTypedChars.charAt(0) !== this.sTypedChars.charAt(1))))) {
sText = this.sTypedChars;
}
oItem = this.searchNextItemByText(sText);
clearTimeout(this.iTypingTimeoutID);
this.iTypingTimeoutID = setTimeout(function() {
this.sTypedChars = "";
this.iTypingTimeoutID = -1;
}.bind(this), 1000);
fnHandleKeyboardNavigation.call(this, oItem);
};
/**
* 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 disabled,
// IE11 browser focus non-focusable elements
if (!this.getEnabled()) {
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 escape is pressed.
*
* @param {jQuery.Event} oEvent The event object.
* @private
*/
Select.prototype.onsapescape = function(oEvent) {
// prevents actions from occurring when the control is disabled,
// IE11 browser focus non-focusable elements
if (!this.getEnabled()) {
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) {
// prevents actions from occurring when the control is disabled,
// IE11 browser focus non-focusable elements
if (!this.getEnabled()) {
return;
}
// mark the event for components that needs to know if the event was handled
oEvent.setMarked();
this.close();
this._checkSelectionChange();
};
/**
* Handle when the spacebar key is pressed.
*
* @param {jQuery.Event} oEvent The event object.
* @private
*/
Select.prototype.onsapspace = function(oEvent) {
// prevents actions from occurring when the control is disabled,
// IE11 browser focus non-focusable elements
if (!this.getEnabled()) {
return;
}
// mark the event for components that needs to know if the event was handled
oEvent.setMarked();
// note: prevent document scrolling when the spacebar key is pressed
oEvent.preventDefault();
if (this.isOpen()) {
this._checkSelectionChange();
}
this.toggleOpenState();
};
/**
* 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 disabled,
// IE11 browser focus non-focusable elements
if (!this.getEnabled()) {
return;
}
// mark the event for components that needs to know if the event was handled
oEvent.setMarked();
// note: prevent document scrolling when arrow keys are pressed
oEvent.preventDefault();
var oNextSelectableItem,
aSelectableItems = this.getSelectableItems();
oNextSelectableItem = aSelectableItems[aSelectableItems.indexOf(this.getSelectedItem()) + 1];
fnHandleKeyboardNavigation.call(this, oNextSelectableItem);
};
/**
* Handles the <code>sapup</code> pseudo event when keyboard UP arrow key is pressed.
*
* @param {jQuery.Event} oEvent The event object.
* @private
*/
Select.prototype.onsapup = function(oEvent) {
// prevents actions from occurring when the control is disabled,
// IE11 browser focus non-focusable elements
if (!this.getEnabled()) {
return;
}
// mark the event for components that needs to know if the event was handled
oEvent.setMarked();
// note: prevent document scrolling when arrow keys are pressed
oEvent.preventDefault();
var oPrevSelectableItem,
aSelectableItems = this.getSelectableItems();
oPrevSelectableItem = aSelectableItems[aSelectableItems.indexOf(this.getSelectedItem()) - 1];
fnHandleKeyboardNavigation.call(this, oPrevSelectableItem);
};
/**
* Handles the <code>saphome</code> pseudo event when keyboard Home key is pressed.
* The first selectable item is selected.
*
* @param {jQuery.Event} oEvent The event object.
* @private
*/
Select.prototype.onsaphome = function(oEvent) {
// prevents actions from occurring when the control is disabled,
// IE11 browser focus non-focusable elements
if (!this.getEnabled()) {
return;
}
// mark the event for components that needs to know if the event was handled
oEvent.setMarked();
// note: prevent document scrolling when Home key is pressed
oEvent.preventDefault();
var oFirstSelectableItem = this.getSelectableItems()[0];
fnHandleKeyboardNavigation.call(this, oFirstSelectableItem);
};
/**
* Handles the <code>sapend</code> pseudo event when keyboard End key is pressed.
* The first selectable item is selected.
*
* @param {jQuery.Event} oEvent The event object.
* @private
*/
Select.prototype.onsapend = function(oEvent) {
// prevents actions from occurring when the control is disabled,
// IE11 browser focus non-focusable elements
if (!this.getEnabled()) {
return;
}
// mark the event for components that needs to know if the event was handled
oEvent.setMarked();
// note: prevent document scrolling when End key is pressed
oEvent.preventDefault();
var oLastSelectableItem = this.findLastEnabledItem(this.getSelectableItems());
fnHandleKeyboardNavigation.call(this, oLastSelectableItem);
};
/**
* Handles the <code>sappagedown</code> pseudo event when keyboard page down key is pressed.
*
* @param {jQuery.Event} oEvent The event object.
* @private
*/
Select.prototype.onsappagedown = function(oEvent) {
// prevents actions from occurring when the control is disabled,
// IE11 browser focus non-focusable elements
if (!this.getEnabled()) {
return;
}
// mark the event for components that needs to know if the event was handled
oEvent.setMarked();
// note: prevent document scrolling when page down key is pressed
oEvent.preventDefault();
var aSelectableItems = this.getSelectableItems(),
oSelectedItem = this.getSelectedItem();
this.setSelectedIndex(aSelectableItems.indexOf(oSelectedItem) + 10, aSelectableItems);
oSelectedItem = this.getSelectedItem();
if (oSelectedItem) {
this.setValue(oSelectedItem.getText());
}
this.scrollToItem(oSelectedItem);
};
/**
* Handles the <code>sappageup</code> pseudo event when keyboard page up key is pressed.
*
* @param {jQuery.Event} oEvent The event object.
* @private
*/
Select.prototype.onsappageup = function(oEvent) {
// prevents actions from occurring when the control is disabled,
// IE11 browser focus non-focusable elements
if (!this.getEnabled()) {
return;
}
// mark the event for components that needs to know if the event was handled
oEvent.setMarked();
// note: prevent document scrolling when page up key is pressed
oEvent.preventDefault();
var aSelectableItems = this.getSelectableItems(),
oSelectedItem = this.getSelectedItem();
this.setSelectedIndex(aSelectableItems.indexOf(oSelectedItem) - 10, aSelectableItems);
oSelectedItem = this.getSelectedItem();
if (oSelectedItem) {
this.setValue(oSelectedItem.getText());
}
this.scrollToItem(oSelectedItem);
};
/**
* Handles the <code>tabnext</code> pseudo event when keyboard TAB key is pressed.
*
* @param {jQuery.Event} oEvent The event object.
* @private
*/
Select.prototype.onsaptabnext = function (oEvent) {
// prevents actions from occurring when the control is disabled,
// IE11 browser focus non-focusable elements
if (!this.getEnabled()) {
return;
}
if (this.isOpen()) {
this.close();
}
};
/**
* Handles the <code>tabprevious</code> pseudo event when keyboard SHIFT+TAB keys are pressed.
*
* @param {jQuery.Event} oEvent The event object.
* @private
*/
Select.prototype.onsaptabprevious = Select.prototype.onsaptabnext;
/**
* Handles the <code>focusin</code> event.
*
* @param {jQuery.Event} oEvent The event object.
* @private
*/
Select.prototype.onfocusin = function(oEvent) {
this._bFocused = true;
if (!this._bFocusoutDueRendering && !this._bProcessChange) {
this._oSelectionOnFocus = this.getSelectedItem();
}
this._bProcessChange = true;
// open the value state popup message as long as the dropdown list is closed
setTimeout(function() {
if (!this.isOpen() && this.shouldValueStateMessageBeOpened() && (document.activeElement === this.getFocusDomRef())) {
this.openValueStateMessage();
}
}.bind(this), 100);
// note: in some circumstances IE browsers focus non-focusable elements
if (oEvent.target !== this.getFocusDomRef()) { // whether an inner element is receiving the focus
// force the focus to leave the inner element and set it back to the control's root element
this.focus();
}
};
/**
* Handles the <code>focusout</code> event.
*
* @param {jQuery.Event} oEvent The event object.
* @private
*/
Select.prototype.onfocusout = function(oEvent) {
this._bFocused = false;
this._handleFocusout(oEvent);
if (this.bRenderingPhase) {
return;
}
// close value state message popup when focus is out of the input
this.closeValueStateMessage();
};
/**
* Handles the <code>focusleave</code> pseudo event.
*
* @param {jQuery.Event} oEvent The event object.
* @private
*/
Select.prototype.onsapfocusleave = function(oEvent) {
var oPicker = this.getAggregation("picker");
if (!oEvent.relatedControlId || !oPicker) {
return;
}
var oControl = sap.ui.getCore().byId(oEvent.relatedControlId),
oFocusDomRef = oControl && oControl.getFocusDomRef();
if (Device.system.desktop && containsOrEquals(oPicker.getFocusDomRef(), oFocusDomRef)) {
// force the focus to stay in the input field
this.focus();
}
};
/* =========================================================== */
/* API methods */
/* =========================================================== */
/* ----------------------------------------------------------- */
/* protected methods */
/* ----------------------------------------------------------- */
/**
* Updates and synchronizes <code>selectedItem</code> association, <code>selectedItemId</code> and <code>selectedKey</code> properties.
*
* @param {sap.ui.core.Item | null} vItem
*/
Select.prototype.setSelection = function(vItem) {
var oList = this.getList(),
sKey;
if (oList) {
oList.setSelection(vItem);
}
this.setAssociation("selectedItem", vItem, true);
this.setProperty("selectedItemId", (vItem instanceof Item) ? vItem.getId() : vItem, true);
if (typeof vItem === "string") {
vItem = sap.ui.getCore().byId(vItem);
}
sKey = vItem ? vItem.getKey() : "";
this.setProperty("selectedKey", sKey, true);
this._handleAriaActiveDescendant(vItem);
};
/**
* Determines whether the <code>selectedItem</code> association and <code>selectedKey</code> property are synchronized.
*
* @returns {boolean}
*/
Select.prototype.isSelectionSynchronized = function() {
var vItem = this.getSelectedItem();
return this.getSelectedKey() === (vItem && vItem.getKey());
};
/**
* Synchronizes the <code>selectedItem</code> association and the <code>selectedItemId</code> property.
*
* @param {sap.ui.core.Item} vItem
* @param {string} sKey
* @param {array} [aItems]
*/
Select.prototype.synchronizeSelection = function() {
SelectList.prototype.synchronizeSelection.apply(this, arguments);
};
/**
* This hook method can be used to add additional content.
*
* @param {sap.m.Dialog | sap.m.Popover} [oPicker]
*/
Select.prototype.addContent = function(oPicker) {};
/**
* Creates a picker popup container where the selection should take place.
*
* @param {string} sPickerType The picker type
* @returns {sap.ui.core.Control} The <code>sap.m.Popover</code> or <code>sap.m.Dialog</code> instance
* @protected
*/
Select.prototype.createPicker = function(sPickerType) {
var oPicker = this.getAggregation("picker"),
CSS_CLASS = this.getRenderer().CSS_CLASS;
if (oPicker) {
return oPicker;
}
oPicker = this["_create" + sPickerType]();
// define a parent-child relationship between the control and the picker popup
this.setAggregation("picker", oPicker, true);
// configuration
oPicker.setHorizontalScrolling(false)
.addStyleClass(CSS_CLASS + "Picker")
.addStyleClass(CSS_CLASS + "Picker-CTX")
.attachBeforeOpen(this.onBeforeOpen, this)
.attachAfterOpen(this.onAfterOpen, this)
.attachBeforeClose(this.onBeforeClose, this)
.attachAfterClose(this.onAfterClose, this)
.addEventDelegate({
onBeforeRendering: this.onBeforeRenderingPicker,
onAfterRendering: this.onAfterRenderingPicker
}, this)
.addContent(this.createList());
return oPicker;
};
/**
* Retrieves the next item from the aggregation named <code>items</code>
* whose text match with the given <code>sText</code>.
*
* @param {string} sText
* @returns {sap.ui.core.Item | null}
* @since 1.26.0
*/
Select.prototype.searchNextItemByText = function(sText) {
var aItems = this.getItems(),
iSelectedIndex = this.getSelectedIndex(),
aItemsAfterSelection = aItems.splice(iSelectedIndex + 1, aItems.length - iSelectedIndex),
aItemsBeforeSelection = aItems.splice(0, aItems.length - 1);
aItems = aItemsAfterSelection.concat(aItemsBeforeSelection);
for (var i = 0, oItem; i < aItems.length; i++) {
oItem = aItems[i];
var bTextIsRelevantString = typeof sText === "string" && sText !== "";
if (oItem.getEnabled() && !(oItem instanceof sap.ui.core.SeparatorItem) && oItem.getText().toLowerCase().startsWith(sText.toLowerCase()) && bTextIsRelevantString) {
return oItem;
}
}
return null;
};
/**
* Create an instance type of <code>sap.m.SelectList</code>.
*
* @returns {sap.m.SelectList}
*/
Select.prototype.createList = function() {
var mListKeyboardNavigationMode = SelectListKeyboardNavigationMode,
sKeyboardNavigationMode = Device.system.phone ? mListKeyboardNavigationMode.Delimited : mListKeyboardNavigationMode.None;
this._oList = new SelectList({
width: "100%",
keyboardNavigationMode: sKeyboardNavigationMode
}).addStyleClass(this.getRenderer().CSS_CLASS + "List-CTX")
.addEventDelegate({
ontap: function(oEvent) {
this._checkSelectionChange();
this.close();
}
}, this)
.attachSelectionChange(this.onSelectionChange, this);
return this._oList;
};
/**
* Determines whether the Select has content or not.
*
* @returns {boolean}
*/
Select.prototype.hasContent = function() {
return this.getItems().length > 0;
};
/**
* This event handler is called before the picker popup is rendered.
*
*/
Select.prototype.onBeforeRenderingPicker = function() {
var fnOnBeforeRenderingPickerType = this["_onBeforeRendering" + this.getPickerType()];
fnOnBeforeRenderingPickerType && fnOnBeforeRenderingPickerType.call(this);
};
/**
* This event handler is called after the picker popup is rendered.
*
*/
Select.prototype.onAfterRenderingPicker = function() {
var fnOnAfterRenderingPickerType = this["_onAfterRendering" + this.getPickerType()];
fnOnAfterRenderingPickerType && fnOnAfterRenderingPickerType.call(this);
};
/**
* Open the control's picker popup.
*
* @returns {sap.m.Select} <code>this</code> to allow method chaining.
* @protected
* @since 1.16
*/
Select.prototype.open = function() {
var oPicker = this.getPicker();
if (oPicker) {
oPicker.open();
}
return this;
};
/**
* Toggle the open state of the control's picker popup.
*
* @returns {sap.m.Select} <code>this</code> to allow method chaining.
* @since 1.26
*/
Select.prototype.toggleOpenState = function() {
if (this.isOpen()) {
this.close();
} else {
this.open();
}
return this;
};
/**
* Gets the visible <code>items</code>.
*
* @return {sap.ui.core.Item[]}
* @since 1.22.0
*/
Select.prototype.getVisibleItems = function() {
var oList = this.getList();
return oList ? oList.getVisibleItems() : [];
};
/**
* Indicates whether the provided item is selected.
*
* @param {sap.ui.core.Item} oItem
* @returns {boolean}
* @since 1.24.0
*/
Select.prototype.isItemSelected = function(oItem) {
return oItem && (oItem.getId() === this.getAssociation("selectedItem"));
};
/**
* Retrieves the index of the selected item from the aggregation named <code>items</code>.
*
* @returns {int} An integer specifying the selected index, or -1 if no item is selected.
* @since 1.26.0
*/
Select.prototype.getSelectedIndex = function() {
var oSelectedItem = this.getSelectedItem();
return oSelectedItem ? this.indexOfItem(this.getSelectedItem()) : -1;
};
/**
* Gets the default selected item object from the aggregation named <code>items</code>.
*
* @returns {sap.ui.core.Item | null}
* @since 1.22.0
*/
Select.prototype.getDefaultSelectedItem = function(aItems) {
return this.getForceSelection() ? this.findFirstEnabledItem() : null;
};
/**
* Gets the selectable items from the aggregation named <code>items</code>.
*
* @return {sap.ui.core.Item[]} An array containing the selectable items.
* @since 1.22.0
*/
Select.prototype.