UNPKG

@openui5/sap.m

Version:

OpenUI5 UI Library sap.m

1,510 lines (1,299 loc) 47.2 kB
/*! * OpenUI5 * (c) Copyright 2026 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ sap.ui.define([ './library', 'sap/ui/core/Control', "sap/ui/core/Element", 'sap/ui/core/EnabledPropagator', 'sap/ui/core/IconPool', './delegate/ValueStateMessage', 'sap/ui/core/message/MessageMixin', 'sap/ui/core/InvisibleMessage', 'sap/ui/core/library', 'sap/ui/Device', 'sap/ui/core/Popup', "sap/ui/dom/containsOrEquals", "sap/ui/core/LabelEnablement", './InputBaseRenderer', 'sap/base/Log', "sap/ui/events/KeyCodes", "sap/ui/thirdparty/jquery", "sap/ui/core/Lib", // jQuery Plugin "cursorPos" "sap/ui/dom/jquery/cursorPos", // jQuery Plugin "getSelectedText" "sap/ui/dom/jquery/getSelectedText", // jQuery Plugin "selectText" "sap/ui/dom/jquery/selectText" ], function( library, Control, Element, EnabledPropagator, IconPool, ValueStateMessage, MessageMixin, InvisibleMessage, coreLibrary, Device, Popup, containsOrEquals, LabelEnablement, InputBaseRenderer, log, KeyCodes, jQuery, Library ) { "use strict"; // shortcut for sap.ui.core.TextDirection var TextDirection = coreLibrary.TextDirection; // shortcut for sap.ui.core.TextAlign var TextAlign = coreLibrary.TextAlign; // shortcut for sap.ui.core.ValueState var ValueState = coreLibrary.ValueState; /** * Constructor for a new <code>sap.m.InputBase</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.InputBase</code> control provides a basic functionality for input controls. * * @extends sap.ui.core.Control * @implements sap.ui.core.IFormContent, sap.ui.core.ISemanticFormContent, sap.ui.core.ILabelable * * @borrows sap.ui.core.ISemanticFormContent.getFormFormattedValue as #getFormFormattedValue * @borrows sap.ui.core.ISemanticFormContent.getFormValueProperty as #getFormValueProperty * @borrows sap.ui.core.ISemanticFormContent.getFormObservingProperties as #getFormObservingProperties * @borrows sap.ui.core.ISemanticFormContent.getFormRenderAsControl as #getFormRenderAsControl * @borrows sap.ui.core.ILabelable.hasLabelableHTMLElement as #hasLabelableHTMLElement * * @author SAP SE * @version 1.146.0 * * @constructor * @public * @since 1.12.0 * @alias sap.m.InputBase */ var InputBase = Control.extend("sap.m.InputBase", /** @lends sap.m.InputBase.prototype */ { metadata: { interfaces : [ "sap.ui.core.IFormContent", "sap.ui.core.ISemanticFormContent", "sap.m.IToolbarInteractiveControl", "sap.ui.core.ILabelable" ], library: "sap.m", properties: { /** * Defines the value of the control. */ value: { type: "string", group: "Data", defaultValue: null, bindable: "bindable" }, /** * Defines the width of the control. * * <b>Note:</b> If the provided width is too small, the control gets stretched to * its min width, which is needed in order for the control to be usable and well aligned. */ width: { type: "sap.ui.core.CSSSize", group: "Dimension", defaultValue: null }, /** * Indicates whether the user can interact with the control or not. * <b>Note:</b> Disabled controls cannot be focused and they are out of the tab-chain. */ enabled: { type: "boolean", group: "Behavior", defaultValue: true }, /** * Visualizes the validation state of the control, e.g. <code>Error</code>, <code>Warning</code>, <code>Success</code>. */ valueState: { type: "sap.ui.core.ValueState", group: "Appearance", defaultValue: ValueState.None }, /** * The name to be used in the HTML code (for example, for HTML forms that send data to the server via submission). */ name: { type: "string", group: "Misc", defaultValue: null }, /** * Defines a short hint intended to aid the user with data entry when the control has no value. */ placeholder: { type: "string", group: "Misc", defaultValue: null }, /** * Defines whether the control can be modified by the user or not. * <b>Note:</b> A user can tab to non-editable control, highlight it, and copy the text from it. * @since 1.12.0 */ editable: { type: "boolean", group: "Behavior", defaultValue: true }, /** * Defines the text that appears in the value state message pop-up. If this is not specified, a default text is shown from the resource bundle. * @since 1.26.0 */ valueStateText: { type: "string", group: "Misc", defaultValue: null }, /** * Indicates whether the value state message should be shown or not. * @since 1.26.0 */ showValueStateMessage: { type: "boolean", group: "Misc", defaultValue: true }, /** * Defines the horizontal alignment of the text that is shown inside the input field. * @since 1.26.0 */ textAlign: { type: "sap.ui.core.TextAlign", group: "Appearance", defaultValue: TextAlign.Initial }, /** * Defines the text directionality of the input field, e.g. <code>RTL</code>, <code>LTR</code> * @since 1.28.0 */ textDirection: { type: "sap.ui.core.TextDirection", group: "Appearance", defaultValue: TextDirection.Inherit }, /** * Indicates that user input is required. This property is only needed for accessibility purposes when a single relationship between * the field and a label (see aggregation <code>labelFor</code> of <code>sap.m.Label</code>) cannot be established * (e.g. one label should label multiple fields). * @since 1.38.4 */ required : {type : "boolean", group : "Misc", defaultValue : false} }, associations: { /** * Association to controls / IDs that label this control (see WAI-ARIA attribute aria-labelledby). * @since 1.27.0 */ ariaLabelledBy: { type: "sap.ui.core.Control", multiple: true, singularName: "ariaLabelledBy" }, /** * Association to controls / IDs that describe this control (see WAI-ARIA attribute aria-describedby). * @since 1.90 */ ariaDescribedBy: { type: "sap.ui.core.Control", multiple: true, singularName: "ariaDescribedBy" } }, events: { /** * Is fired when the text in the input field has changed and the focus leaves the input field or the enter key is pressed. */ change: { parameters: { /** * The new <code>value</code> of the <code>control</code>. */ value: { type: "string" } } } }, aggregations: { /** * Defines the formatted text that appears in the value state message pop-up. * It can include links. If both <code>valueStateText</code> and <code>formattedValueStateText</code> * are set - the latter is shown. * @since 1.78 */ formattedValueStateText: { type: "sap.m.FormattedText", multiple: false }, /** * Clone of the <code>formattedValueStateText</code> aggregation created for the accessibility elements used * by screen readers. * @since 1.84 */ _invisibleFormattedValueStateText: { type: "sap.m.FormattedText", multiple: false, visibility: "hidden" }, /** * Icons that will be placed after the input field * @since 1.58 */ _endIcon: { type: "sap.ui.core.Icon", multiple: true, visibility: "hidden" }, /** * Icons that will be placed before the input field * @since 1.58 */ _beginIcon: { type: "sap.ui.core.Icon", multiple: true, visibility: "hidden" } }, designtime: "sap/m/designtime/InputBase.designtime" }, renderer: InputBaseRenderer }); EnabledPropagator.call(InputBase.prototype); IconPool.insertFontFaceStyle(); // apply the message mixin so all message on the input will get the associated label-texts injected MessageMixin.call(InputBase.prototype); // protected constant for pressed state of icons in the input based controls InputBase.ICON_PRESSED_CSS_CLASS = "sapMInputBaseIconPressed"; InputBase.ICON_CSS_CLASS = "sapMInputBaseIcon"; /* =========================================================== */ /* Private methods and properties */ /* =========================================================== */ /* ----------------------------------------------------------- */ /* Private properties */ /* ----------------------------------------------------------- */ /** * Use labels as placeholder configuration. * It can be necessary for the subclasses to overwrite this when * native placeholder usage causes undesired input events or when * placeholder attribute is not supported for the specified type. * https://html.spec.whatwg.org/multipage/forms.html#input-type-attr-summary * * @see sap.m.InputBase#oninput * @type boolean * @protected */ InputBase.prototype.bShowLabelAsPlaceholder = !Device.support.input.placeholder; /* ----------------------------------------------------------- */ /* Private methods */ /* ----------------------------------------------------------- */ /** * To allow setting of default placeholder e.g. in DatePicker * * FIXME: Remove this workaround * What is the difference between _getPlaceholder and getPlaceholder */ InputBase.prototype._getPlaceholder = function() { return this.getPlaceholder() || ""; }; /** * When parameter is set chops the given parameter * * TODO: write two different functions for two different behaviour */ InputBase.prototype._getInputValue = function(sValue) { return (sValue === undefined) ? this.$("inner").val() || "" : sValue.toString(); }; /** * Returns the name of the tag element used for the input. */ InputBase.prototype._getInputElementTagName = function() { if (!this._sInputTagElementName) { this._sInputTagElementName = this._$input && this._$input.get(0) && this._$input.get(0).tagName; } return this._sInputTagElementName; }; /* =========================================================== */ /* Lifecycle methods */ /* =========================================================== */ /* * Initialization hook. * * TODO: respect hungarian notation for variables */ InputBase.prototype.init = function() { // last changed value this.setLastValue(""); /** * Indicates whether the input field is in the rendering phase. * * @type boolean * @protected */ this.bRenderingPhase = false; this._oValueStateMessage = new ValueStateMessage(this); this._aValueStateLinks = []; // handle composition events & validation of composition symbols this._bIsComposingCharacter = false; this.setLastValueStateText(""); this.setErrorMessageAnnouncementState(false); this.fnCloseValueStateOnClick = this.closeValueStateMessage.bind(this); }; InputBase.prototype._oLinkDelegate = { onfocusout: function (oEvent) { var oRelTarget = oEvent.relatedTarget || (oEvent.relatedControlId && Element.getElementById(oEvent.relatedControlId).getDomRef()); const bFocusGoesInInput = oRelTarget && containsOrEquals(this.getDomRef(), oRelTarget); const bFocusGoesToValueState = oRelTarget && containsOrEquals(this._oValueStateMessage.getDomRef(), oRelTarget); if (!bFocusGoesInInput && !bFocusGoesToValueState) { this.closeValueStateMessage(); } }, onsapup: function(oEvent) { // When focus is on the link, arrow keys should do nothing oEvent.preventDefault(); oEvent.stopImmediatePropagation(); }, onsapdown: function(oEvent) { // When focus is on the link, arrow keys should do nothing oEvent.preventDefault(); oEvent.stopImmediatePropagation(); } }; InputBase.prototype._oFirstLinkDelegate = { onsaptabprevious: function(oEvent){ oEvent.preventDefault(); oEvent.stopImmediatePropagation(); this.getFocusDomRef().focus(); } }; InputBase.prototype._oLastLinkDelegate = { onsaptabnext: function(oEvent) { this.onsapfocusleave(oEvent); // Return focus to the input because the links in the value state message are rendered in the // static area and TAB will focus something rendered before the input this._oPreviousFocus && Popup.applyFocusInfo(this._oPreviousFocus); // Value state message should be closed with delay because Popup.applyfocusInfo returns the focus // to the input in order to proceed with focusing the next element in the tab chain on TAB // If closeValueStateMessage is called without a delay, the next control is focused and the popup remains open setTimeout(() => { this.closeValueStateMessage(); }, 0); } }; /** * Called when the composition of a passage of text is started. * * @private */ InputBase.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. * @private */ InputBase.prototype.oncompositionend = function (oEvent) { this._bIsComposingCharacter = false; // In Firefox the events are fired correctly // http://blog.evanyou.me/2014/01/03/composition-event/ if (!Device.browser.firefox) { // dom value updated other than value property this._bCheckDomValue = true; } }; /** * 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 */ InputBase.prototype.isComposingCharacter = function() { return this._bIsComposingCharacter; }; InputBase.prototype.onBeforeRendering = function() { var oFocusDomRef = this.getFocusDomRef(); var oFormattedVSText = this.getFormattedValueStateText(); var bFormattedValueStateUpdated; if (!this._oInvisibleMessage) { this._oInvisibleMessage = InvisibleMessage.getInstance(); } if (this._bCheckDomValue && !this.bRenderingPhase) { // remember dom value in case of invalidation during keystrokes // so the following should only be used onAfterRendering if (this.isActive()) { this._sDomValue = this._getInputValue(); } else { this._bCheckDomValue = false; } } if (!oFormattedVSText) { bFormattedValueStateUpdated = false; } else { var oFormattedVSTextAcc = this.getAggregation("_invisibleFormattedValueStateText"); bFormattedValueStateUpdated = oFormattedVSText.getHtmlText() !== (oFormattedVSTextAcc && oFormattedVSTextAcc.getHtmlText()); } // The value state error should be announced, when there are dynamic changes // to value state error or value state error message, due to user interaction if (this.getValueState() === ValueState.Error && oFocusDomRef) { var bValueStateUpdated = bFormattedValueStateUpdated || this.getValueStateText() !== this.getLastValueStateText(); this.setErrorMessageAnnouncementState(!oFocusDomRef.hasAttribute('aria-invalid') || bValueStateUpdated); } if (bFormattedValueStateUpdated) { oFormattedVSTextAcc && oFormattedVSTextAcc.destroy(); this.setAggregation("_invisibleFormattedValueStateText", oFormattedVSText.clone()); } // mark the rendering phase this.bRenderingPhase = true; }; InputBase.prototype.onAfterRendering = function() { var sValueState = this.getValueState(); var bIsFocused = this.getFocusDomRef() === document.activeElement; var bClosedValueState = sValueState === ValueState.None; var sValueStateMessageHiddenText = document.getElementById(this.getValueStateMessageId() + '-sr'); // maybe control is invalidated on keystrokes and // even the value property did not change // dom value is still the old value // FIXME: This is very ugly to implement this because of the binding if (this._bCheckDomValue && this._sDomValue !== this._getInputValue()) { // so we should keep the dom up-to-date this.$("inner").val(this._sDomValue); } // Announce error value state update, only when the visual focus is in the input field if (this.getErrorMessageAnnouncementState() && this.hasStyleClass("sapMFocus")) { if (sValueStateMessageHiddenText) { const sValueStateMessageHiddenTextLinks = document.getElementById(this.getValueStateLinksShortcutsId()); const sLinksAnnouncement = sValueStateMessageHiddenTextLinks ? sValueStateMessageHiddenTextLinks.textContent : ""; const sTextToAnnounce = `${sValueStateMessageHiddenText.textContent} ${sLinksAnnouncement}`; this._oInvisibleMessage.announce(sTextToAnnounce); } this.setErrorMessageAnnouncementState(false); } // now dom value is up-to-date this._bCheckDomValue = false; // rendering phase is finished this.bRenderingPhase = false; if (bIsFocused) { this[bClosedValueState ? "closeValueStateMessage" : "openValueStateMessage"](); } if (bClosedValueState) { this.closeValueStateMessage(); } if (this.getAggregation("_invisibleFormattedValueStateText")) { this.getAggregation("_invisibleFormattedValueStateText").getControls().forEach(function(oControl){ oControl.getDomRef() && oControl.getDomRef().setAttribute("tabindex", -1); }); } this.setLastValueStateText(this.getValueStateText()); }; InputBase.prototype.exit = function() { if (this._oValueStateMessage) { this._oValueStateMessage.destroy(); } if (this._oInvisibleMessage) { this._oInvisibleMessage.destroy(); this._oInvisibleMessage = null; } this._oValueStateMessage = null; }; /* =========================================================== */ /* Event handlers */ /* =========================================================== */ /** * Handles the touch start event of the Input. * * @param {jQuery.Event} oEvent The event object. * @private */ InputBase.prototype.ontouchstart = function(oEvent) { // mark the event for components that needs to know if the event was handled oEvent.setMarked(); }; /** * Sets up at focus a touch listener on mobile devices. * * @private */ InputBase.prototype.onfocusin = function(oEvent) { this.addStyleClass("sapMFocus"); // open value state message popup when focus is in the input this.openValueStateMessage(); }; /** * Handles the <code>focusout</code> event of the Input. * * @param {jQuery.Event} oEvent The event object. * @private */ InputBase.prototype.onfocusout = function(oEvent) { this.removeStyleClass("sapMFocus"); // Don't close the ValueStateMessage on focusout if it contains sap.m.FormattedText, it can contain links if (!this._bClickOnValueStateLink(oEvent)) { this.closeValueStateMessage(); } }; /** * Handles the <code>sapfocusleave</code> event of the input. * * @param {jQuery.Event} oEvent The event object. */ InputBase.prototype.onsapfocusleave = function(oEvent) { if (!this.preventChangeOnFocusLeave(oEvent)) { this.onChange(oEvent); } }; /** * Hook method to prevent the change event from being fired when the text input field loses focus. * * @param {jQuery.Event} [oEvent] The event object. * @returns {boolean} Whether or not the change event should be prevented. * @protected * @since 1.46 */ InputBase.prototype.preventChangeOnFocusLeave = function(oEvent) { return this.bFocusoutDueRendering; }; /* * Gets the change event additional parameters. * * @returns {object} A map object with the parameters * @protected * @since 1.48 */ InputBase.prototype.getChangeEventParams = function() { return {}; }; /** * Handle when input is tapped. * * @param {jQuery.Event} oEvent The event object. * @private */ InputBase.prototype.ontap = function(oEvent) { if (!this.isMobileDevice()) { this.openValueStateMessage(); } // in order to stay backward compatible - we need to implement the tap return; }; /** * Handles the change event. * * @protected * @param {jQuery.Event} oEvent The event * @param {object} [mParameters] Additional event parameters to be passed in to the change event handler if the * value has changed * @param {string} sNewValue Passed value on change * @returns {boolean|undefined} true when change event is fired */ InputBase.prototype.onChange = function(oEvent, mParameters, sNewValue) { mParameters = mParameters || this.getChangeEventParams(); // check the control is editable or not if (this.getDomRef() && (!this.getEditable() || !this.getEnabled())) { return; } // get the dom value respect to max length if there is no passed value onChange var sValue = this._getInputValue(sNewValue); // compare with the old known value if (sValue !== this.getLastValue()) { // save the value on change this.setValue(sValue); // get the value back maybe formatted sValue = this.getValue(); // remember the last value on change this.setLastValue(sValue); // fire change event this.fireChangeEvent(sValue, mParameters); // inform change detection return true; } else { // same value as before --> ignore Dom update this._bCheckDomValue = false; } }; /** * Fires the change event for the listeners * * @protected * @param {string} sValue value of the input. * @param {object} [oParams] extra event parameters. * @since 1.22.1 */ InputBase.prototype.fireChangeEvent = function(sValue, oParams) { // generate event parameters var oChangeEvent = jQuery.extend({ value : sValue, // backwards compatibility newValue : sValue }, oParams); // fire change event this.fireChange(oChangeEvent); }; /** * Hook method that gets called when the input value is reverted with hitting escape. * It may require to re-implement this method from sub classes for control specific behaviour. * * @protected * @param {string} sValue Reverted value of the input. * @since 1.26 */ InputBase.prototype.onValueRevertedByEscape = function(sValue, sPreviousValue) { // fire private live change event this.fireEvent("liveChange", { value: sValue, //indicate that ESC key is trigger escPressed: true, //the value that was before pressing ESC key previousValue: sPreviousValue, // backwards compatibility newValue: sValue }); }; /** * Indicates whether the control should use <code>sap.m.Dialog</code> or not. * * @returns {boolean} Boolean. * @protected */ InputBase.prototype.isMobileDevice = function () { return Device.system.phone; }; /* ----------------------------------------------------------- */ /* Keyboard handling */ /* ----------------------------------------------------------- */ /** * Handle when enter is pressed. * * @param {jQuery.Event} oEvent The event object. * @private */ InputBase.prototype.onsapenter = function(oEvent) { // Ignore the change event in IE & Safari when value is selected from IME popover via Enter keypress if (Device.browser.safari && this.isComposingCharacter()) { oEvent.setMarked("invalid"); return; } // handle change event on enter this.onChange(oEvent); }; /** * Handle when escape is pressed. * * @param {jQuery.Event} oEvent The event object. * @private */ InputBase.prototype.onsapescape = function(oEvent) { // get the dom value that respect to max length var sValue = this._getInputValue(); // compare last known value and dom value if (sValue !== this.getLastValue()) { // mark the event that it is handled oEvent.setMarked(); oEvent.preventDefault(); // revert to the old dom value this.updateDomValue(this.getLastValue()); // value is reverted, now call the hook to inform this.onValueRevertedByEscape(this.getLastValue(), sValue); } }; // TODO remove after the end of support for Internet Explorer /** * Handle DOM input event. * * This event is fired synchronously when the value of an <code><input></code> or <code><textarea></code> element is changed. * IE9 does not fire an input event when the user removes characters via BACKSPACE / DEL / CUT * InputBase normalize this behaviour for IE9 and calls oninput for the subclasses * * When the input event is buggy the input event is marked as "invalid". * - IE10+ fires the input event when an input field with a native placeholder is focused. * - IE11 fires input event from read-only fields. * - IE11 fires input event after rendering when value contains an accented character * - IE11 fires input event whenever placeholder attribute is changed * * @param {jQuery.Event} oEvent The event object. */ InputBase.prototype.oninput = function(oEvent) { // dom value updated other than value property this._bCheckDomValue = true; }; /** * Handle keydown event. * * @param {jQuery.Event} oEvent The event object. * @private */ InputBase.prototype.onkeydown = function(oEvent) { // Prevents browser back to previous page in IE // TODO remove after the end of support for Internet Explorer if (this.getDomRef("inner") && this.getDomRef("inner").getAttribute("readonly") && oEvent.keyCode == KeyCodes.BACKSPACE) { oEvent.preventDefault(); } }; InputBase.prototype.areHotKeysPressed = function (oEvent) { return (oEvent.ctrlKey || oEvent.metaKey) && oEvent.altKey && oEvent.which === KeyCodes.F8; }; InputBase.prototype._handleValueStateLinkNav = function() { const aLinks = this._getValueStateLinks(); aLinks.length && aLinks[0].focus(); }; /** * Handle cut event. * * @param {jQuery.Event} oEvent The event object. * @private */ InputBase.prototype.oncut = function(oEvent) {}; /* =========================================================== */ /* API methods */ /* =========================================================== */ /* ----------------------------------------------------------- */ /* protected methods */ /* ----------------------------------------------------------- */ /** * Selects the text within the input field between the specified start and end positions. * Only supported for input control's type of Text, Url, Tel and Password. * * @param {int} iSelectionStart The index into the text at which the first selected character is located. * @param {int} iSelectionEnd The index into the text at which the last selected character is located. * @returns {this} <code>this</code> to allow method chaining. * @protected * @since 1.22.1 */ InputBase.prototype.selectText = function(iSelectionStart, iSelectionEnd) { this.$("inner").selectText(iSelectionStart, iSelectionEnd); return this; }; /** * Retrieves the selected text. * Only supported for input control's type of Text, Url, Tel and Password. * * @returns {string} The selected text. * @protected * @since 1.32 */ InputBase.prototype.getSelectedText = function() { return this.$("inner").getSelectedText(); }; /* * Override setProperty function to know value property changes via API * @override */ InputBase.prototype.setProperty = function(sPropertyName, oValue, bSuppressInvalidate) { if (sPropertyName == "value") { // dom value will be updated with value property this._bCheckDomValue = false; } return Control.prototype.setProperty.apply(this, arguments); }; /** * Returns an object representing the serialized focus information. * To be overwritten by subclasses. * * @returns {sap.ui.core.FocusInfo} An object representing the serialized focus information. * @protected */ InputBase.prototype.getFocusInfo = function() { var oFocusInfo = Control.prototype.getFocusInfo.call(this), oFocusDomRef = this.getFocusDomRef(); // extend the serialized focus information with the current text selection and the cursor position jQuery.extend(oFocusInfo, { cursorPos: 0, selectionStart: 0, selectionEnd: 0 }); if (oFocusDomRef) { oFocusInfo.cursorPos = jQuery(oFocusDomRef).cursorPos(); try { oFocusInfo.selectionStart = oFocusDomRef.selectionStart; oFocusInfo.selectionEnd = oFocusDomRef.selectionEnd; } catch (e) { // note: chrome fail to read the "selectionStart" property from HTMLInputElement: The input element's type "number" does not support selection. } } return oFocusInfo; }; /** * Applies the focus info. * To be overwritten by subclasses. * * @param {sap.ui.core.FocusInfo} oFocusInfo An object representing the serialized focus information. * @returns {this} Returns <code>this</code> to allow method chaining * @protected */ InputBase.prototype.applyFocusInfo = function(oFocusInfo) { Control.prototype.applyFocusInfo.call(this, oFocusInfo); this.$("inner").cursorPos(oFocusInfo.cursorPos); this.selectText(oFocusInfo.selectionStart, oFocusInfo.selectionEnd); return this; }; /** * Sets the DOM value of the input field and handles placeholder visibility. * * @param {string} sValue value of the input field. * @return {this} <code>this</code> to allow method chaining. * @since 1.22 * @protected */ InputBase.prototype.updateDomValue = function(sValue) { var oInnerDomRef = this.getFocusDomRef(); if (!this.isActive()) { return this; } // respect to max length sValue = this._getInputValue(sValue); // update the DOM value when necessary // otherwise cursor can goto end of text unnecessarily if (this._getInputValue() === sValue) { return this; } this._bCheckDomValue = true; // if set to true, handle the user input and data // model updates concurrency in order to not overwrite // values coming from the user if (this._getPreferUserInteraction()) { this.handleInputValueConcurrency(sValue); } else { oInnerDomRef.value = sValue; } return this; }; InputBase.prototype._setValueStateLinks = function(aLinks) { if (this.getFormattedValueStateText() && this.getFormattedValueStateText().getHtmlText() && this.getFormattedValueStateText().getControls().length) { this._aValueStateLinks = this.getFormattedValueStateText().getControls(); return; } this._aValueStateLinks = aLinks; }; /** * If there is <code>sap.m.FormattedText</code> aggregation for value state message * return the links in it, if any. * * @returns {sap.m.Link[]|HTMLAnchorElement[]|Array} Links in a value state message containing <code>sap.m.FormattedText</code> * @private */ InputBase.prototype._getValueStateLinks = function() { return this._aValueStateLinks; }; /** * @param {jQuery.Event} oEvent The event object. * @returns {boolean} Whether or not the click is on a <code>sap.m.FormattedText</code> link. * @private */ InputBase.prototype._bClickOnValueStateLink = function(oEvent) { const aValueStateLinks = this._getValueStateLinks(); const oRelTarget = oEvent && oEvent.relatedTarget; // if the links are declared as aggregation of the sap.m.FormattedText if (aValueStateLinks.length) { return aValueStateLinks.some(function(oLink) { return !!oLink.getDomRef && oRelTarget === oLink.getDomRef(); }); } // links can be passed directly to a sap.m.FormattedText control as part of a HTML message (not as an aggregation) if (oRelTarget && oRelTarget.tagName === "A" && oRelTarget.parentElement.classList.contains("sapMFT")) { this._setValueStateLinks([oRelTarget]); this._attachValueStateLinkActions(); return true; } return false; }; /** * If ValueStateText is sap.m.FormattedText containing * link(s) - close ValueStateMessage after press on <code>sap.m.Link</code> * * @private */ InputBase.prototype._attachValueStateLinkActions = function () { const aLinks = this._getValueStateLinks(); aLinks.forEach( function (oLink, nIndex) { if (oLink.attachPress) { oLink.attachPress(this.fnCloseValueStateOnClick, this); oLink.addDelegate(this._oLinkDelegate, this); if (nIndex === 0) { oLink.addDelegate(this._oFirstLinkDelegate, this); } if (nIndex === this._aValueStateLinks.length - 1) { oLink.addDelegate(this._oLastLinkDelegate, this); } } else { oLink.addEventListener("click", this.fnCloseValueStateOnClick); } }, this); }; InputBase.prototype._detachValueStateLinkActions = function () { const aLinks = this._getValueStateLinks(); aLinks.forEach( function (oLink, nIndex) { if (oLink.detachPress) { oLink.detachPress(this.fnCloseValueStateOnClick, this); oLink.removeDelegate(this._oLinkDelegate, this); if (nIndex === 0) { oLink.removeDelegate(this._oFirstLinkDelegate, this); } if (nIndex === this._aValueStateLinks.length - 1) { oLink.removeDelegate(this._oLastLinkDelegate, this); } } }, this); }; /** * Handles value updates coming from the model and those updated by the user, * when the user interaction is the preferred one. * * @param {string} sValue The value to be updated * @private */ InputBase.prototype.handleInputValueConcurrency = function(sValue) { var oInnerDomRef = this.getFocusDomRef(), sInputDOMValue = oInnerDomRef && this._getInputValue(), sInputPropertyValue = this.getProperty("value"), bInputFocused = document.activeElement === oInnerDomRef, bBindingUpdate = this.isBound("value") && this.isPropertyBeingUpdated("value"); // if the user is currently in the field and he has typed a value, // the changes from the model should not overwrite the user input if (bInputFocused && bBindingUpdate && sInputDOMValue && (sInputPropertyValue !== sInputDOMValue)) { return this; } oInnerDomRef.value = sValue; // when the user has focused on an empty input and a value update is // triggered via binding, after updating, the value should be // selected in order to be easily overwritten by the user if (bInputFocused && bBindingUpdate && !sInputDOMValue) { oInnerDomRef.select(); } }; /** * Sets the behavior of the control to prioritize user interaction over later model updates. When set to <code>true</code>, it prevents the model from overwriting user input. * Example: * Input's value property is bound to a model * The user starts typing and due to this action, the model receives update from the backend, thus forwarding it to the bound control property * Result when <code>false</code>: User input is overwritten by the incoming model update. * Result when <code>true</code>: User input is not overwritten by the incoming model update - the model update is skipped and the value remains unchanged. * * @param {boolean} bPrefer True, if the user interaction is preferred * * @public */ InputBase.prototype.setPreferUserInteraction = function(bPrefer) { this._setPreferUserInteraction(bPrefer); }; /** This method is left temporary for backward compatibility. The public setPreferUserInteraction() should be used. * @param {boolean} bPrefer True, if the user interaction is preferred * @private */ InputBase.prototype._setPreferUserInteraction = function(bPrefer) { this._bPreferUserInteraction = bPrefer; }; /** * Gets the preferred interaction. * * @param {boolean} bPrefer True, if the user interaction is preferred * * @private */ InputBase.prototype._getPreferUserInteraction = function() { return this._bPreferUserInteraction; }; /** * Close value state message popup. * * @since 1.26 * @protected */ InputBase.prototype.closeValueStateMessage = function() { // To avoid execution of the opening logic after the closing one, // when closing the suggestions dialog on mobile devices, due to race condition, // the value state message should be closed with timeout because it's opened that way if (Device.system.phone) { setTimeout(function () { if (this._oValueStateMessage){ this._detachValueStateLinkActions(); this._oValueStateMessage.close(); this._oPreviousFocus = null; } }.bind(this), 0); } else if (this._oValueStateMessage) { this._detachValueStateLinkActions(); this._oValueStateMessage.close(); this._oPreviousFocus = null; } }; /** * Gets the DOM element reference where the message popup is attached. * * @returns {Element} The DOM element reference where the message popup is attached * @since 1.26 * @protected */ InputBase.prototype.getDomRefForValueStateMessage = function() { return this.getDomRef("content"); }; /** * Gets the DOM reference the popup should be docked to. * * @return {Element} The DOM reference */ InputBase.prototype.getPopupAnchorDomRef = function() { return this.getDomRef(); }; InputBase.prototype.iOpenMessagePopupDuration = 0; /** * Gets the ID of the value state message. * * @returns {string} The ID of the value state message * @since 1.42 */ InputBase.prototype.getValueStateMessageId = function() { return this.getId() + "-message"; }; /** * Gets the ID of the hidden value state message related to value state links * * @returns {string} The ID of the hidden value state message related to value state links * @protected */ InputBase.prototype.getValueStateLinksShortcutsId = function() { return this.getValueStateMessageId() + "-shortcuts"; }; /** * Returns the keyboard shortcuts announcement for the value state links * * @returns {string} The text for value state links shortcuts * @protected */ InputBase.prototype.getValueStateLinksShortcutsTextAcc = function () { const aLinks = this.getValueStateLinksForAcc(); if (!aLinks.length) { return ""; } let sSingleLinkShortcutKey = "INPUTBASE_VALUE_STATE_LINK"; let sMultipleLinksShortcutKey = "INPUTBASE_VALUE_STATE_LINKS"; if (Device.os.macintosh){ sSingleLinkShortcutKey += "_MAC"; sMultipleLinksShortcutKey += "_MAC"; } return Library.getResourceBundleFor("sap.m").getText(aLinks.length === 1 ? sSingleLinkShortcutKey : sMultipleLinksShortcutKey); }; /** * Gets the state of the value state message announcemnt. * * @returns {boolean} True, if the error value state should be announced. */ InputBase.prototype.getErrorMessageAnnouncementState = function() { return this._bErrorStateShouldBeAnnounced; }; /** * Sets the state of the value state message announcemnt. * * @param {boolean} bAnnounce Determines, if the error value state message should be announced. */ InputBase.prototype.setErrorMessageAnnouncementState = function(bAnnounce) { this._bErrorStateShouldBeAnnounced = bAnnounce; }; /** * Sets the last value state text. * * @param {string} sValueStateText The Last Value State Text to be set */ InputBase.prototype.setLastValueStateText = function(sValueStateText) { this._sLastValueStateText = sValueStateText; }; /** * Gets the last stored value state text. * * @returns {string} The value state text */ InputBase.prototype.getLastValueStateText = function() { return this._sLastValueStateText; }; /** * Gets the labels referencing this control. * * @returns {sap.m.Label[]} Array of objects which are the current targets of the <code>ariaLabelledBy</code> * association and the labels referencing this control. * @since 1.48 * @protected */ InputBase.prototype.getLabels = function() { var aLabelIDs = this.getAriaLabelledBy() .concat(LabelEnablement.getReferencingLabels(this)); aLabelIDs = aLabelIDs.filter(function(sId, iIndex) { return aLabelIDs.indexOf(sId) === iIndex; }) .map(function(sLabelID) { return Element.getElementById(sLabelID); }); return aLabelIDs; }; /** * Open value state message popup. * * @since 1.26 * @protected */ InputBase.prototype.openValueStateMessage = function() { if (this._oValueStateMessage && this.shouldValueStateMessageBeOpened()) { this._oPreviousFocus = Popup.getCurrentFocusInfo(); // Render the value state message after closing of the popover // is complete and the FormattedText aggregation is finished the parent // switch from the ValueStateHeader to the InputBase. // Also if the input receive the focus and the parent div scrolls, // in IE we should wait until the scroll ends setTimeout(function () { if (!this.bIsDestroyed) { this._setValueStateLinks([]); this._attachValueStateLinkActions(); this._oValueStateMessage.open(); } }.bind(this), 0); } }; InputBase.prototype.shouldValueStateMessageBeOpened = function() { return (this.getValueState() !== ValueState.None) && this.getEditable() && this.getEnabled() && this.getShowValueStateMessage(); }; /** * Calculates the space taken by the icons. * * @private * @return {int | null} CSSSize in px */ InputBase.prototype._calculateIconsSpace = function () { var oEndIcon = this.getAggregation("_endIcon") || [], oBeginIcon = this.getAggregation("_beginIcon") || [], aIcons = oEndIcon.concat(oBeginIcon), iIconMargin, iIconWidth; return aIcons.reduce(function(iAcc, oIcon){ iIconMargin = oIcon && oIcon.getDomRef() ? parseFloat(getComputedStyle(oIcon.getDomRef()).marginRight) : 0; iIconWidth = oIcon && oIcon.getDomRef() ? oIcon.getDomRef().offsetWidth : 0; return iAcc + iIconWidth + iIconMargin; }, 0); }; /* ----------------------------------------------------------- */ /* public methods */ /* ----------------------------------------------------------- */ /** * Setter for property <code>value</code>. * * Default value is empty/<code>undefined</code>. * * @param {string} sValue New value for property <code>value</code>. * @return {this} <code>this</code> to allow method chaining. * @public */ InputBase.prototype.setValue = function(sValue) { // validate given value sValue = this.validateProperty("value", sValue); // get the value respect to the max length sValue = this._getInputValue(sValue); // update the dom value when necessary this.updateDomValue(sValue); // check if we need to update the last value because // when setProperty("value") called setValue is called again via binding if (sValue !== this.getProperty("value")) { this.setLastValue(sValue); } // update value property this.setProperty("value", sValue, true); return this; }; InputBase.prototype.getFocusDomRef = function() { return this.getDomRef("inner"); }; InputBase.prototype.getIdForLabel = function() { return this.getId() + "-inner"; }; /** * Returns if the control can be bound to a label * * @returns {boolean} <code>true</code> if the control can be bound to a label * @public */ InputBase.prototype.hasLabelableHTMLElement = function () { return true; }; /** * @see sap.ui.core.Control#getAccessibilityInfo * @returns {sap.ui.core.AccessibilityInfo} The accessibility information for this <code>InputBase</code> * @protected */ InputBase.prototype.getAccessibilityInfo = function() { var oRb = Library.getResourceBundleFor("sap.m"), sRequired = this.getRequired() ? oRb.getText("ELEMENT_REQUIRED") : '', oRenderer = this.getRenderer(); return { role: oRenderer.getAriaRole(this), type: oRb.getText("ACC_CTR_TYPE_INPUT"), description: [this.getValueDescriptionInfo(), oRenderer.getLabelledByAnnouncement(this), oRenderer.getDescribedByAnnouncement(this), sRequired].join(" ").trim(), focusable: this.getEnabled(), enabled: this.getEnabled(), editable: this.getEnabled() && this.getEditable() }; }; /** * Gets the value of the accessibility description info field. * * @protected * @returns {string} The value of the accessibility description info */ InputBase.prototype.getValueDescriptionInfo = function () { return this.getValue() || Library.getResourceBundleFor("sap.m").getText("INPUTBASE_VALUE_EMPTY"); }; /** * Adds an icon to be rendered * @param {string} sIconPosition a position for the icon to be rendered - begin or end * @param {object} oIconSettings settings for creating an icon * @param {int} iPosition position to be inserted in the aggregation * @see sap.ui.core.IconPool.createControlByURI * @private * @returns {null|sap.ui.core.Icon} */ InputBase.prototype._addIcon = function (sIconPosition, oIconSettings, iPosition) { if (["begin", "end"].indexOf(sIconPosition) === -1) { log.error('icon position is not "begin", neither "end", please check again the passed setting'); return null; } var oIcon = IconPool.createControlByURI(oIconSettings).addStyleClass(InputBase.ICON_CSS_CLASS); if (iPosition !== undefined) { this.insertAggregation("_" + sIconPosition + "Icon", oIcon, iPosition); } else { this.addAggregation("_" + sIconPosition + "Icon", oIcon); } return oIcon; }; /** * Adds an icon to the beginning of the input * @param {object} oIconSettings settings for creating an icon * @see sap.ui.core.IconPool.createControlByURI * @protected * @returns {null|sap.ui.core.Icon} */ InputBase.prototype.addBeginIcon = function (oIconSettings) { return this._addIcon("begin", oIconSettings); }; /** * Adds an icon to the end of the input * @param {object} oIconSettings settings for creating an icon * @param {int} iPosition position to be inserted in the aggregation. If not provided, the icon gets inserted on last position. * @see sap.ui.core.IconPool.createControlByURI * @protected * @returns {null|sap.ui.core.Icon} */ InputBase.prototype.addEndIcon = function (oIconSettings, iPosition) { return this._addIcon("end", oIconSettings, iPosition); }; // do not cache jQuery object and define _$input for compatibility reasons Object.defineProperty(InputBase.prototype, "_$input", { get: function() { return this.$("inner"); } }); /** * Sets the last value of the InputBase * * @param {string} sValue * @returns {this} * @since 1.78 * @protected */ InputBase.prototype.setLastValue = function (sValue) { this._lastValue = sValue; return this; }; /** * Gets the last value of the InputBase * * @returns {string} * @since 1.78 * @protected */ InputBase.prototype.getLastValue = function () { return this._lastValue; }; // support for SemanticFormElement InputBase.prototype.getFormFormattedValue = function() { return this.getValue(); }; InputBase.prototype.getFormValueProperty = function () { return "value"; }; InputBase.prototype.getFormObservingProperties = function() { return ["value"]; }; InputBase.prototype.getFormRenderAsControl = function () { return false; }; /** * Required by the {@link sap.m.IToolbarInteractiveControl} interface. * Determines if the Control is interactive. * * @returns {boolean} If it is an interactive Control * * @private * @ui5-restricted sap.m.OverflowToolbar, sap.m.Toolbar */ InputBase.prototype._getToolbarInteractive = function () { return true; }; /** * Hook function * @returns {Array} Array of links for the value state message acc */ InputBase.prototype.getValueStateLinksForAcc = function(){ return []; }; return InputBase; });