@openui5/sap.m
Version:
OpenUI5 UI Library sap.m
1,741 lines (1,457 loc) • 96.8 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([
'./InputBase',
'./ComboBoxTextField',
'./ComboBoxBase',
'./Input',
'./Tokenizer',
'./Token',
'./ToggleButton',
'./List',
'./Popover',
'./GroupHeaderListItem',
'./library',
'sap/ui/core/EnabledPropagator',
'sap/ui/core/IconPool',
'sap/ui/core/library',
'sap/ui/Device',
'sap/ui/core/Item',
'sap/ui/core/SeparatorItem',
'sap/ui/core/ResizeHandler',
'./MultiComboBoxRenderer',
"sap/ui/dom/containsOrEquals",
"sap/ui/events/KeyCodes",
"sap/base/util/deepEqual",
"sap/base/assert",
"sap/base/Log",
"sap/ui/thirdparty/jquery",
// jQuery Plugin "cursorPos"
"sap/ui/dom/jquery/cursorPos",
// jQuery Plugin "control"
"sap/ui/dom/jquery/control"
],
function(
InputBase,
ComboBoxTextField,
ComboBoxBase,
Input,
Tokenizer,
Token,
ToggleButton,
List,
Popover,
GroupHeaderListItem,
library,
EnabledPropagator,
IconPool,
coreLibrary,
Device,
Item,
SeparatorItem,
ResizeHandler,
MultiComboBoxRenderer,
containsOrEquals,
KeyCodes,
deepEqual,
assert,
Log,
jQuery
) {
"use strict";
// shortcut for sap.m.ListType
var ListType = library.ListType;
// shortcut for sap.m.ListMode
var ListMode = library.ListMode;
// shortcut for sap.ui.core.ValueState
var ValueState = coreLibrary.ValueState;
// shortcut for sap.ui.core.OpenState
var OpenState = coreLibrary.OpenState;
var PlacementType = library.PlacementType;
/**
* Constructor for a new MultiComboBox.
*
* @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 MultiComboBox control provides a list box with items and a text field allowing the user to either type a value directly into the control or choose from the list of existing items.
*
* A drop-down list for selecting and filtering values.
* <h3>Overview</h3>
* The MultiComboBox control is commonly used to enable users to select one or more options from a predefined list. The control provides an editable input field to filter the list, and a dropdown arrow of available options.
* The select options in the list have checkboxes that permit multi-selection. Entered values are displayed as {@link sap.m.Token tokens}.
* <h3>Structure</h3>
* The MultiComboBox consists of the following elements:
* <ul>
* <li> Input field - displays the selected option/s as token/s. Users can type to filter the list.
* <li> Drop-down arrow - expands\collapses the option list.</li>
* <li> Option list - the list of available options.</li>
* </ul>
* <h3>Usage</h3>
* <h4>When to use:</h4>
* <ul>
* <li>The user needs to select one or more options from a long list of options (maximum of approximately 200).</li>
* </ul>
* <h4>When not to use:</h4>
* <ul>
* <li>The user needs to choose between two options such as ON or OFF and YES or NO. In this case, consider using a {@link sap.m.Switch switch} control instead</li>
* <li>You need to display more that one attribute. In this case, consider using the {@link sap.m.SelectDialog select dialog} or value help dialog instead.</li>
* <li>The user needs to search on multiple attributes. In this case, consider using the {@link sap.m.SelectDialog select dialog} or value help dialog instead.</li>
* <li>Your use case requires all available options to be displayed right away, without any user interaction. In this case, consider using the {@link sap.m.Checkbox checkboxes} instead.</li>
* </ul>
* <h3>Responsive Behavior</h3>
* If there are many tokens, the control shows only the last selected tokens that fit and for the others a label N-more is provided.
* In case the length of the last selected token is exceeding the width of the control, only a label N-Items is shown. In both cases, pressing on the label will show the tokens in a popup.
* <u>On Phones:</u>
* <ul>
* <li>A new full-screen dialog opens where all items from the option list are shown.</li>
* <li>You can select and deselect items from the option list.</li>
* <li>With the help of a toggle button you can switch between showing all tokens and only selected ones.</li>
* <li>You can filter the option list by entering a value in the input.</li>
* </ul>
* <u>On Tablets:</u>
* <ul>
* <li>The auto-complete suggestions appear below or above the input field.</li>
* <li>You can review the tokens by swiping them to left or right.</li>
* </ul>
* <u>On Desktop:</u>
* <ul>
* <li>The auto-complete suggestions appear below or above the input field.</li>
* <li>You can review the tokens by pressing the right or left arrows on the keyboard.</li>
* <li>You can select single tokens or a range of tokens and you can copy/cut/delete them.</li>
* </ul>
*
* @author SAP SE
* @version 1.60.39
*
* @constructor
* @extends sap.m.ComboBoxBase
* @public
* @since 1.22.0
* @alias sap.m.MultiComboBox
* @see {@link fiori:https://experience.sap.com/fiori-design-web/multi-combobox/ Multi-Combo Box}
* @ui5-metamodel This control/element also will be described in the UI5 (legacy) designtime metamodel
*/
var MultiComboBox = ComboBoxBase.extend("sap.m.MultiComboBox", /** @lends sap.m.MultiComboBox.prototype */ { metadata: {
library: "sap.m",
designtime: "sap/m/designtime/MultiComboBox.designtime",
properties: {
/**
* Keys of the selected items. If the key has no corresponding item, no changes will apply. If duplicate keys exists the first item matching the key is used.
*/
selectedKeys: { type: "string[]", group: "Data", defaultValue: [] }
},
associations: {
/**
* Provides getter and setter for the selected items from
* the aggregation named items.
*/
selectedItems: { type: "sap.ui.core.Item", multiple: true, singularName: "selectedItem" }
},
events: {
/**
* Event is fired when selection of an item is changed.
* Note: please do not use the "change" event inherited from sap.m.InputBase
*/
selectionChange: {
parameters: {
/**
* Item which selection is changed
*/
changedItem: { type: "sap.ui.core.Item" },
/**
* Selection state: true if item is selected, false if
* item is not selected
*/
selected: { type: "boolean" }
}
},
/**
* Event is fired when user has finished a selection of items in a list box and list box has been closed.
*/
selectionFinish: {
parameters: {
/**
* The selected items which are selected after list box has been closed.
*/
selectedItems: { type: "sap.ui.core.Item[]" }
}
}
}
}});
IconPool.insertFontFaceStyle();
EnabledPropagator.apply(MultiComboBox.prototype, [true]);
/* ----------------------------------------------------------- */
/* Keyboard handling */
/* ----------------------------------------------------------- */
/**
* Handle End key pressed. Scroll the last token into viewport.
*
* @param {jQuery.Event} oEvent The event object
* @private
*/
MultiComboBox.prototype.onsapend = function(oEvent) {
sap.m.Tokenizer.prototype.onsapend.apply(this._oTokenizer, arguments);
};
/**
* Handle Home key pressed. Scroll the first token into viewport.
*
* @param {jQuery.Event} oEvent The event object
* @private
*/
MultiComboBox.prototype.onsaphome = function(oEvent) {
sap.m.Tokenizer.prototype.onsaphome.apply(this._oTokenizer, arguments);
};
/**
* Handle DOWN arrow key pressed. Set focus to the first list item if the list is open. Otherwise show in input field
* the description of the next traversal item.
*
* @param {jQuery.Event} oEvent The event object
* @private
*/
MultiComboBox.prototype.onsapdown = function(oEvent) {
if (!this.getEnabled() || !this.getEditable()) {
return;
}
// mark the event for components that needs to know if the event was handled
// by this control
oEvent.setMarked();
// note: prevent document scrolling when arrow keys are pressed
oEvent.preventDefault();
// If list is open then go to the first visible list item. Set this item
// into the visual viewport.
// If list is closed...
var aItems = this.getSelectableItems();
var oItem = aItems[0];
if (oItem && this.isOpen()) {
this.getListItem(oItem).focus();
return;
}
if (this._oTokenizer.getSelectedTokens().length) {
return;
}
this._oTraversalItem = this._getNextTraversalItem();
if (this._oTraversalItem) {
this.updateDomValue(this._oTraversalItem.getText());
this.selectText(0, this.getValue().length);
}
};
/**
* Handle UP arrow key pressed. Set focus to input field if first list item has focus. Otherwise show in input field
* description of the previous traversal item.
*
* @param {jQuery.Event} oEvent The event object
* @private
*/
MultiComboBox.prototype.onsapup = function(oEvent) {
if (!this.getEnabled() || !this.getEditable()) {
return;
}
// mark the event for components that needs to know if the event was handled
// by this control
oEvent.setMarked();
// note: prevent document scrolling when arrow keys are pressed
oEvent.preventDefault();
if (this._oTokenizer.getSelectedTokens().length) {
return;
}
this._oTraversalItem = this._getPreviousTraversalItem();
if (this._oTraversalItem) {
this.updateDomValue(this._oTraversalItem.getText());
this.selectText(0, this.getValue().length);
}
};
/**
* Handles the <code>onsapshow</code> event when either F4 is pressed or Alt + Down arrow are pressed.
*
* @param {jQuery.Event} oEvent The event object
*/
MultiComboBox.prototype.onsapshow = function(oEvent) {
var oList = this.getList(),
oPicker = this.getPicker(),
aSelectableItems = this.getSelectableItems(),
aSelectedItems = this.getSelectedItems(),
oItemToFocus, oItemNavigation = oList.getItemNavigation(),
iItemToFocus, oCurrentFocusedControl,
sValue = this.getValue();
oCurrentFocusedControl = jQuery(document.activeElement).control()[0];
if (oCurrentFocusedControl instanceof sap.m.Token) {
oItemToFocus = this._getItemByToken(oCurrentFocusedControl);
} else if (sValue) {
oItemToFocus = this._getItemByValue(sValue);
}
if (!oItemToFocus) {
// we need to take the list's first selected item not the first selected item by the combobox user
oItemToFocus = aSelectedItems.length ? this._getItemByListItem(this.getList().getSelectedItems()[0]) : aSelectableItems[0];
}
iItemToFocus = this.getItems().indexOf(oItemToFocus);
if (oItemNavigation) {
oItemNavigation.setSelectedIndex(iItemToFocus);
} else {
this._bListItemNavigationInvalidated = true;
this._iInitialItemFocus = iItemToFocus;
}
oPicker.setInitialFocus(oList);
ComboBoxBase.prototype.onsapshow.apply(this, arguments);
};
/**
* Handles when Alt + Up arrow are pressed.
*
* @param {jQuery.Event} oEvent The event object.
*/
MultiComboBox.prototype.onsaphide = MultiComboBox.prototype.onsapshow;
/**
* Handles the item selection when user triggers an item selection via key press (TAB, ENTER etc.).
*
* @param {jQuery.Event} oEvent The key event object
* @private
*/
MultiComboBox.prototype._selectItemByKey = function(oEvent) {
var aVisibleItems, oParam,
oItem, i, bItemMatched,
bPickerOpened = this.isOpen();
if (!this.getEnabled() || !this.getEditable()) {
return;
}
// mark the event for components that needs to know if the event was handled
// by this control
if (oEvent) {
oEvent.setMarked();
}
aVisibleItems = this._getUnselectedItems(bPickerOpened ? "" : this.getValue());
for (i = 0; i < aVisibleItems.length; i++) {
if (aVisibleItems[i].getText().toUpperCase() === this.getValue().toUpperCase()) {
oItem = aVisibleItems[i];
bItemMatched = true;
break;
}
}
if (bItemMatched) {
oParam = {
item: oItem,
id: oItem.getId(),
key: oItem.getKey(),
fireChangeEvent: true,
fireFinishEvent: true,
suppressInvalidate: true,
listItemUpdated: false
};
this._bPreventValueRemove = false;
if (this.getValue() === "" || (typeof this.getValue() === "string" && oItem.getText().toLowerCase().startsWith(this.getValue().toLowerCase()))) {
if (this.getListItem(oItem).isSelected()) {
this.setValue('');
} else {
this.setSelection(oParam);
}
}
} else {
this._bPreventValueRemove = true;
this._showWrongValueVisualEffect();
}
if (oEvent) {
this.close();
}
};
/**
* Function calculates the available space for the tokenizer
*
* @private
* @return {String | null} CSSSize in px
*/
MultiComboBox.prototype._calculateSpaceForTokenizer = function () {
if (this.getDomRef()) {
var iWidth = this.getDomRef().offsetWidth,
iArrowButtonWidth = parseInt(this.getDomRef("arrow").offsetWidth, 10),
iInputWidth = parseInt(this.$().find(".sapMInputBaseInner").css("min-width"), 10) || 0,
iInputPadding = parseInt(this.$().find(".sapMInputBaseInner").css("padding-right"), 10) || 0;
return iWidth - (iArrowButtonWidth + iInputWidth + iInputPadding) + "px";
} else {
return null;
}
};
/**
* Handle when enter is pressed.
*
* @param {jQuery.Event} oEvent The event object
* @private
*/
MultiComboBox.prototype.onsapenter = function(oEvent) {
InputBase.prototype.onsapenter.apply(this, arguments);
if (this.getValue()) {
this._selectItemByKey(oEvent);
}
};
/**
* Handles tab key event. Selects an item according to given input if there is exactly one fitting item available.
*
* @param {jQuery.Event} oEvent The event object
* @private
*/
MultiComboBox.prototype.onsaptabnext = function(oEvent) {
var sInputValue = this.getValue();
if (sInputValue) {
var aSelectableItems = this._getUnselectedItemsStartingText(sInputValue);
if (aSelectableItems.length === 1) {
this._selectItemByKey(oEvent);
} else {
this._showWrongValueVisualEffect();
}
}
};
/* =========================================================== */
/* Event handlers */
/* =========================================================== */
/**
* Handle the focus leave event.
*
* @param {jQuery.Event} oEvent The event object
* @private
*/
MultiComboBox.prototype.onsapfocusleave = function(oEvent) {
var oPicker = this.getAggregation("picker"),
bTablet = this.isPlatformTablet(),
oControl = sap.ui.getCore().byId(oEvent.relatedControlId),
oFocusDomRef = oControl && oControl.getFocusDomRef(),
sOldValue = this.getValue();
// If focus target is outside of picker
if (!oPicker || !oPicker.getFocusDomRef() || !oFocusDomRef || !jQuery.contains(oPicker.getFocusDomRef(), oFocusDomRef)) {
this.setValue(null);
// fire change event only if the value of the MCB is not empty
if (sOldValue) {
this.fireChangeEvent("", { value: sOldValue });
}
// If focus is outside of the MultiComboBox
if (!(oControl instanceof Token || oEvent.srcControl instanceof Token)) {
this._oTokenizer.scrollToEnd();
}
// if the focus is outside the MultiComboBox, the tokenizer should be collapsed
if (!jQuery.contains(this.getDomRef(), document.activeElement)) {
this._oTokenizer._useCollapsedMode(true);
}
}
if (oPicker && oFocusDomRef) {
if (deepEqual(oPicker.getFocusDomRef(), oFocusDomRef) && !bTablet && !this.isPickerDialog()) {
// force the focus to stay in the MultiComboBox field when scrollbar
// is moving
this.focus();
}
}
};
/**
* Handle the focus in event.
*
* @param {jQuery.Event} oEvent The event object
* @private
*/
MultiComboBox.prototype.onfocusin = function(oEvent) {
var oPicker = this.getPicker();
var bPreviousFocusInDropdown = false;
var oPickerDomRef = oPicker.getFocusDomRef();
var bPickerClosing = oPicker.oPopup.getOpenState() === OpenState.CLOSING;
var bDropdownPickerType = this.getPickerType() === "Dropdown";
if (bDropdownPickerType) {
bPreviousFocusInDropdown = oPickerDomRef && jQuery.contains(oPickerDomRef, oEvent.relatedTarget);
}
if (this.getEditable()) {
this._oTokenizer._useCollapsedMode(false);
this._oTokenizer.scrollToEnd();
}
if (oEvent.target === this.getFocusDomRef()) {
this.getEditable() && this.addStyleClass("sapMFocus");
// enable type ahead when switching focus from the dropdown to the input field
// we need to check whether the focus has been triggered by the popover's closing or just a manual focusin
// isOpen is still true as the closing has not finished yet.
!bPickerClosing && bPreviousFocusInDropdown && this.handleInputValidation(oEvent, false);
}
if (oEvent.target === this.getOpenArea() && bDropdownPickerType && !this.isPlatformTablet()) {
// avoid the text-editing mode popup to be open on mobile,
// text-editing mode disturbs the usability experience (it blocks the UI in some devices)
// force the focus to stay in the input field
this.focus();
}
// message popup won't open when the item list is shown
if (!this.isOpen() && this.shouldValueStateMessageBeOpened()) {
this.openValueStateMessage();
}
};
/**
* Handle the browser tap event on the List item.
*
* @param {sap.ui.base.Event} oEvent The event object
* @private
*/
MultiComboBox.prototype._handleItemTap = function(oEvent) {
var oTappedControl = jQuery(oEvent.target).control(0);
if (!oTappedControl.isA("sap.m.CheckBox") && !oTappedControl.isA("sap.m.GroupHeaderListItem")) {
this._bCheckBoxClicked = false;
if (this.isOpen() && !this._isListInSuggestMode()) {
this.close();
}
}
};
/**
* Handle the item press event on the List.
*
* @param {sap.ui.base.Event} oEvent The event object
* @private
*/
MultiComboBox.prototype._handleItemPress = function(oEvent) {
// If an item is selected clicking on checkbox inside of suggest list the list with all entries should be opened
if (this.isOpen() && this._isListInSuggestMode() && this.getPicker().oPopup.getOpenState() !== OpenState.CLOSING) {
this.clearFilter();
var oItem = this._getLastSelectedItem();
// Scrolls an item into the visual viewport
if (oItem) {
this.getListItem(oItem).focus();
}
}
};
/**
* Handle the selection change event on the List.
*
* @param {sap.ui.base.Event} oEvent The event object
* @private
*/
MultiComboBox.prototype._handleSelectionLiveChange = function(oEvent) {
var oListItem = oEvent.getParameter("listItem");
var bIsSelected = oEvent.getParameter("selected");
var oNewSelectedItem = this._getItemByListItem(oListItem);
var oInputControl = this.isPickerDialog() ? this.getPickerTextField() : this;
if (oListItem.getType() === "Inactive") {
// workaround: this is needed because the List fires the "selectionChange" event on inactive items
return;
}
// pre-assertion
assert(oNewSelectedItem, "The corresponding mapped item was not found on " + this);
if (!oNewSelectedItem) {
return;
}
var oParam = {
item: oNewSelectedItem,
id: oNewSelectedItem.getId(),
key: oNewSelectedItem.getKey(),
fireChangeEvent: true,
suppressInvalidate: true,
listItemUpdated: true
};
if (bIsSelected) {
// update the selected item
this.fireChangeEvent(oNewSelectedItem.getText());
this.setSelection(oParam);
} else {
this.fireChangeEvent(oNewSelectedItem.getText());
this.removeSelection(oParam);
}
if (this._bCheckBoxClicked) {
oInputControl.setValue(this._sOldInput);
if (this.isOpen() && this.getPicker().oPopup.getOpenState() !== OpenState.CLOSING) {
// workaround: this is needed because the List fires the "selectionChange" event during the popover is closing.
// So clicking on list item description the focus should be replaced to input field. Otherwise the focus is set to
// oListItem.
// Scrolls an item into the visual viewport
oListItem.focus();
}
} else {
this._bCheckBoxClicked = true;
this.setValue("");
this.close();
}
};
/**
* Function is called on key down keyboard input
*
* @private
* @param {jQuery.Event} oEvent The event object
*/
MultiComboBox.prototype.onkeydown = function(oEvent) {
ComboBoxBase.prototype.onkeydown.apply(this, arguments);
if (!this.getEnabled() || !this.getEditable()) {
return;
}
this._bIsPasteEvent = (oEvent.ctrlKey || oEvent.metaKey) && (oEvent.which === KeyCodes.V);
// only if there is no text and tokenizer has some tokens
if (this.getValue().length === 0 && (oEvent.ctrlKey || oEvent.metaKey) && (oEvent.which === KeyCodes.A)
&& this._hasTokens()) {
this._oTokenizer.focus();
this._oTokenizer.selectAllTokens(true);
oEvent.preventDefault();
}
// workaround - keyup is not fired on mobile device
if (this.isPickerDialog()) {
this._sOldValue = this.getPickerTextField().getValue();
this._iOldCursorPos = jQuery(this.getFocusDomRef()).cursorPos();
}
this._bDoTypeAhead = (oEvent.which !== KeyCodes.BACKSPACE) && (oEvent.which !== KeyCodes.DELETE);
};
/**
* Handle the input event on the control's input field.
*
* @param {jQuery.Event} oEvent The event object
* @private
*/
MultiComboBox.prototype.oninput = function(oEvent) {
ComboBoxBase.prototype.oninput.apply(this, arguments);
var oInput = oEvent.srcControl;
if (!this.getEnabled() || !this.getEditable()) {
return;
}
// suppress invalid value
this.handleInputValidation(oEvent, this.isComposingCharacter());
if (this._bIsPasteEvent) {
oInput.updateDomValue(this._sOldValue || oEvent.target.value || "");
return;
}
if (this.isOpen()) {
// wait a tick so the setVisible call has replaced the DOM
setTimeout(this._highlightList.bind(this, this._sOldInput));
}
};
/**
* Filters array of items for given value
*
* @private
*/
MultiComboBox.prototype.filterItems = function (mOptions) {
var fnFilter = this.fnFilter ? this.fnFilter : ComboBoxBase.DEFAULT_TEXT_FILTER;
var aFilteredItems = [];
var bGrouped = false;
var oGroups = [];
mOptions.items.forEach(function(oItem) {
if (oItem.isA("sap.ui.core.SeparatorItem")) {
oGroups.push({
separator: oItem
});
this.getListItem(oItem).setVisible(false);
bGrouped = true;
return;
}
var bMatch = !!fnFilter(mOptions.value, oItem, "getText");
if (mOptions.value === "") {
bMatch = true;
if (!this.bOpenedByKeyboardOrButton && !this.isPickerDialog()) {
// prevent filtering of the picker if it will be closed
return;
}
}
if (bGrouped && bMatch) {
this.getListItem(oGroups[oGroups.length - 1].separator).setVisible(true);
}
var oListItem = this.getListItem(oItem);
if (oListItem) {
oListItem.setVisible(bMatch);
bMatch && aFilteredItems.push(oItem);
}
}, this);
return aFilteredItems;
};
/**
* Function is called on key up keyboard input
*
* @private
* @param {jQuery.Event} oEvent The event object
*/
MultiComboBox.prototype.onkeyup = function(oEvent) {
if (!this.getEnabled() || !this.getEditable()) {
return;
}
this._sOldValue = this.getValue();
this._iOldCursorPos = jQuery(this.getFocusDomRef()).cursorPos();
};
/* ----------------------------------------------------------- */
/* */
/* ----------------------------------------------------------- */
/**
* Triggers the value state "Error" for 1s, and resets the state to the previous one.
*
* @private
*/
MultiComboBox.prototype._showWrongValueVisualEffect = function() {
var sOldValueState = this.getValueState();
if (sOldValueState === ValueState.Error) {
return;
}
if (this.isPickerDialog()) {
this.getPickerTextField().setValueState(ValueState.Error);
setTimeout(this.getPickerTextField()["setValueState"].bind(this.getPickerTextField(), sOldValueState), 1000);
} else {
this.setValueState(ValueState.Error);
setTimeout(this["setValueState"].bind(this, sOldValueState), 1000);
}
this._syncInputWidth(this._oTokenizer);
};
/**
* Returns a modified instance type of <code>sap.m.Popover</code> used in read-only mode.
*
* @returns {sap.m.Popover} The Popover instance
* @private
*/
MultiComboBox.prototype._getReadOnlyPopover = function() {
if (!this._oReadOnlyPopover) {
this._oReadOnlyPopover = this._createReadOnlyPopover();
}
return this._oReadOnlyPopover;
};
/**
* Creates an instance type of <code>sap.m.Popover</code> used in read-only mode.
*
* @returns {sap.m.Popover} The Popover instance
* @private
*/
MultiComboBox.prototype._createReadOnlyPopover = function() {
return new Popover({
showArrow: true,
placement: PlacementType.Auto,
showHeader: false,
contentMinWidth: "auto"
}).addStyleClass("sapMMultiComboBoxReadOnlyPopover");
};
/**
* Creates a picker. To be overwritten by subclasses.
*
* @param {string} sPickerType The picker type
* @returns {sap.m.Popover | sap.m.Dialog} The picker pop-up to be used
* @protected
* @function
*/
MultiComboBox.prototype.createPicker = function(sPickerType) {
var oPicker = this.getAggregation("picker");
if (oPicker) {
return oPicker;
}
oPicker = this["create" + sPickerType]();
// define a parent-child relationship between the control's and the picker pop-up (Popover or Dialog)
this.setAggregation("picker", oPicker, true);
var oRenderer = this.getRenderer(),
CSS_CLASS_MULTICOMBOBOX = oRenderer.CSS_CLASS_MULTICOMBOBOX;
// configuration
oPicker.setHorizontalScrolling(false)
.addStyleClass(oRenderer.CSS_CLASS_COMBOBOXBASE + "Picker")
.addStyleClass(CSS_CLASS_MULTICOMBOBOX + "Picker")
.addStyleClass(CSS_CLASS_MULTICOMBOBOX + "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.getList());
return oPicker;
};
MultiComboBox.prototype.createPickerTextField = function () {
var that = this;
return new Input({
// select a list item, when enter is triggered in picker text field
submit: function (oEvent) {
var sValue = this.getValue();
if (sValue) {
that.setValue(sValue);
that._selectItemByKey();
this.setValue(that._sOldInput);
}
}
}).addEventDelegate({
// remove the type ahead when focus is not in the input
onfocusout: this._handleInputFocusOut
}, this);
};
MultiComboBox.prototype.onBeforeRendering = function() {
ComboBoxBase.prototype.onBeforeRendering.apply(this, arguments);
var aItems = this.getItems();
var oList = this.getList();
if (oList) {
this._synchronizeSelectedItemAndKey(aItems);
// prevent closing of popup on re-rendering
oList.destroyItems();
this._clearTokenizer();
this._fillList(aItems);
// save focused index, and re-apply after rendering of the list
if (oList.getItemNavigation()) {
this._iFocusedIndex = oList.getItemNavigation().getFocusedIndex();
}
// Re-apply editable state to make sure tokens are rendered in right state.
this.setEditable(this.getEditable());
}
this._deregisterResizeHandler();
};
/**
* Registers resize handler
*
* @private
*/
MultiComboBox.prototype._registerResizeHandler = function () {
assert(!this._iResizeHandlerId, "Resize handler already registered");
this._iResizeHandlerId = ResizeHandler.register(this, this._onResize.bind(this));
};
/**
* Deregisters resize handler
*
* @private
*/
MultiComboBox.prototype._deregisterResizeHandler = function () {
if (this._iResizeHandlerId) {
ResizeHandler.deregister(this._iResizeHandlerId);
this._iResizeHandlerId = null;
}
};
/**
* Handler for resizing
*
* @private
*/
MultiComboBox.prototype._onResize = function () {
this._oTokenizer.setMaxWidth(this._calculateSpaceForTokenizer());
this._syncInputWidth(this._oTokenizer);
};
/**
* This hook method is called before the MultiComboBox's Pop-up is rendered.
*
* @protected
*/
MultiComboBox.prototype.onBeforeRenderingPicker = function() {
var fnOnBeforeRenderingPopupType = this["_onBeforeRendering" + this.getPickerType()];
if (fnOnBeforeRenderingPopupType) {
fnOnBeforeRenderingPopupType.call(this);
}
};
/**
* This hook method is called after the MultiComboBox's Pop-up is rendered.
*
* @protected
*/
MultiComboBox.prototype.onAfterRenderingPicker = function() {
var fnOnAfterRenderingPopupType = this["_onAfterRendering" + this.getPickerType()];
if (fnOnAfterRenderingPopupType) {
fnOnAfterRenderingPopupType.call(this);
}
};
/**
* This event handler will be called before the MultiComboBox Popup is opened.
*
* @private
*/
MultiComboBox.prototype.onBeforeOpen = function() {
var fnPickerTypeBeforeOpen = this["_onBeforeOpen" + this.getPickerType()];
// add the active state to the MultiComboBox's field
this.addStyleClass(InputBase.ICON_PRESSED_CSS_CLASS);
this._resetCurrentItem();
this.addContent();
this._aInitiallySelectedItems = this.getSelectedItems();
if (fnPickerTypeBeforeOpen) {
fnPickerTypeBeforeOpen.call(this);
}
};
/**
* This event handler will be called after the MultiComboBox's Pop-up is opened.
*
* @private
*/
MultiComboBox.prototype.onAfterOpen = function() {
var oDomRef = this.getFocusDomRef();
oDomRef && oDomRef.setAttribute("aria-expanded", "true");
// reset the initial focus back to the input
if (!this.isPlatformTablet()) {
this.getPicker().setInitialFocus(this);
}
// close error message when the list is open, otherwise the list can be covered by the message
this.closeValueStateMessage();
};
/**
* This event handler will be called before the MultiComboBox's Pop-up is closed.
*
*/
MultiComboBox.prototype.onBeforeClose = function () {
ComboBoxBase.prototype.onBeforeClose.apply(this, arguments);
};
/**
* This event handler will be called after the MultiComboBox's Pop-up is closed.
*
* @private
*/
MultiComboBox.prototype.onAfterClose = function() {
var bUseCollapsed = !jQuery.contains(this.getDomRef(), document.activeElement) || this.isPickerDialog(),
oDomRef = this.getFocusDomRef();
oDomRef && oDomRef.setAttribute("aria-expanded", "false");
// remove the active state of the MultiComboBox's field
this.removeStyleClass(InputBase.ICON_PRESSED_CSS_CLASS);
// Show all items when the list will be opened next time
this.clearFilter();
// resets or not the value of the input depending on the event (enter does not clear the value)
!this.isComposingCharacter() && !this._bPreventValueRemove && this.setValue("");
this._sOldValue = "";
if (this.isPickerDialog()) {
this.getPickerTextField().setValue("");
this._getFilterSelectedButton() && this._getFilterSelectedButton().setPressed(false);
}
this.fireSelectionFinish({
selectedItems: this.getSelectedItems()
});
this._oTokenizer._useCollapsedMode(bUseCollapsed);
};
/**
* Called before the Dialog is opened.
*
* @private
*/
MultiComboBox.prototype._onBeforeOpenDialog = function() {};
/**
* This event handler will be called before the control's picker popover is opened.
*
*/
MultiComboBox.prototype._onBeforeOpenDropdown = function() {
var oPopover = this.getPicker(),
oDomRef = this.getDomRef(),
sWidth;
if (oDomRef && oPopover) {
sWidth = (oDomRef.offsetWidth / parseFloat(library.BaseFontSize)) + "rem";
oPopover.setContentMinWidth(sWidth);
}
};
/**
* Decorate a Popover instance by adding some private methods.
*
* @param {sap.m.Popover} oPopover The popover to be decorated
* @private
*/
MultiComboBox.prototype._decoratePopover = function(oPopover) {
var that = this;
oPopover.open = function() {
return this.openBy(that);
};
};
/**
* Creates an instance type of <code>sap.m.Popover</code>.
*
* @returns {sap.m.Popover} The Popover instance
* @private
*/
MultiComboBox.prototype.createDropdown = function() {
var oDropdown = new Popover(this.getDropdownSettings());
oDropdown.setInitialFocus(this);
this._decoratePopover(oDropdown);
return oDropdown;
};
MultiComboBox.prototype.createDialog = function () {
var oDialog = ComboBoxBase.prototype.createDialog.apply(this, arguments),
oSelectAllButton = this._createFilterSelectedButton();
oDialog.getSubHeader().addContent(oSelectAllButton);
return oDialog;
};
/**
* Creates an instance of <code>sap.m.ToggleButton</code>.
*
* @returns {sap.m.ToggleButton} The Button instance
* @private
*/
MultiComboBox.prototype._createFilterSelectedButton = function () {
var sIconURI = IconPool.getIconURI("multiselect-all"),
oRenderer = this.getRenderer(),
that = this;
return new ToggleButton({
icon: sIconURI,
press: that._filterSelectedItems.bind(this)
}).addStyleClass(oRenderer.CSS_CLASS_MULTICOMBOBOX + "ToggleButton");
};
MultiComboBox.prototype._getFilterSelectedButton = function () {
return this.getPicker().getSubHeader().getContent()[1];
};
/**
* Filters visible selected items
* @param {jQuery.Event} oEvent The event object
* @returns {void}
* @private
*/
MultiComboBox.prototype._filterSelectedItems = function (oEvent, bForceShowSelected) {
var oSource = oEvent.oSource, oListItem, bMatch,
sValue = this.getPickerTextField() ? this.getPickerTextField().getValue() : "",
bShowSelectedOnly = (oSource && oSource.getPressed && oSource.getPressed()) || bForceShowSelected,
aVisibleItems = this.getVisibleItems(),
aItems = this.getItems(),
aSelectedItems = this.getSelectedItems(),
oLastGroupListItem = null;
if (bShowSelectedOnly) {
aVisibleItems.forEach(function(oItem) {
bMatch = aSelectedItems.indexOf(oItem) > -1 ? true : false;
oListItem = this.getListItem(oItem);
if (!oListItem) {
return;
}
if (oListItem.isA("sap.m.GroupHeaderListItem")) {
oListItem.setVisible(false);
oLastGroupListItem = oListItem;
} else {
oListItem.setVisible(bMatch);
if (bMatch && oLastGroupListItem) {
oLastGroupListItem.setVisible(true);
}
}
}, this);
} else {
this.filterItems({ value: sValue, items: aItems });
}
};
MultiComboBox.prototype.revertSelection = function () {
this.setSelectedItems(this._aInitiallySelectedItems);
};
/**
* Create an instance type of <code>sap.m.List</code>.
*
* @protected
*/
MultiComboBox.prototype.createList = function() {
var oRenderer = this.getRenderer();
// list to use inside the picker
this._oList = new List({
width: "100%",
mode: ListMode.MultiSelect,
includeItemInSelection: true,
rememberSelections: false
}).addStyleClass(oRenderer.CSS_CLASS_COMBOBOXBASE + "List")
.addStyleClass(oRenderer.CSS_CLASS_MULTICOMBOBOX + "List")
.attachBrowserEvent("tap", this._handleItemTap, this)
.attachSelectionChange(this._handleSelectionLiveChange, this)
.attachItemPress(this._handleItemPress, this);
this._oList.addEventDelegate({
onAfterRendering: this.onAfterRenderingList,
onfocusin: this.onFocusinList
}, this);
};
/**
* Update and synchronize "selectedItems" association and the "selectedItems" in the List.
*
* @param {object} mOptions Options object
* @param {sap.ui.core.Item | null} mOptions.item The item instance
* @param {string} mOptions.id The item ID
* @param {string} mOptions.key The item key
* @param {boolean} [mOptions.suppressInvalidate] Whether invalidation should be suppressed
* @param {boolean} [mOptions.listItemUpdated] Whether the item list is updated
* @param {boolean} [mOptions.fireChangeEvent] Whether the change event is fired
* @private
*/
MultiComboBox.prototype.setSelection = function(mOptions) {
if (mOptions.item && this.isItemSelected(mOptions.item)) {
return;
}
if (!mOptions.item) {
return;
}
if (!mOptions.listItemUpdated && this.getListItem(mOptions.item)) {
// set the selected item in the List
this.getList().setSelectedItem(this.getListItem(mOptions.item), true);
}
// Fill Tokenizer
var oToken = new sap.m.Token({
key: mOptions.key
});
oToken.setText(mOptions.item.getText());
oToken.setTooltip(mOptions.item.getText());
mOptions.item.data(this.getRenderer().CSS_CLASS_COMBOBOXBASE + "Token", oToken);
this._oTokenizer.addToken(oToken);
this.$().toggleClass("sapMMultiComboBoxHasToken", this._hasTokens());
this.setValue('');
this.addAssociation("selectedItems", mOptions.item, mOptions.suppressInvalidate);
var aSelectedKeys = this.getKeys(this.getSelectedItems());
this.setProperty("selectedKeys", aSelectedKeys, mOptions.suppressInvalidate);
if (mOptions.fireChangeEvent) {
this.fireSelectionChange({
changedItem: mOptions.item,
selected: true
});
}
if (mOptions.fireFinishEvent) {
// Fire selectionFinish also if tokens are deleted directly in input field
if (!this.isOpen()) {
this.fireSelectionFinish({
selectedItems: this.getSelectedItems()
});
}
}
};
/**
* Remove an item from "selectedItems" association and the "selectedItems" in the List.
*
* @param {object} mOptions Options object
* @param {sap.ui.core.Item | null} mOptions.item The item instance
* @param {string} mOptions.id The item ID
* @param {string} mOptions.key The item key
* @param {boolean} [mOptions.suppressInvalidate] Whether invalidation should be suppressed
* @param {boolean} [mOptions.listItemUpdated] Whether the item list is updated
* @param {boolean} [mOptions.fireChangeEvent] Whether the change event is fired
* @private
*/
MultiComboBox.prototype.removeSelection = function(mOptions) {
if (mOptions.item && !this.isItemSelected(mOptions.item)) {
return;
}
if (!mOptions.item) {
return;
}
this.removeAssociation("selectedItems", mOptions.item, mOptions.suppressInvalidate);
var aSelectedKeys = this.getKeys(this.getSelectedItems());
this.setProperty("selectedKeys", aSelectedKeys, mOptions.suppressInvalidate);
if (!mOptions.listItemUpdated && this.getListItem(mOptions.item)) {
// set the selected item in the List
this.getList().setSelectedItem(this.getListItem(mOptions.item), false);
}
// Synch the Tokenizer
if (!mOptions.tokenUpdated) {
var oToken = this._getTokenByItem(mOptions.item);
mOptions.item.data(this.getRenderer().CSS_CLASS_COMBOBOXBASE + "Token", null);
this._oTokenizer.removeToken(oToken);
}
this.$().toggleClass("sapMMultiComboBoxHasToken", this._hasTokens());
if (mOptions.fireChangeEvent) {
this.fireSelectionChange({
changedItem: mOptions.item,
selected: false
});
}
if (mOptions.fireFinishEvent) {
// Fire selectionFinish also if tokens are deleted directly in input field
if (!this.isOpen()) {
this.fireSelectionFinish({
selectedItems: this.getSelectedItems()
});
}
}
};
/**
* Synchronize selected item and key.
*
* @param {array} [aItems] The items array
* @private
*/
MultiComboBox.prototype._synchronizeSelectedItemAndKey = function(aItems) {
// no items
if (!aItems.length) {
Log.info("Info: _synchronizeSelectedItemAndKey() the MultiComboBox control does not contain any item on ", this);
return;
}
var aSelectedKeys = this.getSelectedKeys() || this._aCustomerKeys;
var aKeyOfSelectedItems = this.getKeys(this.getSelectedItems());
// the "selectedKey" property is not synchronized
if (aSelectedKeys.length) {
for ( var i = 0, sKey = null, oItem = null, iIndex = null, iLength = aSelectedKeys.length; i < iLength; i++) {
sKey = aSelectedKeys[i];
if (aKeyOfSelectedItems.indexOf(sKey) > -1) {
if (this._aCustomerKeys.length && (iIndex = this._aCustomerKeys.indexOf(sKey)) > -1) {
this._aCustomerKeys.splice(iIndex, 1);
}
continue;
}
oItem = this.getItemByKey("" + sKey);
// if the "selectedKey" has no corresponding aggregated item, no
// changes will apply
if (oItem) {
if (this._aCustomerKeys.length && (iIndex = this._aCustomerKeys.indexOf(sKey)) > -1) {
this._aCustomerKeys.splice(iIndex, 1);
}
this.setSelection({
item: oItem,
id: oItem.getId(),
key: oItem.getKey(),
fireChangeEvent: false,
suppressInvalidate: true,
listItemUpdated: false
});
}
}
return;
}
};
// --------------------------- End ------------------------------------
/**
* Get token instance for a specific item
*
* @param {sap.ui.core.Item} oItem The item in question
* @returns {sap.m.Token | null} Token instance, null if not found
* @private
*/
MultiComboBox.prototype._getTokenByItem = function(oItem) {
return oItem ? oItem.data(this.getRenderer().CSS_CLASS_COMBOBOXBASE + "Token") : null;
};
MultiComboBox.prototype.updateItems = function (sReason) {
var bKeyItemSync, aItems,
// Get selected keys should be requested at that point as it
// depends on getSelectedItems()- calls it internally
aKeys = this.getSelectedKeys();
var oUpdateItems = ComboBoxBase.prototype.updateItems.apply(this, arguments);
// It's important to request the selected items after the update,
// because the sync breaks there.
aItems = this.getSelectedItems();
// Check if selected keys and selected items are in sync
bKeyItemSync = (aItems.length === aKeys.length) && aItems.every(function (oItem) {
return oItem && oItem.getKey && aKeys.indexOf(oItem.getKey()) > -1;
});
// Synchronize if sync has been broken by the update
if (!bKeyItemSync) {
aItems = aKeys.map(this.getItemByKey, this);
this.setSelectedItems(aItems);
}
return oUpdateItems;
};
/**
* Get selected items from "aItems".
*
* @param {array | null} aItems Array of sap.ui.core.Item
* @returns {array} The array of selected items
* @private
*/
MultiComboBox.prototype._getSelectedItemsOf = function(aItems) {
for ( var i = 0, iLength = aItems.length, aSelectedItems = []; i < iLength; i++) {
if (this.getListItem(aItems[i]).isSelected()) {
aSelectedItems.push(aItems[i]);
}
}
return aSelectedItems;
};
/**
* Get the last selected item
* @returns {sap.ui.core.Item} The selected item
* @private
*/
MultiComboBox.prototype._getLastSelectedItem = function() {
var aTokens = this._oTokenizer.getTokens();
var oToken = aTokens.length ? aTokens[aTokens.length - 1] : null;
if (!oToken) {
return null;
}
return this._getItemByToken(oToken);
};
/**
* Get the selected items ordered
* @returns {sap.ui.core.Item[]} The ordered list of selected items
* @private
*/
MultiComboBox.prototype._getOrderedSelectedItems = function() {
var aItems = [];
for (var i = 0, aTokens = this._oTokenizer.getTokens(), iLength = aTokens.length; i < iLength; i++) {
aItems[i] = this._getItemByToken(aTokens[i]);
}
return aItems;
};
/**
* Get the focused item from list
* @returns {sap.ui.core.Item} The focused item in the list
* @private
*/
MultiComboBox.prototype._getFocusedListItem = function() {
if (!document.activeElement) {
return null;
}
var oFocusedElement = sap.ui.getCore().byId(document.activeElement.id);
if (this.getList()
&& containsOrEquals(this.getList().getFocusDomRef(), oFocusedElement.getFocusDomRef())) {
return oFocusedElement;
}
return null;
};
/**
* Get the focused item
* @returns {sap.ui.core.Item} The focused item
* @private
*/
MultiComboBox.prototype._getFocusedItem = function() {
var oListItem = this._getFocusedListItem();
return this._getItemByListItem(oListItem);
};
/**
* Tests if an item is in a selected range
* @param {sap.ui.core.Item} oListItem The item
* @returns {boolean} True if the item is in the selected range
* @private
*/
MultiComboBox.prototype._isRangeSelectionSet = function(oListItem) {
var $ListItem = oListItem.getDomRef();
return $ListItem.indexOf(this.getRenderer().CSS_CLASS_MULTICOMBOBOX + "ItemRangeSelection") > -1 ? true : false;
};
/**
* Tests if there are tokens in the combo box
* @returns {boolean} True if there are tokens
* @private
*/
MultiComboBox.prototype._hasTokens = function() {
return this._oTokenizer.getTokens().length > 0;
};
/**
* Gets the current item
* @returns {sap.ui.core.Item} The current item
* @private
*/
MultiComboBox.prototype._getCurrentItem = function() {
if (!this._oCurrentItem) {
return this._getFocusedItem();
}
return this._oCurrentItem;
};
/**
* Sets the current item
* @param {sap.ui.core.Item} oItem The item to be set
* @private
*/
MultiComboBox.prototype._setCurrentItem = function(oItem) {
this._oCurrentItem = oItem;
};
/**
* Resets the current item
* @private
*/
MultiComboBox.prototype._resetCurrentItem = function() {
this._oCurrentItem = null;
};
/**
* Decorate a ListItem instance by adding some delegate methods.
*
* @param {sap.m.StandardListItem} oListItem The item to be decorated
* @private
*/
MultiComboBox.prototype._decorateListItem = function(oListItem) {
oListItem.addDelegate({
onkeyup: function(oEvent) {
var oItem = null;
// If an item is selected with SPACE inside of
// suggest list the list
// with all entries should be opened
if (oEvent.which == KeyCodes.SPACE && this.isOpen() && this._isListInSuggestMode()) {
this.open();
oItem = this._getLastSelectedItem();
// Scrolls an item into the visual viewport
if (oItem) {
this.getListItem(oItem).focus();
}
return;
}
},
onkeydown: function(oEvent) {
var oItem = null, oItemCurrent = null;
if (oEvent.shiftKey && oEvent.which == KeyCodes.ARROW_DOWN) {
oItemCurrent = this._getCurrentItem();
oItem = this._getNextVisibleItemOf(oItemCurrent);
}
if (oEvent.shiftKey && oEvent.which == KeyCodes.ARROW_UP) {
oItemCurrent = this._getCurrentItem();
oItem = this._getPreviousVisibleItemOf(oItemCurrent);
}
if (oEvent.shiftKey && oEvent.which === KeyCodes.SPACE) {
oItemCurrent = this._getCurrentItem();
this._selectPreviousItemsOf(oItemCurrent);
}
if (oItem && oItem !== oItemCurrent) {
if (this.getListItem(oItemCurrent).isSelected()) {
this.setSelection({
item: oItem,
id: oItem.getId(),
key: oItem.getKey(),
fireChangeEvent: true,
suppressInvalidate: true
});
this._setCurrentItem(oItem);
} else {
this.removeSelection({
item: oItem,
id: oItem.getId(),
key: oItem.getKey(),
fireChangeEvent: true,
suppressInvalidate: true
});
this._setCurrentItem(oItem);
}
return;
}
this._resetCurrentItem();
// Handle when CTRL + A is pressed to select all
// Note: at first this function should be called and
// not the
// ListItemBase
if ((oEvent.ctrlKey || oEvent.metaKey) && oEvent.which == KeyCodes.A) {
oEvent.setMarked();
oEvent.preventDefault();
var aVisibleItems = this.getSelectableItems();
var aSelectedItems = this._getSelectedItemsOf(aVisibleItems);
if (aSelectedItems.length !== aVisibleItems.length) {
aVisibleItems.forEach(function(oItem) {
this.setSelection({
item: oItem,
id: oItem.getId(),
key: oItem.getKey(),
fireChangeEvent: true,
suppressInvalidate: true,
listItemUpdated: false
});
}, this);
} else {
aVisibleItems.forEach(function(oItem) {
this.removeSelection({
item: oItem,
id: oItem.getId(),
key: oItem.getKey(),
fireChangeEvent: true,
suppressInvalidate: true,
listItemUpdated: false
});
}, this);
}
}
}
}, true, this);
oListItem.addEventDelegate({
onsapbackspace: function(oEvent) {
// Prevent the backspace key from navigating back
oEvent.preventDefault();
},
onsapshow: function(oEvent) {
// Handle when F4 or Alt + DOWN arrow are pressed.
oEvent.setMarked();
if (this.isOpen()) {
this.close();
return;
}
if (this.hasContent()) {
this.open();
}
},
onsaphide: function(oEvent) {
// Handle when Alt + UP arrow are pressed.
this.onsapshow(oEvent);
},
onsapenter: function(oEvent) {
// Handle when enter is pressed.
oEvent.setMarked();
this.close();
},
onsaphome: function(oEvent) {
// Handle when Pos1 is pressed.
oEvent.setMarked();
// note: prevent document scrolling when Home key is pressed
oEvent.preventDefault();
var aVisibleItems = this.getSelectableItems();
var oItem = aVisibleItems[0];
// Scrolls an item into the visual viewport
this.getListItem(oItem).focus();
},
onsapend: function(oEvent) {
// Handle when End is pressed.
oEvent.setMarked();
// note: prevent document scrolling when End key is pressed
oEvent.preventDefault();
var aVisibleItems = this.getSelectableItems();
var oItem = aVisibleItems[aVisibleItems.length - 1];
// Scrolls an item into the visual viewport
this.getListItem(oItem).focus();
},
onsapup: function(oEvent) {
// Handle when key UP is pressed.
oEvent.setMarked();
// note: prevent document scrolling when arrow keys are pressed
oEvent.preventDefault();
var aVisibleItems = this.getSelectableItems();
var oItemFirst = aVisibleItems[0];
var oItemCurrent = jQuery(document.activeElement).control()[0];
if (oItemCurrent === this.getListItem(oItemFirst)) {
this.focus();
// Stop the propagation of event. Otherwise the l