UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

1,255 lines (1,137 loc) 53.6 kB
/*! * OpenUI5 * (c) Copyright 2026 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ // Provides base class sap.ui.core.Control for all controls sap.ui.define([ './CustomStyleClassSupport', './Core', './Element', './ElementRegistry', './UIArea', './StaticArea', './RenderManager', './BusyIndicatorUtils', './BlockLayerUtils', "sap/base/future", "sap/base/Log", "sap/ui/performance/trace/Interaction", "sap/ui/thirdparty/jquery" ], function( CustomStyleClassSupport, Core, Element, ElementRegistry, UIArea, StaticArea, RenderManager, BusyIndicatorUtils, BlockLayerUtils, future, Log, Interaction, jQuery ) { "use strict"; // soft dependency var ResizeHandler; /** * Creates and initializes a new control with the given <code>sId</code> and settings. * * @param {string} [sId] Optional ID for the new control; generated automatically if no non-empty ID is given * Note: this can be omitted, no matter whether <code>mSettings</code> will be given or not! * @param {object} [mSettings] Object with initial settings for the new control * @public * * @class Base Class for Controls. * * Controls provide the following features: * <ul> * <li><b>Rendering</b>: the <code>RenderManager</code> only expects instances of class <code>Control</code> * in its {@link sap.ui.core.RenderManager#renderControl renderControl} method. * By convention, each control class has an associated static class that takes care of rendering * the control (its 'Renderer').</li> * <li><b>show / hide</b>: a control can be hidden, although it is still part of the control tree, * see property {@link #getVisible visible}</li> * <li><b>local busy indicator</b>: marks a control visually as 'busy', see properties {@link #getBusy busy} * and {@link #getBusyIndicatorDelay busyIndicatorDelay}</li> * <li><b>field groups</b>: by assigning the same group ID to a set of editable controls, they form a * group which can be validated together. See property {@link #getFieldGroupIds fieldGroupIds} * and event {@link #event:validateFieldGroup validateFieldGroup}. * The term <i>field</i> was chosen as most often this feature will be used to group editable * fields in a form.</li> * See the documentation for {@link topic:5b0775397e394b1fb973fa207554003e Field Groups} for more details. * <li><b>custom style classes</b>: all controls allow to add custom CSS classes to their rendered DOM * without modifying their renderer code. See methods {@link #addStyleClass addStyleClass}, * {@link #removeStyleClass removeStyleClass}, {@link #toggleStyleClass toggleStyleClass} * and {@link #hasStyleClass hasStyleClass}.</br> * The necessary implementation is encapsulated in {@link sap.ui.core.CustomStyleClassSupport * CustomStyleClassSupport} and can be applied to selected element classes as well.</li> * <li><b>browser events</b>: by calling the methods {@link #attachBrowserEvent attachBrowserEvent} and * {@link #detachBrowserEvent detachBrowserEvent}, consumers can let the control class take care of * registering / de-registering a given set of event listeners to the control's root DOM node. * The framework will adapt the registration whenever the DOM node changes (e.g. before or after * rendering or when the control is destroyed).</li> * </ul> * * See section "{@link topic:8dcab0011d274051808f959800cabf9f Developing Controls}" * in the documentation for an introduction to control development. * * @extends sap.ui.core.Element * @abstract * @author SAP SE * @version 1.147.0 * @alias sap.ui.core.Control */ var Control = Element.extend("sap.ui.core.Control", /** @lends sap.ui.core.Control.prototype */ { metadata : { stereotype : "control", "abstract" : true, publicMethods: ["placeAt", "attachBrowserEvent", "detachBrowserEvent", "getControlsByFieldGroup", "triggerValidateFieldGroup", "checkFieldGroupIds"], library: "sap.ui.core", properties : { /** * Whether the control is currently in blocked state. * * @deprecated since version 1.69 The blocked property is deprecated. * There is no accessibility support for this property. * Blocked controls should not be used inside Controls, which rely on keyboard navigation, e.g. List controls. */ "blocked" : {type: "boolean", defaultValue: false}, /** * Whether the control is currently in busy state. */ "busy" : {type: "boolean", defaultValue: false}, /** * The delay in milliseconds, after which the busy indicator will show up for this control. */ "busyIndicatorDelay" : {type: "int", defaultValue: 1000}, /** * The size of the BusyIndicator. For controls with a width smaller 3rem a * <code>sap.ui.core.BusyIndicatorSize.Small</code> should be used. * If the size could vary in width and the width could get smaller than 3rem, the * <code>sap.ui.core.BusyIndicatorSize.Auto</code> option could be used. * The default is set to <code>sap.ui.core.BusyIndicatorSize.Medium</code> * For a full screen BusyIndicator use <code>sap.ui.core.BusyIndicatorSize.Large</code>. * @since 1.54 */ "busyIndicatorSize" : {type: "sap.ui.core.BusyIndicatorSize", defaultValue: 'Medium'}, /** * Whether the control should be visible on the screen. * * If set to false, a placeholder will be rendered to mark the location of the invisible * control in the DOM of the current page. The placeholder will be hidden and have * zero dimensions (<code>display: none</code>). * * Also see {@link module:sap/ui/core/InvisibleRenderer InvisibleRenderer}. */ "visible" : { type: "boolean", group : "Appearance", defaultValue: true }, /** * The IDs of a logical field group that this control belongs to. * * All fields in a logical field group should share the same <code>fieldGroupId</code>. * Once a logical field group is left, the <code>validateFieldGroup</code> event is fired. * * For backward compatibility with older releases, field group IDs are syntactically not * limited, but it is suggested to use only valid {@link sap.ui.core.ID}s. * * See {@link #attachValidateFieldGroup} or consult the * {@link topic:5b0775397e394b1fb973fa207554003e Field Group} documentation. * * @since 1.31 */ "fieldGroupIds" : { type: "string[]", defaultValue: [] } }, events : { /** * Event is fired if a logical field group defined by <code>fieldGroupIds</code> of a control was left * or when the user explicitly pressed the key combination that triggers validation. * * By default, the <code>RETURN</code> key without any modifier keys triggers validation, * see {@link #triggerValidateFieldGroup}. * * Listen to this event to validate data of the controls belonging to a field group. * See {@link #setFieldGroupIds}, or consult the * {@link topic:5b0775397e394b1fb973fa207554003e Field Group} documentation. */ validateFieldGroup : { enableEventBubbling:true, parameters : { /** * field group IDs of the logical field groups to validate */ fieldGroupIds : {type : "string[]"} } } } }, constructor : function(sId, mSettings) { // TODO initialization should happen in init // but many of the existing controls don't call super.init() // As a workaround I moved the initialization of bAllowTextSelection here // so that it doesn't overwrite settings in init() (e.g. ListBox) this.bAllowTextSelection = true; Element.apply(this,arguments); this.bOutput = this.getDomRef() != null; // whether this control has already produced output this._bOnBeforeRenderingPhase = false; // whether the control is in the onBeforeRendering phase }, renderer : null // Control has no renderer }); /** * Defines a new subclass of Control with the name <code>sClassName</code> and enriches it with * the information contained in <code>oClassInfo</code>. * * <code>oClassInfo</code> can contain the same information that {@link sap.ui.core.Element.extend} already accepts, * plus the following <code>renderer</code> property: * * Example: * <pre> * Control.extend("sap.mylib.MyControl", { * metadata : { * library : "sap.mylib", * properties : { * text : "string", * width : "sap.ui.core.CSSSize" * } * }, * renderer: { * apiVersion: 2, * render: function(oRM, oControl) { * oRM.openStart("div", oControl); * oRM.style("width", oControl.getWidth()); * oRM.openEnd(); * oRM.text(oControl.getText()); * oRM.close("div"); * } * } * }); * </pre> * * There are multiple ways how a renderer can be specified: * <ul> * <li>As a <b>plain object</b>: The object will be used to create a new renderer by using {@link * sap.ui.core.Renderer.extend} to extend the renderer of the base class of this control. The new renderer * will have the same global name as this control class with the additional suffix 'Renderer'.<br> * <b>Note:</b> The <code>Renderer.extend</code> method expects a plain object (no prototype chain).</li> * <li>As a <b>function</b>: The given function will be used as <code>render</code> function of a new renderer; * the renderer will be created in the same way as described for the <i>plain object</i> case.</li> * <li>As a <b>ready-made renderer</b>, e.g. imported from the corresponding renderer module. As renderers * are simple objects (not instances of a specific class), some heuristic is used to distinguish * renderers from the <i>plain object</i> case above: An object is assumed to be a ready-made renderer * when it has a <code>render</code> function and either is already exposed under the expected global * name or has an <code>extend</code> method.</li> * <li>As a <b>fully qualified name</b>: The name will be looked up as a global property. If not defined, a * module name will be derived from the global name (dots replaced by slashes), the module will be required * and provides the renderer, either as AMD export or via the named global property.</li> * <li><b>Omitting the <code>renderer</code> property</b> or setting it to <code>undefined</code>: * The fully qualified name of the renderer will be derived from the fully qualified name of the control * by adding the suffix "Renderer". The renderer then is retrieved in the same way as described for the * <i>fully qualified name</i> case.</li> * <li><b><code>null</code> or empty string</b>: The control will have no renderer, a call to * <code>oControl.getMetadata().getRenderer()</code> will return <code>undefined</code>.</li> * </ul> * * If the resulting renderer is incomplete (has no <code>render</code> function) or if it cannot be found at all, * rendering of the control will be skipped. * * <b>Note:</b> The <code>apiVersion: 2</code> flag is required to enable in-place rendering technology. * Before setting this property, please ensure that the constraints documented in section "Contract for * Renderer.apiVersion 2" of the {@link sap.ui.core.RenderManager RenderManager} API documentation are * fulfilled. * * @param {string} sClassName Name of the class to be created * @param {object} [oClassInfo] Object literal with information about the class * @param {function} [FNMetaImpl] Constructor function for the metadata object. If not given, it defaults to <code>sap.ui.core.ElementMetadata</code>. * @returns {function} Constructor of the newly created class * * @public * @static * @name sap.ui.core.Control.extend * @function */ /** * Overrides {@link sap.ui.core.Element#clone Element.clone} to clone additional * internal state. * * The additionally cloned information contains: * <ul> * <li>browser event handlers attached with {@link #attachBrowserEvent}</li> * <li>text selection behavior</li> * <li>style classes added with {@link #addStyleClass}</li> * </ul> * * @param {string} [sIdSuffix] a suffix to be appended to the cloned element id * @param {string[]} [aLocalIds] an array of local IDs within the cloned hierarchy (internally used) * @returns {this} reference to the newly created clone * @override * @public */ Control.prototype.clone = function() { var oClone = Element.prototype.clone.apply(this, arguments); if ( this.aBindParameters ) { for (var i = 0, l = this.aBindParameters.length; i < l; i++) { var aParams = this.aBindParameters[i]; oClone.attachBrowserEvent(aParams.sEventType, aParams.fnHandler, aParams.oListener !== this ? aParams.oListener : undefined); } } oClone.bAllowTextSelection = this.bAllowTextSelection; return oClone; }; // must appear after clone() method and metamodel definition CustomStyleClassSupport.apply(Control.prototype); /** * Checks whether the control is still active (part of the active DOM) * * @return {boolean} whether the control is still in the active DOM * @private */ Control.prototype.isActive = function() { return document.getElementById(this.sId) != null; }; /** * Determines whether the control is in rendering phase. * * @returns {boolean} * @private */ function isInRenderingPhase(oControl) { if (!oControl || !oControl.isA) { return false; } if (oControl.isA("sap.ui.core.Control")) { return oControl._bRenderingPhase; } return isInRenderingPhase(oControl.getParent()); } /** * Marks this control and its children for a re-rendering, usually because its state has changed and now differs * from the rendered DOM. * * Managed settings (properties, aggregations, associations) automatically invalidate the corresponding object. * Changing the state via the standard mutators, therefore, does not require an explicit call to <code>invalidate</code>. * * By default, all invalidations are buffered and processed together (asynchronously) in a new browser task. * * The <code>oOrigin</code> parameter was introduced to allow parent controls to limit their re-rendering to * certain areas that have been invalidated by their children. As there is no strong guideline for control * developers whether or not to provide the parameter, it is not a reliable source of information. It is, * therefore, not recommended in general to use it, only in scenarios where a control and its descendants * know each other very well (e.g. complex controls where parent and children have the same code owner). * * @param {sap.ui.base.ManagedObject} [oOrigin] Child control for which the method was called * @protected */ Control.prototype.invalidate = function(oOrigin) { var oUIArea; // Invalidations that happen in the onBeforeRendering hook of controls can be ignored // since the rendering of the control is about to start. if ( this._bOnBeforeRenderingPhase ) { return; } // Mark the control that it is in a dirty state and requires rendering. // This flag will be used by the RenderManager to determine whether the rendering // is necessary for the child controls while they are getting rendered with their parents. // This will be cleared by the RenderManager when the control is rendered completely. this._bNeedsRendering = true; var oParent = this.getParent(); if ( (this.bOutput || isInRenderingPhase(oParent)) && (oUIArea = this.getUIArea()) ) { // if this control has been rendered before (bOutput) // or if the invalidation happens while parent rendering (isInRenderingPhase(oParent)) // and if it is contained in a UIArea (!!oUIArea) // then control re-rendering can be used (see UIArea.rerender() for details) // // The check for bOutput is necessary as the control // re-rendering needs to identify the previous rendering results. // Otherwise it wouldn't be able to replace them. // // Note about destroy(): when this control is currently in the process of being // destroyed, registering it for an autonomous re-rendering doesn't make sense. // In most cases, invalidation of the parent also doesn't make sense, // but there might be composite controls that rely on being invalidated when // a child is destroyed, so we keep the invalidation propagation untouched. if ( !this._bIsBeingDestroyed ) { oUIArea.addInvalidatedControl(this); } } else { // else we bubble up the hierarchy if (oParent && !oParent.isInvalidateSuppressed() && ( this.bOutput /* && !this.getUIArea() */ || /* !this.bOutput && */ !(this.getVisible && this.getVisible() === false))) { // Note: the two comments in the condition above show additional conditions // that help to understand the logic. As they are always fulfilled, // they have been omitted for better performance. // // If this control has a parent but either // - has produced output before ('this.bOutput') but is not part of a UIArea (!this.getUIArea()) // - or if it didn't produce output (!this.bOutput') before and is/became visible // then invalidate the parent to request re-rendering // // The first commented condition is always true, otherwise the initial if condition // in this method would have been met. The second one must be true as well because of the // short evaluation logic of JavaScript. When bOutput is true the second half of the Or won't be processed. oParent.invalidate(this); } } }; /** * Synchronously updates the DOM of this control to reflect the current object state. * * Note that this method can only be called when the control already has a DOM representation (it has * been rendered before) and when the control still is assigned to a UIArea. * * @deprecated As of 1.70, using this method is no longer recommended, but calling it still * causes a re-rendering of the control. Synchronous DOM updates via this method have several * drawbacks: they only work when the control has been rendered before (no initial rendering * possible), multiple state changes won't be combined automatically into a single re-rendering, * they might cause additional layout thrashing, standard invalidation might cause another * async re-rendering. * * The recommended alternative is to rely on invalidation and standard re-rendering. * * @protected */ Control.prototype.rerender = function() { this._bNeedsRendering = true; UIArea.rerenderControl(this); }; // @see sap.ui.core.Element#getDomRef Control.prototype.getDomRef = function(sSuffix) { // while cloning we know that control DOM does not exist if (this.bOutput === false && !this.oParent) { return null; } return Element.prototype.getDomRef.call(this, sSuffix); }; /** * Defines whether the user can select text inside this control. * Defaults to <code>true</code> as long as this method has not been called. * * <b>Note:</b>This only works in Safari; for Firefox the element's style must * be set to: * <pre> * -moz-user-select: none; * </pre> * in order to prevent text selection. * * @param {boolean} bAllow whether to allow text selection or not * @returns {this} Returns <code>this</code> to allow method chaining * @public */ Control.prototype.allowTextSelection = function(bAllow) { this.bAllowTextSelection = bAllow; return this; }; /** * The string given as "sStyleClass" will be added to the "class" attribute of this control's root HTML element. * * This method is intended to be used to mark controls as being of a special type for which * special styling can be provided using CSS selectors that reference this style class name. * * <pre> * Example: * myButton.addStyleClass("myRedTextButton"); // add a CSS class to one button instance * * ...and in CSS: * .myRedTextButton { * color: red; * } * </pre> * * This will add the CSS class "myRedTextButton" to the Button HTML and the CSS code above will then * make the text in this particular button red. * * Only characters allowed inside HTML attributes are allowed. * Quotes are not allowed and this method will ignore any strings containing quotes. * Strings containing spaces are interpreted as multiple custom style classes which are split by space and can be removed * individually later by calling removeStyleClass. * Multiple calls with the same sStyleClass will have no different effect than calling once. * If sStyleClass is null, empty string or it contains quotes, the call is ignored. * * @name sap.ui.core.Control.prototype.addStyleClass * @function * * @param {string} sStyleClass the CSS class name to be added * @returns {this} Returns <code>this</code> to allow method chaining * @public */ /** * Removes the given string from the list of custom style classes that have been set previously. * Regular style classes like "sapUiBtn" cannot be removed. * * @name sap.ui.core.Control.prototype.removeStyleClass * @function * * @param {string} sStyleClass the style to be removed * @returns {this} Returns <code>this</code> to allow method chaining * @public */ /** * The string given as "sStyleClass" will be be either added to or removed from the "class" attribute of this control's root HTML element, * depending on the value of "bAdd": if bAdd is true, sStyleClass will be added. * If bAdd is not given, sStyleClass will be removed if it is currently present and will be added if not present. * If sStyleClass is null or empty string, the call is ignored. * * See addStyleClass and removeStyleClass for further documentation. * * @name sap.ui.core.Control.prototype.toggleStyleClass * @function * * @param {string} sStyleClass the CSS class name to be added or removed * @param {boolean} [bAdd] whether sStyleClass should be added (or removed); when this parameter is not given, sStyleClass will be toggled (removed, if present, and added if not present) * @return {this} Returns <code>this</code> to allow method chaining * @public */ /** * Returns true if the given style class or all multiple style classes which are generated by splitting the given string with space are already set on the control * via previous call(s) to addStyleClass(). * * @name sap.ui.core.Control.prototype.hasStyleClass * @function * * @param {string} sStyleClass the style to check for * @returns {boolean} Whether the given style(s) has been set before * @public */ /** * Allows binding handlers for any native browser event to the root HTML element of this Control. This internally handles * DOM element replacements caused by re-rendering. * * <b>IMPORTANT:</b></br> * This should be only used as FALLBACK when the Control events do not cover a specific use-case! Always try using * SAPUI5 control events, as e.g. accessibility-related functionality is then provided automatically. * E.g. when working with a <code>sap.m.Button</code>, always use the Button's "press" event, not the native "click" event, because * "press" is also guaranteed to be fired when certain keyboard activity is supposed to trigger the Button. * * In the event handler, <code>this</code> refers to the Control - not to the root DOM element like in jQuery. While the DOM element can * be used and modified, the general caveats for working with SAPUI5 control DOM elements apply. In particular the DOM element * may be destroyed and replaced by a new one at any time, so modifications that are required to have permanent effect may not * be done. E.g. use {@link #addStyleClass} instead if the modification is of visual nature. * * Use {@link #detachBrowserEvent} to remove the event handler(s) again. * * @param {string} sEventType A string containing one or more JavaScript event types, such as "click" or "blur". * @param {function} fnHandler A function to execute each time the event is triggered. * @param {object} [oListener] The object, that wants to be notified, when the event occurs * @returns {this} Returns <code>this</code> to allow method chaining * @public */ Control.prototype.attachBrowserEvent = function(sEventType, fnHandler, oListener) { if (sEventType && (typeof (sEventType) === "string")) { // do nothing if the first parameter is empty or not a string if (typeof fnHandler === "function") { // also do nothing if the second parameter is not a function // store the parameters for bind() if (!this.aBindParameters) { this.aBindParameters = []; } oListener = oListener || this; var fnProxy = fnHandler.bind(oListener); this.aBindParameters.push({ sEventType: sEventType, fnHandler: fnHandler, oListener: oListener, fnProxy : fnProxy }); if (!this._sapui_bInAfterRenderingPhase) { // if control is rendered, directly call bind() this.$().on(sEventType, fnProxy); } } } return this; }; /** * Removes event handlers which have been previously attached using {@link #attachBrowserEvent}. * * Note: listeners are only removed, if the same combination of event type, callback function * and context object is given as in the call to <code>attachBrowserEvent</code>. * * @param {string} sEventType A string containing one or more JavaScript event types, such as "click" or "blur". * @param {function} fnHandler The function that is to be no longer executed. * @param {object} [oListener] The context object that was given in the call to <code>attachBrowserEvent</code>. * @returns {this} Returns <code>this</code> to allow method chaining * @public */ Control.prototype.detachBrowserEvent = function(sEventType, fnHandler, oListener) { if (sEventType && (typeof (sEventType) === "string")) { // do nothing if the first parameter is empty or not a string if (typeof (fnHandler) === "function") { // also do nothing if the second parameter is not a function var $ = this.$(),i,oParamSet; oListener = oListener || this; // remove the bind parameters from the stored array if (this.aBindParameters) { for (i = this.aBindParameters.length - 1; i >= 0; i--) { oParamSet = this.aBindParameters[i]; if ( oParamSet.sEventType === sEventType && oParamSet.fnHandler === fnHandler && oParamSet.oListener === oListener ) { this.aBindParameters.splice(i, 1); // if control is rendered, directly call unbind() $.off(sEventType, oParamSet.fnProxy); } } } } } return this; }; /** * Returns a renderer for this control instance. * * It is retrieved using the RenderManager as done during rendering. * * @return {sap.ui.core.ControlRenderer} a Renderer suitable for this Control instance. * @protected */ Control.prototype.getRenderer = function () { return RenderManager.getRenderer(this); }; /** * Puts <code>this</code> control into the specified container (<code>oRef</code>) at the given * position (<code>oPosition</code>). * * First it is checked whether <code>oRef</code> is a container element / control (has a * multiple aggregation with type <code>sap.ui.core.Control</code> and name 'content') or is an Id String * of such a container. * If this is not the case <code>oRef</code> can either be a Dom Reference or Id String of the UIArea * (if it does not yet exist implicitly a new UIArea is created), * * The <code>oPosition</code> can be one of the following: * * <ul> * <li>"first": The control is added as the first element to the container.</li> * <li>"last": The control is added as the last element to the container (default).</li> * <li>"only": All existing children of the container are removed (not destroyed!) and the control is added as new child.</li> * <li><i>index</i>: The control is added at the specified <i>index</i> to the container.</li> * </ul> * * @param {string|Element|sap.ui.core.Control} oRef container into which the control should be put * @param {string|int} [vPosition="last"] Describes the position where the control should be put into the container * @returns {this} Returns <code>this</code> to allow method chaining * @public */ Control.prototype.placeAt = function(oRef, vPosition) { Core.ready(function() { // 1st try to resolve the oRef as a container control var oContainer = oRef; if (typeof oRef === "string") { oContainer = Element.getElementById(oRef); } if (oContainer instanceof Element) { if (!isSuitableAsContainer(oContainer)) { future.warningThrows("placeAt cannot be processed because container " + oContainer + " does not have an aggregation 'content'."); return this; } } else { // if no container control is found, use the corresponding UIArea if (oRef === StaticArea.STATIC_UIAREA_ID || oRef && oRef.id === StaticArea.STATIC_UIAREA_ID) { oContainer = StaticArea.getUIArea(); } else { oContainer = UIArea.create(oRef); } } if (typeof vPosition === "number") { oContainer.insertContent(this, vPosition); } else { vPosition = vPosition || "last"; //"last" is default switch (vPosition) { case "last": oContainer.addContent(this); break; case "first": oContainer.insertContent(this, 0); break; case "only": oContainer.removeAllContent(); oContainer.addContent(this); break; default: future.warningThrows("Position " + vPosition + " is not supported for function placeAt."); } } }.bind(this)); return this; }; /** * Checks whether the given UI5 element is suitable as a container for placeAt * @param {sap.ui.core.Element} oContainer * @returns {boolean} Whether the given UI5 element is suitable as a container * @private */ function isSuitableAsContainer(oContainer) { var oContentAggInfo = oContainer.getMetadata().getAggregation("content"); if (oContentAggInfo) { return oContentAggInfo.multiple && oContentAggInfo.type === "sap.ui.core.Control"; } // use duck typing to allow placeAt even when there's no 'content' aggregation available. // TODO This is a workaround for sap.ui.commons.AbsoluteLayout, abandon when it's removed return ( typeof oContainer.addContent === "function" && typeof oContainer.insertContent === "function" && typeof oContainer.removeAllContent === "function" ); } /* * Event handling */ /** * Cancels user text selection if text selection is disabled for this control. * See the {@link #allowTextSelection} method. * @private */ Control.prototype.onselectstart = function (oBrowserEvent) { if (!this.bAllowTextSelection) { oBrowserEvent.preventDefault(); oBrowserEvent.stopPropagation(); } }; /* * Rendering */ /** * Function is called before the rendering of the control is started. * * Applications must not call this hook method directly, it is called by the framework. * * Subclasses of Control should override this hook to implement any necessary actions before the rendering. * * @param {jQuery.Event} oEvent onBeforeRendering event object * @returns {void|undefined} This hook method must not have a return value. Return value <code>void</code> is deprecated since 1.120, as it does not force functions to <b>not</b> return something. * This implies that, for instance, no async function returning a Promise should be used. * * <b>Note:</b> While the return type is currently <code>void|undefined</code>, any * implementation of this hook must not return anything but undefined. Any other * return value will cause an error log in this version of UI5 and will fail in future * major versions of UI5. * @protected */ Control.prototype.onBeforeRendering = function() { // Before adding any implementation, please remember that this method was first implemented in release 1.54. // Therefore, many subclasses will not call this method at all. return undefined; }; /** * Function is called when the rendering of the control is completed. * * Applications must not call this hook method directly, it is called by the framework. * * Subclasses of Control should override this hook to implement any necessary actions after the rendering. * * @param {jQuery.Event} oEvent onAfterRendering event object * @returns {void|undefined} This hook method must not have a return value. Return value <code>void</code> is deprecated since 1.120, as it does not force functions to <b>not</b> return something. * This implies that, for instance, no async function returning a Promise should be used. * * <b>Note:</b> While the return type is currently <code>void|undefined</code>, any * implementation of this hook must not return anything but undefined. Any other * return value will cause an error log in this version of UI5 and will fail in future * major versions of UI5. * @protected */ Control.prototype.onAfterRendering = function() { // Before adding any implementation, please remember that this method was first implemented in release 1.54. // Therefore, many subclasses will not call this method at all. return undefined; }; /** * Returns the DOMNode Id to be used for the "labelFor" attribute of the label. * * By default, this is the Id of the control itself. * * @return {string} Id to be used for the <code>labelFor</code> * @public */ Control.prototype.getIdForLabel = function () { return this.getId(); }; Control.prototype.destroy = function(bSuppressInvalidate) { // ignore repeated calls if (this.bIsDestroyed) { return; } // avoid rerendering this._bIsBeingDestroyed = true; //Cleanup Busy Indicator this._cleanupBusyIndicator(); // do not load ResizeHandler - if it isn't there, there should be no resize handler registrations ResizeHandler = ResizeHandler || sap.ui.require("sap/ui/core/ResizeHandler"); if ( ResizeHandler ) { ResizeHandler.deregisterAllForControl(this.getId()); } // Controls can have their visible-property set to "false" in which case the Element's destroy method will // fail to remove the placeholder content from the DOM. We have to remove it here in that case if (!this.getVisible()) { var oPlaceholder = document.getElementById(RenderManager.createInvisiblePlaceholderId(this)); if (oPlaceholder && oPlaceholder.parentNode) { oPlaceholder.parentNode.removeChild(oPlaceholder); } } Element.prototype.destroy.call(this, bSuppressInvalidate); }; // ---- local busy indicator handling --------------------------------------------------------------------------------------- var oRenderingDelegate = { /** * @this {sap.ui.core.Control} * @private */ onBeforeRendering: function() { if (this.getBusy()) { fnRemoveBusyIndicator.call(this); } // remove all block-layers to prevent leftover DOM elements and eventhandlers fnRemoveAllBlockLayers.call(this); }, /** * @this {sap.ui.core.Control} * @private */ onAfterRendering: function () { if (this.getBlocked() && this.getDomRef() && !this.getDomRef("blockedLayer")) { this._oBlockState = BlockLayerUtils.block(this, this.getId() + "-blockedLayer", this._sBlockSection); jQuery(this._oBlockState.$blockLayer.get(0)).addClass("sapUiBlockLayerOnly"); } if (this.getBusy() && this.getDomRef() && !this._busyIndicatorDelayedCallId && !this.getDomRef("busyIndicator")) { // Also use the BusyIndicatorDelay when a control is initialized // with "busy = true". If the delayed call was already initialized // skip any further call if the control was re-rendered while // the delay is running. var iDelay = this.getBusyIndicatorDelay(); // Only do it via timeout if there is a delay. Otherwise append the // BusyIndicator immediately if (iDelay) { this._busyIndicatorDelayedCallId = setTimeout(fnAppendBusyIndicator.bind(this), iDelay); } else { // To win against the focus restoration from RenderManager, we have to set focus asynchronously. fnAppendBusyIndicator.call(this, /* bAsyncFocus */ true); } } } }; function checkAndFocusBlockLayer() { if (this._oBusyBlockState && this.getDomRef(this._sBusySection)?.contains(document.activeElement)) { // Move focus to the busy indicator if the focus is currently within the busy control's DOM. this._oBusyBlockState.lastFocusPosition = document.activeElement; this._oBusyBlockState.$blockLayer.get(0).focus({ preventScroll: true }); } } /** * Add busy indicator to DOM * * @param {boolean} [asyncFocus=false] whether focus should be set asynchronously. * This is need to set the focus after the restoration from RenderManager * @private */ function fnAppendBusyIndicator(bAsyncFocus) { // Only append if busy state is still set if (!this.getBusy()) { return; } var $this = this.$(this._sBusySection); //If there is a pending delayed call to append the busy indicator, we can clear it now if (this._busyIndicatorDelayedCallId) { clearTimeout(this._busyIndicatorDelayedCallId); delete this._busyIndicatorDelayedCallId; } // if no busy section/control jquery instance could be retrieved -> the control is not part of the dom anymore // this might happen in certain scenarios when e.g. a dialog is closed faster than the busyIndicatorDelay if (!$this || $this.length === 0) { Log.warning("BusyIndicator could not be rendered. The outer control instance is not valid anymore."); return; } if (this._sBlockSection === this._sBusySection) { if (this._oBlockState) { BusyIndicatorUtils.addHTML(this._oBlockState, this.getBusyIndicatorSize()); BlockLayerUtils.toggleAnimationStyle(this._oBlockState, true); this._oBusyBlockState = this._oBlockState; } else { // BusyIndicator is the first blocking element created fnAddStandaloneBusyIndicator.call(this); } } else { // Standalone busy indicator fnAddStandaloneBusyIndicator.call(this); } if (bAsyncFocus) { setTimeout(checkAndFocusBlockLayer.bind(this), 0); } else { checkAndFocusBlockLayer.call(this); } } /** * Adds a standalone block-layer. Might be shared with a BusyIndicator later on. */ function fnAddStandaloneBlockLayer () { this._oBlockState = BlockLayerUtils.block(this, this.getId() + "-blockedLayer", this._sBlockSection); jQuery(this._oBlockState.$blockLayer.get(0)).addClass("sapUiBlockLayerOnly"); } /** * Adds a standalone BusyIndicator. * The block-layer code is able to recognize that a new block-layer is not needed. */ function fnAddStandaloneBusyIndicator () { // if there's already a busy block state, remove it first before creating a new one // the existing busy block state can't be reused, because the block layer DOM is removed by the renderer. A new // block layer needs to be created if (this._oBusyBlockState) { BlockLayerUtils.unblock(this._oBusyBlockState); } this._oBusyBlockState = BlockLayerUtils.block(this, this.getId() + "-busyIndicator", this._sBusySection); BusyIndicatorUtils.addHTML(this._oBusyBlockState, this.getBusyIndicatorSize()); } /** * Cleanup all block-layers. * Doesn't matter if it's a busy- or regular block-layer. * Used to remove all unnecessary DOM elements and event-handlers during destroy and before rerendering. */ function fnRemoveAllBlockLayers() { BlockLayerUtils.unblock(this._oBlockState); BlockLayerUtils.unblock(this._oBusyBlockState); delete this._oBlockState; delete this._oBusyBlockState; } /** * Remove busy indicator from DOM * @param {boolean} bForceRemoval Forces the removal of all Block layers * @private */ function fnRemoveBusyIndicator(bForceRemoval) { // removing all block layers is done upon rerendering and destroy of the control if (bForceRemoval) { fnRemoveAllBlockLayers.call(this); return; } // Restore focus on last focus position, if possible let oLastFocusedElement; if (this._oBusyBlockState) { const oBlockLayerDOM = this._oBusyBlockState.$blockLayer.get(0); // Focus might be moved from the busy indicator // If it is still on the busy indicator, we restore the focus. Otherwise do nothing. if (oBlockLayerDOM === document.activeElement) { // Check if last focused DOM element is still available, restore focus on the DOM element // Otherwise, move focus to the control's DOM Ref if (jQuery(this._oBusyBlockState.lastFocusPosition).is(":sapFocusable")) { oLastFocusedElement = this._oBusyBlockState.lastFocusPosition; } else { oLastFocusedElement = Element.closestTo(this._oBusyBlockState.lastFocusPosition) || this; } oLastFocusedElement.focus(); } } const $this = this.$(this._sBusySection); $this.removeClass('sapUiLocalBusy'); $this.removeAttr("aria-busy"); if (this._sBlockSection === this._sBusySection) { if (!this.getBlocked() && !this.getBusy()) { // Remove shared block state & busy block state reference fnRemoveAllBlockLayers.call(this); } else if (this.getBlocked()) { if (this._oBlockState || this._oBusyBlockState) { // Hide animation in shared block layer BlockLayerUtils.toggleAnimationStyle(this._oBlockState || this._oBusyBlockState, false); this._oBlockState = this._oBusyBlockState; } } else if (this._oBusyBlockState) { BlockLayerUtils.unblock(this._oBusyBlockState); delete this._oBusyBlockState; } } else if (this._oBusyBlockState) { // standalone busy block state BlockLayerUtils.unblock(this._oBusyBlockState); delete this._oBusyBlockState; } } /** * Set the controls block state. * * @param {boolean} bBlocked The new blocked state to be set * @returns {this} <code>this</code> to allow method chaining * * @private * @ui5-restricted sap.ui.core, sap.m, sap.viz, sap.ui.rta, sap.ui.table * @deprecated since version 1.69, the blocked property is deprecated. * There is no accessibility support for this property. * Blocked controls should not be used inside Controls, which rely on keyboard navigation, e.g. List controls. */ Control.prototype.setBlocked = function(bBlocked, sBlockedSection /* this is an internal parameter to apply partial blocking for a specific section of the control */) { //If the new state is already set, we don't need to do anything if (!!bBlocked == this.getProperty("blocked")) { return this; } this._sBlockSection = sBlockedSection || this._sBlockSection; //No rerendering - should be modeled as a non-invalidating property once we have that this.setProperty("blocked", bBlocked, /*bSuppressInvalidate*/ true); if (bBlocked) { this.addDelegate(oRenderingDelegate, false, this); } else if (!this.getBusy()) { // only remove delegate if control is not still in "busy" state this.removeDelegate(oRenderingDelegate); } //If no domref exists stop here. if (!this.getDomRef()) { return this; } if (bBlocked) { // blocking if (this._sBlockSection === this._sBusySection) { // only create a new block state if neither busy nor blocking blockState is present if (!this._oBusyBlockState && !this._oBlockState) { fnAddStandaloneBlockLayer.call(this); } else { Log.info("The control is already busy. Hence, no new block-layer was created for the shared section."); } } else { fnAddStandaloneBlockLayer.call(this); } } else { // unblocking if (this._sBlockSection === this._sBusySection) { if (!this.getBlocked() && !this.getBusy()) { // Remove shared block state & busy block state reference fnRemoveAllBlockLayers.call(this); } else if (this.getBusy()) { // Control or section is still busy, hence no removal required Log.info("The control is already busy. Hence, no new block-layer was created for the shared section."); } } else if (this._oBlockState) { // standalone block state BlockLayerUtils.unblock(this._oBlockState); delete this._oBlockState; } } return this; }; /** * Gets current value of property blocked. * @returns {boolean} Whether the control is currently in blocked state. Default is 'false'. * @public * * @deprecated since version 1.69, the blocked property is deprecated. * There is no accessibility support for this property. * Blocked controls should not be used inside Controls, which rely on keyboard navigation, e.g. List controls. */ Control.prototype.getBlocked = function() { return this.getProperty("blocked"); }; /** * Set the controls busy state. * * <b>Note:</b> The busy state can't be set on controls (e.g. sap.m.ColumnListItem) * which renderings have the following tags as DOM root element: * area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr|tr * * @param {boolean} bBusy The new busy state to be set * @returns {this} <code>this</code> to allow method chaining * @public */ Control.prototype.setBusy = function (bBusy, sBusySection /* this is an internal parameter to apply partial local busy indicator for a specific section of the control */) { //If the new state is already set, we don't need to do anything if (!!bBusy == this.getProperty("busy")) { return this; } this._sBusySection = sBusySection || this._sBusySection; //No rerendering - should be modeled as a non-invalidating property once we have that this.setProperty("busy", bBusy, /*bSuppressInvalidate*/ true); if (bBusy) { Interaction.notifyShowBusyIndicator(this); this.addDelegate(oRenderingDelegate, false, this); } else { // only remove the delegate if the control is not still in "blocked" state if (!this.getProperty("blocked")) { this.removeDelegate(oRenderingDelegate); } //If there is a pending delayed call we clear it if (this._busyIndicatorDelayedCallId) { clearTimeout(this._busyIndicatorDelayedCallId); delete this._busyIndicatorDelayedCallId; } } //If no domref exists stop here. if (!this.getDomRef()) { return this; } if (bBusy) { if (this.getBusyIndicatorDelay() <= 0) { fnAppendBusyIndicator.call(this); } else { this._busyIndicatorDelayedCallId = setTimeout(fnAppendBusyIndicator.bind(this), this.getBusyIndicatorDelay()); } } else { fnRemoveBusyIndicator.call(this); Interaction.notifyHideBusyIndicator(this); } return this; }; /** * Check if the control is currently in busy state. * * @public * @deprecated As of 1.15, use {@link #getBusy} instead * @returns {boolean} * @function */ Control.prototype.isBusy = Control.prototype.getBusy; /** * Define the delay, after which the busy indicator will show up. * * @public * @param {int} iDelay The delay in ms * @returns {this} <code>this</code> to allow method chaining */ Control.prototype.setBusyIndicatorDelay = function(iDelay) { // should be modeled as a non-invalidating property once we have that this.setProperty("busyIndicatorDelay", iDelay, /*bSuppressInvalidate*/ true); return this; }; /** * Cleanup all timers which might have been created by the busy indicator. * * @private */ Control.prototype._cleanupBusyIndicator = function() { //If there is a pending delayed call we clear it if (this._busyIndicatorDelayedCallId) { clearTimeout(this._busyIndicatorDelayedCallId); delete this._busyIndicatorDelayedCallId; } fnRemoveBusyIndicator.call(this, true); }; // ---- field groups -------------------------------------------------------------------------------------- /** * Returns a copy of the field group IDs array. Modification of the field group IDs * need to call {@link #setFieldGroupIds setFieldGroupIds} to apply the changes. * * @name sap.ui.core.Control.prototype.getFieldGroupIds * @function * * @return {string[]} copy of the field group IDs * @public */ /** * Returns a list of all child controls with a field group ID. * See {@link #checkFieldGroupIds checkFieldGroupIds} for a description of the * <code>vFieldGroupIds</code> parameter. * Associated controls are not taken into account. * * @param {string|string[]} [vFieldGroupIds] ID of the field group or an array of field group IDs to match * @return {sap.ui.core.Control[]} The list of controls with a field group ID * @public */ Control.prototype.getControlsByFieldGroupId = function(vFieldGroupIds) { return this.findAggregatedObjects(true, function(oElement) { if (oElement instanceof Control) { return oElement.checkFieldGroupIds(vFieldGroupIds); } return false; }); }; /** * Returns whether this control belongs to a given combination of field groups. * * If the <code>vFieldGroupIds</code> parameter is not specified, the method checks whether this control belongs * to <strong>any</strong> field group, that is, whether any field group ID is defined for it. * * If a list of field group IDs is specified, either as an array of strings or as a single string (interpreted as * a comma separated list of IDs), then the method will check whether this control belongs to <strong>all</strong> * given field groups. Accordingly, an empty list of IDs (empty array or empty string) will always return true. * * Note that a string value for <code>vFieldGroupIds</code> (comma separated list) will not be trimmed. * All whitespace characters are significant, but in general not recommended in field group IDs. * * @param {string|string[]} [vFieldGroupIds] An array of field group IDs or a single string with a comma separated list of IDs to match * @return {boolean} Whether the field group IDs defined fo