UNPKG

@openui5/sap.m

Version:

OpenUI5 UI Library sap.m

1,461 lines (1,245 loc) 43.3 kB
/*! * 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', './ComboBoxTextField', './Toolbar', './Button', './Bar', './Text', './Title', 'sap/ui/core/InvisibleText', 'sap/ui/core/IconPool', 'sap/ui/core/ValueStateSupport', 'sap/base/Log', './library', 'sap/ui/Device', 'sap/ui/core/library', './ComboBoxBaseRenderer', "sap/ui/dom/containsOrEquals", "sap/ui/events/KeyCodes", "sap/ui/thirdparty/jquery", "sap/base/security/encodeXML", "sap/base/strings/escapeRegExp" ], function( Dialog, ComboBoxTextField, Toolbar, Button, Bar, Text, Title, InvisibleText, IconPool, ValueStateSupport, Log, library, Device, coreLibrary, ComboBoxBaseRenderer, containsOrEquals, KeyCodes, jQuery, encodeXML, escapeRegExp ) { "use strict"; // shortcut for sap.m.PlacementType var PlacementType = library.PlacementType; // shortcut for sap.ui.core.ValueState var ValueState = coreLibrary.ValueState; /** * Constructor for a new <code>sap.m.ComboBoxBase</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 * An abstract class for combo boxes. * @extends sap.m.ComboBoxTextField * @abstract * * @author SAP SE * @version 1.60.39 * * @constructor * @public * @since 1.22.0 * @alias sap.m.ComboBoxBase * @ui5-metamodel This control will also be described in the UI5 (legacy) design time meta model. */ var ComboBoxBase = ComboBoxTextField.extend("sap.m.ComboBoxBase", /** @lends sap.m.ComboBoxBase.prototype */ { metadata: { library: "sap.m", "abstract": true, defaultAggregation: "items", properties: { /** * Indicates whether the text values of the <code>additionalText</code> property of a * {@link sap.ui.core.ListItem} are shown. * @since 1.60 */ showSecondaryValues: { type: "boolean", group: "Misc", defaultValue: false } }, aggregations: { /** * Defines the items contained within this control. */ items: { type: "sap.ui.core.Item", multiple: true, singularName: "item", bindable: "bindable" }, /** * Internal aggregation to hold the inner picker popup. */ picker: { type: "sap.ui.core.PopupInterface", multiple: false, visibility: "hidden" } }, events: { /** * This event is fired when the end user clicks the combo box button to open the dropdown list and * the data used to display items is not already loaded. * Alternatively, it is fired after the user moves the cursor to the combo box text * field and perform an action that requires data to be loaded. For example, * pressing F4 to open the dropdown list or typing something in the text field fires the event. * * <b>Note:</b> Use this feature in performance critical scenarios only. * Loading the data lazily (on demand) to defer initialization has several implications for the * end user experience. For example, the busy indicator has to be shown while the items are being * loaded and assistive technology software also has to announce the state changes * (which may be confusing for some screen reader users). * * <b>Note</b>: Currently the <code>sap.m.MultiComboBox</code> does not support this event. * @since 1.38 */ loadItems: {} } } }); /** * Default filtering function for items. * * @param {string} sInputValue Current value of the input field. * @param {sap.ui.core.Item} oItem Item to be matched * @param {string} sPropertyGetter A Getter for property of an item (could be getText or getAdditionalText) * @static * @since 1.58 */ ComboBoxBase.DEFAULT_TEXT_FILTER = function (sInputValue, oItem, sPropertyGetter) { var sLowerCaseText, sInputLowerCaseValue, oMatchingTextRegex; if (!oItem[sPropertyGetter]) { return false; } sLowerCaseText = oItem[sPropertyGetter]().toLowerCase(); sInputLowerCaseValue = sInputValue.toLowerCase(); oMatchingTextRegex = new RegExp('(^|\\s)' + escapeRegExp(sInputLowerCaseValue) + ".*", 'g'); return oMatchingTextRegex.test(sLowerCaseText); }; /** * Called when the composition of a passage of text is started. * * @protected */ ComboBoxBase.prototype.oncompositionstart = function () { this._bIsComposingCharacter = true; }; /** * Called when the composition of a passage of text has been completed or cancelled. * * @param {jQuery.Event} oEvent The event object. * @protected */ ComboBoxBase.prototype.oncompositionend = function (oEvent) { this._bIsComposingCharacter = false; this._sComposition = oEvent.target.value; // In Firefox and Edge the events are fired correctly // http://blog.evanyou.me/2014/01/03/composition-event/ if (!Device.browser.edge && !Device.browser.firefox) { ComboBoxTextField.prototype.handleInput.apply(this, arguments); this.handleInputValidation(oEvent, this.isComposingCharacter()); } }; /** * indicating if a character is currently composing. * * @returns {boolean} Whether or not a character is composing. * True if after "compositionstart" event and before "compositionend" event. * @protected */ ComboBoxBase.prototype.isComposingCharacter = function() { return this._bIsComposingCharacter; }; /* =========================================================== */ /* Private methods */ /* =========================================================== */ /** * Called whenever the binding of the aggregation items is changed. * @param {string} sReason The reason for the update * */ ComboBoxBase.prototype.updateItems = function(sReason) { this.bItemsUpdated = false; // for backward compatibility and to keep the old data binding behavior, // the items should be destroyed before calling .updateAggregation("items") this.destroyItems(); this.updateAggregation("items"); this.bItemsUpdated = true; if (this.hasLoadItemsEventListeners()) { this.onItemsLoaded(); } }; /** * Sets a custom filter function for items. * The function accepts two parameters: * - currenly typed value in the input field * - item to be matched * The function should return a Boolean value (true or false) which represents whether an item will be shown in the dropdown or not. * * @public * @param {function} fnFilter A callback function called when typing in a ComboBoxBase control or ancestor. * @returns {sap.m.ComboBoxBase} <code>this</code> to allow method chaining. * @since 1.58 */ ComboBoxBase.prototype.setFilterFunction = function(fnFilter) { if (fnFilter === null || fnFilter === undefined) { this.fnFilter = null; return this; } if (typeof (fnFilter) !== "function") { Log.warning("Passed filter is not a function and the default implementation will be used"); } else { this.fnFilter = fnFilter; } return this; }; /** * Highlights Dom Refs based on a value of the input and text of an item * * @param {string} sValue Currently typed value of the input * @param {object[]} aItemsDomRefs Array of objects with information for dom ref and text to be highlighted * @param {function} fnBold Method for bolding the text * * @protected * @since 1.58 */ ComboBoxBase.prototype.highLightList = function (sValue, aItemsDomRefs) { var iInitialValueLength = sValue.length, // do not care for any special character sValue = sValue.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'), // find all words that start with a value oRegex = new RegExp("\\b" + sValue, "gi"), $ItemRef; aItemsDomRefs.forEach(function(oItemTextRef) { $ItemRef = jQuery(oItemTextRef.ref); $ItemRef.html(this._boldItemRef.call(this, oItemTextRef.text, oRegex, iInitialValueLength)); }, this); }; /** * Handles bolding of innerHTML of items. * * @param {string} sItemText The item text * @param {RegExp} oRegex A regEx to split the item * @param {string} iInitialValueLength The characters length of the value of the item * * @returns {string} The HTML string * @private * @since 1.58 */ ComboBoxBase.prototype._boldItemRef = function (sItemText, oRegex, iInitialValueLength) { // reset regex last index oRegex.lastIndex = 0; var sResult, oRegexInfo = oRegex.exec(sItemText); if (oRegexInfo === null) { return encodeXML(sItemText); } var iMatchedIndex = oRegexInfo.index; var sTextReplacement = "<b>" + encodeXML(sItemText.slice(iMatchedIndex, iMatchedIndex + iInitialValueLength)) + "</b>"; // parts should always be max of two because regex is not defined as global // see above method var aParts = sItemText.split(oRegex); if (aParts.length === 1) { // no match found, return value as it is sResult = encodeXML(sItemText); } else { sResult = aParts.map(function (sPart) { return encodeXML(sPart); }).join(sTextReplacement); } return sResult; }; /** * 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 */ ComboBoxBase.prototype.refreshItems = function() { this.bItemsUpdated = false; this.refreshAggregation("items"); }; /** * Fires the {@link #loadItems} event if the data used to display items in the dropdown list * is not already loaded and enqueue the <code>fnCallBack</code> and <code>mOptions</code> into a message * queue for processing. * * @param {function} [fnCallBack] A callback function to execute after the items are loaded. * @param {object} [mOptions] Additional options. * @param {string} [mOptions.name] Identifier of the message. * @param {boolean} [mOptions.busyIndicator=true] Indicate whether the loading indicator is shown in the * text field after some delay. * @param {int} [mOptions.busyIndicatorDelay=300] Indicates the delay in milliseconds after which the busy * indicator is shown. * @since 1.32.4 */ ComboBoxBase.prototype.loadItems = function(fnCallBack, mOptions) { var bCallBackIsAFunction = typeof fnCallBack === "function"; // items are not loaded if (this.hasLoadItemsEventListeners() && (this.getItems().length === 0)) { this._bOnItemsLoadedScheduled = false; if (bCallBackIsAFunction) { mOptions = jQuery.extend({ action: fnCallBack, busyIndicator: true, busyIndicatorDelay: 300 }, mOptions); this.aMessageQueue.push(mOptions); // sets up a timeout to know if the data used to display the items in the dropdown list // is loaded after a 300ms delay, to show the busy indicator in the text field, // notice that if the items are loaded before 300ms the timeout is canceled if ((this.iLoadItemsEventInitialProcessingTimeoutID === -1) && // the busy indicator in the input field should not be shown while the user is typing (mOptions.busyIndicator)) { this.iLoadItemsEventInitialProcessingTimeoutID = setTimeout(function onItemsNotLoadedAfterDelay() { this.setInternalBusyIndicatorDelay(0); this.setInternalBusyIndicator(true); }.bind(this), mOptions.busyIndicatorDelay); } } // process the loadItems event only once if (!this.bProcessingLoadItemsEvent) { this.bProcessingLoadItemsEvent = true; // application code must provide the items // in the loadItems event listener this.fireLoadItems(); } // items are already loaded } else if (bCallBackIsAFunction) { // synchronous callback fnCallBack.call(this); } }; ComboBoxBase.prototype.onItemsLoaded = function() { this.bProcessingLoadItemsEvent = false; clearTimeout(this.iLoadItemsEventInitialProcessingTimeoutID); // restore the busy indicator state to its previous state (if it has not been changed), // this is needed to avoid overriding application settings if (this.bInitialBusyIndicatorState !== this.getBusy()) { this.setInternalBusyIndicator(this.bInitialBusyIndicatorState); } // restore the busy indicator delay to its previous state (if it has not been changed), // this is needed to avoid overriding application settings if (this.iInitialBusyIndicatorDelay !== this.getBusyIndicatorDelay()) { this.setInternalBusyIndicatorDelay(this.iInitialBusyIndicatorDelay); } // process the message queue for (var i = 0, mCurrentMessage, mNextMessage, bIsCurrentMessageTheLast; i < this.aMessageQueue.length; i++) { mCurrentMessage = this.aMessageQueue.shift(); // get and delete the first event from the queue i--; bIsCurrentMessageTheLast = (i + 1) === this.aMessageQueue.length; mNextMessage = bIsCurrentMessageTheLast ? null : this.aMessageQueue[i + 1]; if (typeof mCurrentMessage.action === "function") { if ((mCurrentMessage.name === "input") && !bIsCurrentMessageTheLast && (mNextMessage.name === "input")) { // no need to process this input event because the next is pending continue; } mCurrentMessage.action.call(this); } } }; ComboBoxBase.prototype.hasLoadItemsEventListeners = function() { return this.hasListeners("loadItems"); }; ComboBoxBase.prototype._scheduleOnItemsLoadedOnce = function() { if (!this._bOnItemsLoadedScheduled && !this.isBound("items") && this.hasLoadItemsEventListeners() && this.bProcessingLoadItemsEvent) { this._bOnItemsLoadedScheduled = true; setTimeout(this.onItemsLoaded.bind(this), 0); } }; /** * Gets the ID of the hidden label * @returns {string} Id of hidden text * @protected */ ComboBoxBase.prototype.getPickerInvisibleTextId = function() { return InvisibleText.getStaticId("sap.m", "COMBOBOX_AVAILABLE_OPTIONS"); }; /* =========================================================== */ /* Lifecycle methods */ /* =========================================================== */ ComboBoxBase.prototype.init = function() { ComboBoxTextField.prototype.init.apply(this, arguments); // sets the picker popup type this.setPickerType(Device.system.phone ? "Dialog" : "Dropdown"); // initialize composites this.createPicker(this.getPickerType()); // indicate whether the items are updated this.bItemsUpdated = false; // indicates if the picker is opened by the keyboard or by a click/tap on the downward-facing arrow button this.bOpenedByKeyboardOrButton = false; // indicates if the picker should be closed when toggling the opener icon this._bShouldClosePicker = false; this._oPickerValueStateText = null; this.bProcessingLoadItemsEvent = false; this.iLoadItemsEventInitialProcessingTimeoutID = -1; this.aMessageQueue = []; this.bInitialBusyIndicatorState = this.getBusy(); this.iInitialBusyIndicatorDelay = this.getBusyIndicatorDelay(); this._bOnItemsLoadedScheduled = false; this._bDoTypeAhead = true; this.getIcon().addEventDelegate({ onmousedown: function (oEvent) { this._bShouldClosePicker = this.isOpen(); } }, this); this.getIcon().attachPress(function (oEvent) { var oPicker; // in case of a non-editable or disabled combo box, the picker popup cannot be opened if (!this.getEnabled() || !this.getEditable()) { return; } if (this._bShouldClosePicker) { this._bShouldClosePicker = false; this.close(); return; } this.loadItems(); this.bOpenedByKeyboardOrButton = true; if (this.isPlatformTablet()) { oPicker = this.getPicker(); oPicker.setInitialFocus(oPicker); } this.open(); }, this); // handle composition events & validation of composition symbols this._sComposition = ""; // a method to define whether an item should be filtered in the picker this.fnFilter = null; }; ComboBoxBase.prototype.onBeforeRendering = function() { var sNoneState = this.getValueState() === ValueState.None; ComboBoxTextField.prototype.onBeforeRendering.apply(this, arguments); if (!this.isPickerDialog() && sNoneState) { this._showValueStateText(false); } }; ComboBoxBase.prototype.exit = function() { ComboBoxTextField.prototype.exit.apply(this, arguments); if (this.getList()) { this.getList().destroy(); this._oList = null; } if (this._oPickerValueStateText) { this._oPickerValueStateText.destroy(); } clearTimeout(this.iLoadItemsEventInitialProcessingTimeoutID); this.aMessageQueue = null; this.fnFilter = null; }; /* ----------------------------------------------------------- */ /* Keyboard handling */ /* ----------------------------------------------------------- */ /** * Handles the <code>onsapshow</code> event when either F4 is pressed or Alt + Down arrow are pressed. * * @param {jQuery.Event} oEvent The event object. */ ComboBoxBase.prototype.onsapshow = function(oEvent) { // in case of a non-editable or disabled combo box, the picker popup cannot be opened if (!this.getEnabled() || !this.getEditable()) { return; } // mark the event for components that needs to know if the event was handled oEvent.setMarked(); if (oEvent.keyCode === KeyCodes.F4) { this.onF4(oEvent); } if (this.isOpen()) { this.close(); return; } this.selectText(0, this.getValue().length); // select all text this.loadItems(); this.bOpenedByKeyboardOrButton = true; this.open(); }; /** * Handles when the F4 key is pressed. * * @param {jQuery.Event} oEvent The event object. * @since 1.46 */ ComboBoxBase.prototype.onF4 = function(oEvent) { // prevent browser address bar to be open in ie, when F4 is pressed oEvent.preventDefault(); }; /** * Handles when escape is pressed. * * If picker popup is closed, cancels changes and revert to the original value when the input field got its focus. * If list is open, closes list. * * @param {jQuery.Event} oEvent The event object. */ ComboBoxBase.prototype.onsapescape = function(oEvent) { // a non editable or disabled ComboBox, the value cannot be changed if (this.getEnabled() && this.getEditable() && this.isOpen()) { // mark the event for components that needs to know if the event was handled oEvent.setMarked(); // fix for Firefox oEvent.preventDefault(); this.close(); } else { // cancel changes and revert to the value which the Input field had when it got the focus ComboBoxTextField.prototype.onsapescape.apply(this, arguments); } }; /** * Handles when Alt + Up arrow are pressed. * * @param {jQuery.Event} oEvent The event object. */ ComboBoxBase.prototype.onsaphide = ComboBoxBase.prototype.onsapshow; /** * Handles the <code>sapfocusleave</code> event of the input field. * * @param {jQuery.Event} oEvent The event object. */ ComboBoxBase.prototype.onsapfocusleave = function(oEvent) { if (!oEvent.relatedControlId) { ComboBoxTextField.prototype.onsapfocusleave.apply(this, arguments); return; } var oRelatedControl = sap.ui.getCore().byId(oEvent.relatedControlId); // to prevent the change event from firing when the downward-facing arrow button is pressed if (oRelatedControl === this) { return; } var oPicker = this.getAggregation("picker"), oFocusDomRef = oRelatedControl && oRelatedControl.getFocusDomRef(); // to prevent the change event from firing when an item is pressed if (oPicker && containsOrEquals(oPicker.getFocusDomRef(), oFocusDomRef)) { return; } ComboBoxTextField.prototype.onsapfocusleave.apply(this, arguments); }; /* =========================================================== */ /* API methods */ /* =========================================================== */ /** * Gets the DOM reference the popup should be docked to. * * @return {object} The DOM reference */ ComboBoxBase.prototype.getPopupAnchorDomRef = function() { return this.getDomRef(); }; /** * Hook method, can be used to add additional content to the control's picker popup. * * @param {sap.m.Dialog | sap.m.Popover} [oPicker] The picker popup */ ComboBoxBase.prototype.addContent = function(oPicker) {}; /** * Gets the <code>list</code>. * * @returns {sap.m.SelectList} The list instance object or <code>null</code>. * @protected */ ComboBoxBase.prototype.getList = function() { if (this.bIsDestroyed) { return null; } return this._oList; }; /** * Sets the property <code>_sPickerType</code>. * * @param {string} sPickerType The picker type * @protected */ ComboBoxBase.prototype.setPickerType = function(sPickerType) { this._sPickerType = sPickerType; }; /** * Gets the property <code>_sPickerType</code> * * @returns {string} The picker type * @protected */ ComboBoxBase.prototype.getPickerType = function() { return this._sPickerType; }; ComboBoxBase.prototype.setValueState = function(sValueState) { var sAdditionalText, sValueStateText = this.getValueStateText(), bShow = ( sValueState === ValueState.None ? false : this.getShowValueStateMessage()); this._sOldValueState = this.getValueState(); ComboBoxTextField.prototype.setValueState.apply(this, arguments); this._showValueStateText(bShow); if (sValueStateText) { this._setValueStateText(sValueStateText); } else { sAdditionalText = ValueStateSupport.getAdditionalText(this); this._setValueStateText(sAdditionalText); } this._alignValueStateStyles(); return this; }; ComboBoxBase.prototype.setValueStateText = function(sText) { ComboBoxTextField.prototype.setValueStateText.apply(this, arguments); this._setValueStateText(this.getValueStateText()); return this; }; ComboBoxBase.prototype.setShowValueStateMessage = function(bShow) { ComboBoxTextField.prototype.setShowValueStateMessage.apply(this, arguments); this._showValueStateText(this.getShowValueStateMessage()); return this; }; ComboBoxBase.prototype._showValueStateText = function(bShow) { var oCustomHeader; if (this.isPickerDialog()) { if (this._oPickerValueStateText) { this._oPickerValueStateText.setVisible(bShow); } } else { oCustomHeader = this._getPickerCustomHeader(); if (oCustomHeader) { oCustomHeader.setVisible(bShow); } } }; ComboBoxBase.prototype._setValueStateText = function(sText) { var oHeader; if (this.isPickerDialog()) { this._oPickerValueStateText = this.getPickerValueStateText(); this._oPickerValueStateText.setText(sText); } else { oHeader = this._getPickerCustomHeader(); if (oHeader) { oHeader.getContentLeft()[0].setText(sText); } } }; ComboBoxBase.prototype._getPickerCustomHeader = function() { var oInternalTitle, oInternalHeader, oPicker = this.getPicker(), sPickerTitleClass = this.getRenderer().CSS_CLASS_COMBOBOXBASE + "PickerTitle"; if (!oPicker) { return null; } if (oPicker.getCustomHeader()) { return oPicker.getCustomHeader(); } oInternalTitle = new Title({ textAlign: "Left" }).addStyleClass(sPickerTitleClass); oInternalHeader = new Bar({ visible: false, contentLeft: oInternalTitle }); oPicker.setCustomHeader(oInternalHeader); return oInternalHeader; }; ComboBoxBase.prototype._alignValueStateStyles = function() { var sOldValueState = this._sOldValueState, PICKER_CSS_CLASS = this.getRenderer().CSS_CLASS_COMBOBOXBASE + "Picker", sPickerWithState = PICKER_CSS_CLASS + "ValueState", sOldCssClass = PICKER_CSS_CLASS + sOldValueState + "State", sCssClass = PICKER_CSS_CLASS + this.getValueState() + "State", oCustomHeader; if (this.isPickerDialog() && this._oPickerValueStateText) { this._oPickerValueStateText.addStyleClass(sPickerWithState); this._oPickerValueStateText.removeStyleClass(sOldCssClass); this._oPickerValueStateText.addStyleClass(sCssClass); } else { oCustomHeader = this._getPickerCustomHeader(); if (oCustomHeader) { oCustomHeader.addStyleClass(sPickerWithState); oCustomHeader.removeStyleClass(sOldCssClass); oCustomHeader.addStyleClass(sCssClass); } } }; ComboBoxBase.prototype.shouldValueStateMessageBeOpened = function() { var bShouldValueStateMessageBeOpened = ComboBoxTextField.prototype.shouldValueStateMessageBeOpened.apply(this, arguments); return (bShouldValueStateMessageBeOpened && !this.isOpen()); }; ComboBoxBase.prototype.onPropertyChange = function(oControlEvent, oData) { var sNewValue = oControlEvent.getParameter("newValue"), sProperty = oControlEvent.getParameter("name"), sMutator = "set" + sProperty.charAt(0).toUpperCase() + sProperty.slice(1), oControl = (oData && oData.srcControl) || this.getPickerTextField(); // propagate some property changes to the picker text field if (/\bvalue\b|\benabled\b|\bname\b|\bplaceholder\b|\beditable\b|\btextAlign\b|\btextDirection\b|\bvalueState\b/.test(sProperty) && oControl && (typeof oControl[sMutator] === "function")) { oControl[sMutator](sNewValue); } }; /* * Determines if the Picker is a Dialog * * @returns {boolean} * @protected * @since 1.42 */ ComboBoxBase.prototype.isPickerDialog = function() { return this.getPickerType() === "Dialog"; }; /* * Determines if the platform is a tablet. * * @returns {boolean} * @protected * @since 1.48 */ ComboBoxBase.prototype.isPlatformTablet = function() { var bNotCombi = !Device.system.combi, bTablet = Device.system.tablet && bNotCombi; return bTablet; }; /* * Gets the dropdown default settings. * @returns {object} A map object with the default settings * @protected * @since 1.48 */ ComboBoxBase.prototype.getDropdownSettings = function() { return { showArrow: false, placement: PlacementType.VerticalPreferredBottom, offsetX: 0, offsetY: 0, bounce: false, ariaLabelledBy: this.getPickerInvisibleTextId() || undefined }; }; /* * Gets the picker value state message object. * * @returns {sap.m.Text} * @protected * @since 1.46 */ ComboBoxBase.prototype.getPickerValueStateText = function() { var oPicker = this.getPicker(); if (!this._oPickerValueStateText) { this._oPickerValueStateText = new Text({ width: "100%" }); oPicker.insertContent(this._oPickerValueStateText, 0); } return this._oPickerValueStateText; }; /** * Creates a picker popup container where the selection should take place. * To be overwritten by subclasses. * * @param {string} sPickerType The picker type * @protected */ ComboBoxBase.prototype.createPicker = function(sPickerType) {}; /** * This event handler is called before the picker popup is closed. * */ ComboBoxBase.prototype.onBeforeClose = function() { // reset opener this.bOpenedByKeyboardOrButton = false; }; /** * Gets the control's picker popup. * * @returns {sap.m.Dialog | sap.m.Popover | null} The picker instance, creating it if necessary by calling * the <code>createPicker()</code> method. * @protected */ ComboBoxBase.prototype.getPicker = function() { if (this.bIsDestroyed) { return null; } // initialize the control's picker return this.createPicker(this.getPickerType()); }; /** * Gets the control's input from the picker. * * @returns {sap.m.ComboBoxTextField | sap.m.Input | null} Picker's input for filtering the list * @protected * @since 1.42 */ ComboBoxBase.prototype.getPickerTextField = function() { var oPicker = this.getPicker(), oSubHeader = oPicker.getSubHeader(); return oSubHeader && oSubHeader.getContent()[0] || null; }; /* * Gets the picker header title. * * @returns {sap.m.Title | null} The title instance of the Picker * @protected * @since 1.42 */ ComboBoxBase.prototype.getPickerTitle = function() { var oPicker = this.getPicker(), oHeader = oPicker && oPicker.getCustomHeader(); if (this.isPickerDialog() && oHeader) { return oHeader.getContentMiddle()[0]; } return null; }; /** * Creates an instance of <code>sap.m.Dialog</code>. * * @returns {sap.m.Dialog} The created Dialog */ ComboBoxBase.prototype.createDialog = function() { var that = this, oTextField = this.createPickerTextField(), oTextFieldHandleEvent = oTextField._handleEvent; oTextField._handleEvent = function(oEvent) { oTextFieldHandleEvent.apply(this, arguments); if (/keydown|sapdown|sapup|saphome|sapend|sappagedown|sappageup|input/.test(oEvent.type)) { that._handleEvent(oEvent); } }; return new Dialog({ stretch: true, customHeader: that.createPickerHeader(), buttons: this.createPickerCloseButton(), subHeader: new Toolbar({ content: oTextField }), beforeOpen: function() { that.updatePickerHeaderTitle(); }, afterClose: function() { that.focus(); library.closeKeyboard(); }, ariaLabelledBy: that.getPickerInvisibleTextId() || undefined }); }; /** * Creates an instance of <code>sap.m.Bar</code>. * * @returns {sap.m.Bar} Picker's header * @protected * @since 1.42 */ ComboBoxBase.prototype.createPickerHeader = function() { var that = this, sIconURI = IconPool.getIconURI("decline"); return new Bar({ contentMiddle: new Title(), contentRight: new Button({ icon: sIconURI, press: function() { that.close(); that.revertSelection(); } }) }); }; /* * Reverts the selection as before opening the picker * * @type void * @protected * @since 1.42 */ ComboBoxBase.prototype.revertSelection = function() {}; /* * Updates the title of the Picker. If it is labeled the text of the label is assigned as a title, * otherwise a default text is shown. * * @protected * @since 1.42 */ ComboBoxBase.prototype.updatePickerHeaderTitle = function() { var oPicker = this.getPicker(), oResourceBundle = sap.ui.getCore().getLibraryResourceBundle("sap.m"), oLabel, aLabels; if (!oPicker) { return; } aLabels = this.getLabels(); if (aLabels.length) { oLabel = aLabels[0]; if (oLabel && (typeof oLabel.getText === "function")) { this.getPickerTitle().setText(oLabel.getText()); } } else { this.getPickerTitle().setText(oResourceBundle.getText("COMBOBOX_PICKER_TITLE")); } }; /** * Creates an instance of <code>sap.m.Button</code>. * * @returns {sap.m.Button} The created Button * @private * @since 1.42 */ ComboBoxBase.prototype.createPickerCloseButton = function() { var that = this, oTextField, oResourceBundle = sap.ui.getCore().getLibraryResourceBundle("sap.m"); return new Button({ text: oResourceBundle.getText("COMBOBOX_CLOSE_BUTTON"), press: function() { oTextField = that.getPickerTextField(); that.updateDomValue(oTextField.getValue()); that.onChange(); that.close(); } }); }; /** * Determines whether the control has content or not. * * @returns {boolean} True if the control has content * @protected */ ComboBoxBase.prototype.hasContent = function() { return this.getItems().length > 0; }; /** * Retrieves the first enabled item from the aggregation named <code>items</code>. * * @param {array} [aItems] The items array * @returns {sap.ui.core.Item | null} The first enabled item */ ComboBoxBase.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] The items array * @returns {sap.ui.core.Item | null} The last enabled item */ ComboBoxBase.prototype.findLastEnabledItem = function(aItems) { var oList = this.getList(); return oList ? oList.findLastEnabledItem(aItems) : null; }; /** * Opens the control's picker popup. * * @returns {sap.m.ComboBoxBase} <code>this</code> to allow method chaining. * @protected */ ComboBoxBase.prototype.open = function() { var oPicker = this.getPicker(); if (oPicker) { oPicker.open(); } return this; }; /* * Gets the visible items from the aggregation named <code>items</code>. * * @return {sap.ui.core.Item[]} * @protected */ ComboBoxBase.prototype.getVisibleItems = function() { var oList = this.getList(); return oList ? oList.getVisibleItems() : []; }; /* * Checks whether an item is selected or not. * To be overwritten by subclasses. * * @param {sap.ui.core.Item} oItem * @returns {boolean} Whether the item is selected. * @protected * @since 1.24.0 */ ComboBoxBase.prototype.isItemSelected = function() {}; /* * Get key of each item from the aggregation named items. * * @param {sap.ui.core.Item[]} [aItems] * @return {string[]} * @protected * @since 1.24.0 */ ComboBoxBase.prototype.getKeys = function(aItems) { aItems = aItems || this.getItems(); for (var i = 0, aKeys = []; i < aItems.length; i++) { aKeys[i] = aItems[i].getKey(); } return aKeys; }; /** * Gets the selectable items from the aggregation named <code>items</code>. * * @returns {sap.ui.core.Item[]} An array containing the selectables items. */ ComboBoxBase.prototype.getSelectableItems = function() { var oList = this.getList(); return oList ? oList.getSelectableItems() : []; }; /** * Retrieves an item by searching for the given property/value from the aggregation named <code>items</code>. * * <b>Note:</b> If duplicate values exist, the first item matching the value is returned. * * @param {string} sProperty An item property. * @param {string} sValue An item value that specifies the item to be retrieved. * @returns {sap.ui.core.Item | null} The matched item or <code>null</code>. */ ComboBoxBase.prototype.findItem = function(sProperty, sValue) { var oList = this.getList(); return oList ? oList.findItem(sProperty, sValue) : null; }; /* * Gets the item with the given value from the aggregation named <code>items</code>. * * <b>Note:</b> If duplicate values exist, the first item matching the value is returned. * * @param {string} sText An item value that specifies the item to be retrieved. * @returns {sap.ui.core.Item | null} The matched item or <code>null</code>. * @protected */ ComboBoxBase.prototype.getItemByText = function(sText) { return this.findItem("text", sText); }; /** * Scrolls an item into the visual viewport. * @param {object} oItem The item to be scrolled * */ ComboBoxBase.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); } }; /** * Clears the filter. * */ ComboBoxBase.prototype.clearFilter = function() { for (var i = 0, aItems = this.getItems(); i < aItems.length; i++) { aItems[i].bVisible = true; } }; /** * Handles properties' changes of items in the aggregation named <code>items</code>. * To be overwritten by subclasses. * * @experimental * @param {sap.ui.base.Event} oControlEvent The change event * @since 1.30 */ ComboBoxBase.prototype.onItemChange = function(oControlEvent) {}; /** * Clears the selection. * To be overwritten by subclasses. * * @protected */ ComboBoxBase.prototype.clearSelection = function() {}; ComboBoxBase.prototype.setInternalBusyIndicator = function(bBusy) { this.bInitialBusyIndicatorState = this.getBusy(); return this.setBusy.apply(this, arguments); }; ComboBoxBase.prototype.setInternalBusyIndicatorDelay = function(iDelay) { this.iInitialBusyIndicatorDelay = this.getBusyIndicatorDelay(); return this.setBusyIndicatorDelay.apply(this, arguments); }; /* ----------------------------------------------------------- */ /* public methods */ /* ----------------------------------------------------------- */ /** * Adds an item to the aggregation named <code>items</code>. * * @param {sap.ui.core.Item} oItem The item to be added; if empty, nothing is added. * @returns {sap.m.ComboBoxBase} <code>this</code> to allow method chaining. * @public */ ComboBoxBase.prototype.addItem = function(oItem) { this.addAggregation("items", oItem); if (oItem) { oItem.attachEvent("_change", this.onItemChange, this); } this._scheduleOnItemsLoadedOnce(); return this; }; /** * Inserts an item into the aggregation named <code>items</code>. * * @param {sap.ui.core.Item} oItem The item to be inserted; if empty, nothing is inserted. * @param {int} iIndex The <code>0</code>-based index the item should be inserted at; for * a negative value of <code>iIndex</code>, the item is inserted at position 0; for a value * greater than the current size of the aggregation, the item is inserted at the last position. * @returns {sap.m.ComboBoxBase} <code>this</code> to allow method chaining. * @public */ ComboBoxBase.prototype.insertItem = function(oItem, iIndex) { this.insertAggregation("items", oItem, iIndex, true); if (oItem) { oItem.attachEvent("_change", this.onItemChange, this); } this._scheduleOnItemsLoadedOnce(); return this; }; /** * Gets the item from the aggregation named <code>items</code> at the given 0-based index. * * @param {int} iIndex Index of the item to return. * @returns {sap.ui.core.Item} Item at the given index, or null if none. * @public */ ComboBoxBase.prototype.getItemAt = function(iIndex) { return this.getItems()[ +iIndex] || null; }; /** * Gets the first item from the aggregation named <code>items</code>. * * @returns {sap.ui.core.Item} The first item, or null if there are no items. * @public */ ComboBoxBase.prototype.getFirstItem = function() { return this.getItems()[0] || null; }; /** * Gets the last item from the aggregation named <code>items</code>. * * @returns {sap.ui.core.Item} The last item, or null if there are no items. * @public */ ComboBoxBase.prototype.getLastItem = function() { var aItems = this.getItems(); return aItems[aItems.length - 1] || null; }; /** * Gets the enabled items from the aggregation named <code>items</code>. * * @param {sap.ui.core.Item[]} [aItems=getItems()] Items to filter. * @returns {sap.ui.core.Item[]} An array containing the enabled items. * @public */ ComboBoxBase.prototype.getEnabledItems = function(aItems) { var oList = this.getList(); return oList ? oList.getEnabledItems(aItems) : []; }; /** * Gets the item with the given key from the aggregation named <code>items</code>. * * <b>Note:</b> If duplicate keys exist, the first item matching the key is returned. * * @param {string} sKey An item key that specifies the item to retrieve. * @returns {sap.ui.core.Item} The matching item * @public */ ComboBoxBase.prototype.getItemByKey = function(sKey) { var oList = this.getList(); return oList ? oList.getItemByKey(sKey) : null; }; /** * Indicates whether the control's picker popup is open. * * @returns {boolean} Determines whether the control's picker popup is currently open * (this includes opening and closing animations). * @public */ ComboBoxBase.prototype.isOpen = function() { var oPicker = this.getAggregation("picker"); return !!(oPicker && oPicker.isOpen()); }; /** * Closes the control's picker popup. * * @returns {sap.m.ComboBoxBase} <code>this</code> to allow method chaining. * @public */ ComboBoxBase.prototype.close = function() { var oPicker = this.getAggregation("picker"); if (oPicker) { oPicker.close(); } return this; }; /** * Removes an item from the aggregation named <code>items</code>. * * @param {int | string | sap.ui.core.Item} vItem The item to remove or its index or ID. * @returns {sap.ui.core.Item} The removed item or null. * @public */ ComboBoxBase.prototype.removeItem = function(vItem) { vItem = this.removeAggregation("items", vItem); if (vItem) { vItem.detachEvent("_change", this.onItemChange, this); } return vItem; }; /** * Removes all the controls in the aggregation named <code>items</code>. * Additionally unregisters them from the hosting UIArea and clears the selection. * * @returns {sap.ui.core.Item[]} An array of the removed items (might be empty). * @public */ ComboBoxBase.prototype.removeAllItems = function() { var aItems = this.removeAllAggregation("items"); // clear the selection this.clearSelection(); for (var i = 0; i < aItems.length; i++) { aItems[i].detachEvent("_change", this.onItemChange, this); } return aItems; }; /** * Finds the common items of two arrays * @param {sap.ui.core.Item[]} aItems Array of Items * @param {sap.ui.core.Item[]} aOtherItems Second array of items * @protected * @returns {sap.ui.core.Item[]} Array of unique items from both arrays */ ComboBoxBase.prototype.intersectItems = function (aItems, aOtherItems) { return aItems.filter(function (oItem) { return aOtherItems.map(function(oOtherItem) { return oOtherItem.getId(); }).indexOf(oItem.getId()) !== -1; }); }; return ComboBoxBase; });