@openui5/sap.ui.core
Version:
OpenUI5 Core Library sap.ui.core
1,255 lines (1,137 loc) • 53.6 kB
JavaScript
/*!
* 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