UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

1,232 lines (1,119 loc) 70.1 kB
/*! * OpenUI5 * (c) Copyright 2009-2021 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ // Provides the base class for all controls and UI elements. sap.ui.define([ '../base/DataType', '../base/Object', '../base/ManagedObject', '../base/ManagedObjectRegistry', './ElementMetadata', '../Device', "sap/ui/performance/trace/Interaction", "sap/base/Log", "sap/base/assert", "sap/ui/thirdparty/jquery", "sap/ui/events/F6Navigation", "./RenderManager" ], function( DataType, BaseObject, ManagedObject, ManagedObjectRegistry, ElementMetadata, Device, Interaction, Log, assert, jQuery, F6Navigation, RenderManager ) { "use strict"; /** * Constructs and initializes a UI Element with the given <code>sId</code> and settings. * * * <h3>Uniqueness of IDs</h3> * * Each <code>Element</code> must have an ID. If no <code>sId</code> or <code>mSettings.id</code> is * given at construction time, a new ID will be created automatically. The IDs of all elements that exist * at the same time in the same window must be different. Providing an ID which is already used by another * element throws an error. * * When an element is created from a declarative source (e.g. XMLView), then an ID defined in that * declarative source needs to be unique only within the declarative source. Declarative views will * prefix that ID with their own ID (and some separator) before constructing the element. * Programmatically created views (JSViews) can do the same with the {@link sap.ui.core.mvc.View#createId} API. * Similarly, UIComponents can prefix the IDs of elements created in their context with their own ID. * Also see {@link sap.ui.core.UIComponent#getAutoPrefixId UIComponent#getAutoPrefixId}. * * * <h3>Settings</h3> * If the optional <code>mSettings</code> are given, they must be a JSON-like object (object literal) * that defines values for properties, aggregations, associations or events keyed by their name. * * <b>Valid Names:</b> * * The property (key) names supported in the object literal are exactly the (case sensitive) * names documented in the JSDoc for the properties, aggregations, associations and events * of the control and its base classes. Note that for 0..n aggregations and associations this * usually is the plural name, whereas it is the singular name in case of 0..1 relations. * * Each subclass should document the set of supported names in its constructor documentation. * * <b>Valid Values:</b> * * <ul> * <li>for normal properties, the value has to be of the correct simple type (no type conversion occurs)</li> * <li>for 0..1 aggregations, the value has to be an instance of the aggregated control or element type</li> * <li>for 0..n aggregations, the value has to be an array of instances of the aggregated type</li> * <li>for 0..1 associations, an instance of the associated type or an id (string) is accepted</li> * <li>0..n associations are not supported yet</li> * <li>for events either a function (event handler) is accepted or an array of length 2 * where the first element is a function and the 2nd element is an object to invoke the method on.</li> * </ul> * * Special aggregation <code>dependents</code> is connected to the lifecycle management and databinding, * but not rendered automatically and can be used for popups or other dependent controls or elements. * This allows the definition of popup controls in declarative views and enables propagation of model * and context information to them. * * @param {string} [sId] 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] optional map/JSON-object with initial property values, aggregated objects etc. for the new element * * @abstract * * @class Base Class for UI Elements. * * <code>Element</code> is the most basic building block for UI5 UIs. An <code>Element</code> has state like a * <code>ManagedObject</code>, it has a unique ID by which the framework remembers it. It can have associated * DOM, but it can't render itself. Only {@link sap.ui.core.Control Controls} can render themselves and also * take care of rendering <code>Elements</code> that they aggregate as children. If an <code>Element</code> * has been rendered, its related DOM gets the same ID as the <code>Element</code> and thereby can be retrieved * via API. When the state of an <code>Element</code> changes, it informs its parent <code>Control</code> which * usually re-renders then. * * <h3>Dispatching Events</h3> * * The UI5 framework already registers generic listeners for common browser events, such as <code>click</code> * or <code>keydown</code>. When called, the generic listener first determines the corresponding target element * using {@link jQuery#control}. Then it checks whether the element has an event handler method for the event. * An event handler method by convention has the same name as the event, but prefixed with "on": Method * <code>onclick</code> is the handler for the <code>click</code> event, method <code>onkeydown</code> the handler * for the <code>keydown</code> event and so on. If there is such a method, it will be called with the original * event as the only parameter. If the element has a list of delegates registered, their handler functions will * be called the same way, where present. The set of implemented handlers might differ between element and * delegates. Not each handler implemented by an element has to be implemented by its delegates, and delegates * can implement handlers that the corresponding element doesn't implement. * * A list of browser events that are handled that way can be found in {@link module:sap/ui/events/ControlEvents}. * Additionally, the framework dispatches pseudo events ({@link module:sap/ui/events/PseudoEvents}) using the same * naming convention. Last but not least, some framework events are also dispatched that way, e.g. * <code>BeforeRendering</code>, <code>AfterRendering</code> (only for controls) and <code>ThemeChanged</code>. * * If further browser events are needed, controls can register listeners on the DOM using native APIs in their * <code>onAfterRendering</code> handler. If needed, they can do this for their aggregated elements as well. * If events might fire often (e.g. <code>mousemove</code>), it is best practice to register them only while * needed, and deregister afterwards. Anyhow, any registered listeners must be cleaned up in the * <code>onBeforeRendering</code> listener and before destruction in the <code>exit</code> hook. * * @extends sap.ui.base.ManagedObject * @author SAP SE * @version 1.87.1 * @public * @alias sap.ui.core.Element * @ui5-metamodel This control/element also will be described in the UI5 (legacy) designtime metamodel */ var Element = ManagedObject.extend("sap.ui.core.Element", { metadata : { stereotype : "element", "abstract" : true, publicMethods : [ "getId", "getMetadata", "getTooltip_AsString", "getTooltip_Text", "getModel", "setModel", "hasModel", "bindElement", "unbindElement", "getElementBinding", "prop", "getLayoutData", "setLayoutData" ], library : "sap.ui.core", aggregations : { /** * The tooltip that should be shown for this Element. * * In the most simple case, a tooltip is a string that will be rendered by the control and * displayed by the browser when the mouse pointer hovers over the control's DOM. In this * variant, <code>tooltip</code> behaves like a simple control property. * * Controls need to explicitly support this kind of tooltip as they have to render it, * but most controls do. Exceptions will be documented for the corresponding controls * (e.g. <code>sap.ui.core.HTML</code> does not support tooltips). * * Alternatively, <code>tooltip</code> can act like a 0..1 aggregation and can be set to a * tooltip control (an instance of a subclass of <code>sap.ui.core.TooltipBase</code>). In * that case, the framework will take care of rendering the tooltip control in a popup-like * manner. Such a tooltip control can display arbitrary content, not only a string. * * UI5 currently does not provide a recommended implementation of <code>TooltipBase</code> * as the use of content-rich tooltips is discouraged by the Fiori Design Guidelines. * Existing subclasses of <code>TooltipBase</code> therefore have been deprecated. * However, apps can still subclass from <code>TooltipBase</code> and create their own * implementation when needed (potentially taking the deprecated implementations as a * starting point). * * See the section {@link https://experience.sap.com/fiori-design-web/using-tooltips/ Using Tooltips} * in the Fiori Design Guideline. */ tooltip : {name : "tooltip", type : "sap.ui.core.TooltipBase", altTypes : ["string"], multiple : false}, /** * Custom Data, a data structure like a map containing arbitrary key value pairs. */ customData : {name : "customData", type : "sap.ui.core.CustomData", multiple : true, singularName : "customData"}, /** * Defines the layout constraints for this control when it is used inside a Layout. * LayoutData classes are typed classes and must match the embedding Layout. * See VariantLayoutData for aggregating multiple alternative LayoutData instances to a single Element. */ layoutData : {name : "layoutData", type : "sap.ui.core.LayoutData", multiple : false, singularName : "layoutData"}, /** * Dependents are not rendered, but their databinding context and lifecycle are bound to the aggregating Element. * @since 1.19 */ dependents : {name : "dependents", type : "sap.ui.core.Element", multiple : true}, /** * Defines the drag-and-drop configuration. * <b>Note:</b> This configuration might be ignored due to control {@link sap.ui.core.Element.extend metadata} restrictions. * * @since 1.56 */ dragDropConfig : {name : "dragDropConfig", type : "sap.ui.core.dnd.DragDropBase", multiple : true, singularName : "dragDropConfig"} } }, constructor : function(sId, mSettings) { ManagedObject.apply(this, arguments); }, renderer : null // Element has no renderer }, /* Metadata constructor */ ElementMetadata); // apply the registry mixin ManagedObjectRegistry.apply(Element, { onDuplicate: function(sId, oldElement, newElement) { if ( oldElement._sapui_candidateForDestroy ) { Log.debug("destroying dangling template " + oldElement + " when creating new object with same ID"); oldElement.destroy(); } else { var sMsg = "adding element with duplicate id '" + sId + "'"; // duplicate ID detected => fail or at least log a warning if (sap.ui.getCore().getConfiguration().getNoDuplicateIds()) { Log.error(sMsg); throw new Error("Error: " + sMsg); } else { Log.warning(sMsg); } } } }); /** * Creates metadata for a UI Element by extending the Object Metadata. * * @param {string} sClassName name of the class to build the metadata for * @param {object} oStaticInfo static information used to build the metadata * @param {function} [fnMetaImpl=sap.ui.core.ElementMetadata] constructor to be used for the metadata * @return {sap.ui.core.ElementMetadata} the created metadata * @static * @public * @deprecated Since 1.3.1. Use the static <code>extend</code> method of the desired base class (e.g. {@link sap.ui.core.Element.extend}) */ Element.defineClass = function(sClassName, oStaticInfo, fnMetaImpl) { // create and attach metadata but with an Element specific implementation return BaseObject.defineClass(sClassName, oStaticInfo, fnMetaImpl || ElementMetadata); }; /** * Elements don't have a facade and therefore return themselves as their interface. * * @returns {this} <code>this</code> as there's no facade for elements * @see sap.ui.base.Object#getInterface * @public */ Element.prototype.getInterface = function() { return this; }; /** * Defines a new subclass of Element 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.base.ManagedObject.extend} already accepts, * plus the following <code>dnd</code> property to configure drag-and-drop behavior in the metadata object literal: * * Example: * <pre> * Element.extend('sap.mylib.MyElement', { * metadata : { * library : 'sap.mylib', * properties : { * value : 'string', * width : 'sap.ui.core.CSSSize' * }, * dnd : { draggable: true, droppable: false }, * aggregations : { * items : { type: 'sap.ui.core.Control', multiple : true, dnd : {draggable: false, dropppable: true, layout: "Horizontal" } }, * header : {type : "sap.ui.core.Control", multiple : false, dnd : true }, * } * } * }); * </pre> * * <h3><code>dnd</code> key as a metadata property</h3> * * <b>dnd</b>: <i>object|boolean</i><br> * Defines draggable and droppable configuration of the element. * The following keys can be provided via <code>dnd</code> object literal to configure drag-and-drop behavior of the element: * <ul> * <li><code>[draggable=false]: <i>boolean</i></code> Defines whether the element is draggable or not. The default value is <code>false</code>.</li> * <li><code>[droppable=false]: <i>boolean</i></code> Defines whether the element is droppable (it allows being dropped on by a draggable element) or not. The default value is <code>false</code>.</li> * </ul> * If <code>dnd</code> property is of type Boolean, then the <code>draggable</code> and <code>droppable</code> configuration are set to this Boolean value. * * <h3><code>dnd</code> key as an aggregation metadata property</h3> * * <b>dnd</b>: <i>object|boolean</i><br> * In addition to draggable and droppable configuration, the layout of the aggregation can be defined as a hint at the drop position indicator. * <ul> * <li><code>[layout="Vertical"]: </code> The arrangement of the items in this aggregation. This setting is recommended for the aggregation with multiplicity 0..n (<code>multiple: true</code>). Possible values are <code>Vertical</code> (e.g. rows in a table) and <code>Horizontal</code> (e.g. columns in a table). It is recommended to use <code>Horizontal</code> layout if the arrangement is multidimensional.</li> * </ul> * * @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} Created class / constructor function * * @public * @static * @name sap.ui.core.Element.extend * @function */ /** * Dispatches the given event, usually a browser event or a UI5 pseudo event. * @private */ Element.prototype._handleEvent = function (oEvent) { var that = this, sHandlerName = "on" + oEvent.type; function each(aDelegates) { var i,l,oDelegate; if ( aDelegates && (l = aDelegates.length) > 0 ) { // To be robust against concurrent modifications of the delegates list, we loop over a copy. // When there is only a single entry, the loop is safe without a copy (length is determined only once!) aDelegates = l === 1 ? aDelegates : aDelegates.slice(); for (i = 0; i < l; i++ ) { if (oEvent.isImmediateHandlerPropagationStopped()) { return; } oDelegate = aDelegates[i].oDelegate; if (oDelegate[sHandlerName]) { oDelegate[sHandlerName].call(aDelegates[i].vThis === true ? that : aDelegates[i].vThis || oDelegate, oEvent); } } } } each(this.aBeforeDelegates); if ( oEvent.isImmediateHandlerPropagationStopped() ) { return; } if ( this[sHandlerName] ) { this[sHandlerName](oEvent); } each(this.aDelegates); }; /** * Initializes the element instance after creation. * * Applications must not call this hook method directly, it is called by the framework * while the constructor of an element is executed. * * Subclasses of Element should override this hook to implement any necessary initialization. * * @protected */ Element.prototype.init = 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. }; /** * Hook method for cleaning up the element instance before destruction. * * Applications must not call this hook method directly, it is called by the framework * when the element is {@link #destroy destroyed}. * * Subclasses of Element should override this hook to implement any necessary cleanup. * * <pre> * exit: function() { * // ... do any further cleanups of your subclass e.g. detach events... * this.$().off("click", this.handleClick); * * if (Element.prototype.exit) { * Element.prototype.exit.apply(this, arguments); * } * } * </pre> * * For a more detailed description how to to use the exit hook, see Section * {@link topic:d4ac0edbc467483585d0c53a282505a5 exit() Method} in the documentation. * * @protected */ Element.prototype.exit = 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. }; /** * Creates a new Element from the given data. * * If <code>vData</code> is an Element already, that element is returned. * If <code>vData</code> is an object (literal), then a new element is created with <code>vData</code> as settings. * The type of the element is either determined by a property named <code>Type</code> in the <code>vData</code> or * by a type information in the <code>oKeyInfo</code> object * @param {sap.ui.core.Element|object} vData Data to create the element from * @param {object} [oKeyInfo] An entity information (e.g. aggregation info) * @param {string} [oKeyInfo.type] Type info for the entity * @returns {sap.ui.core.Element} * The newly created <code>Element</code> * @public * @static * @deprecated As of 1.44, use the more flexible {@link sap.ui.base.ManagedObject.create}. * @function */ Element.create = ManagedObject.create; /** * Returns a simple string representation of this element. * * Mainly useful for tracing purposes. * @public * @return {string} a string description of this element */ Element.prototype.toString = function() { return "Element " + this.getMetadata().getName() + "#" + this.sId; }; /** * Returns the best suitable DOM Element that represents this UI5 Element. * By default the DOM Element with the same ID as this Element is returned. * Subclasses should override this method if the lookup via id is not sufficient. * * Note that such a DOM Element does not necessarily exist in all cases. * Some elements or controls might not have a DOM representation at all (e.g. * a naive FlowLayout) while others might not have one due to their current * state (e.g. an initial, not yet rendered control). * * If an ID suffix is given, the ID of this Element is concatenated with the suffix * (separated by a single dash) and the DOM node with that compound ID will be returned. * This matches the UI5 naming convention for named inner DOM nodes of a control. * * @param {string} [sSuffix] ID suffix to get the DOMRef for * @return {Element} The Element's DOM Element sub DOM Element or null * @protected */ Element.prototype.getDomRef = function(sSuffix) { return document.getElementById(sSuffix ? this.getId() + "-" + sSuffix : this.getId()); }; /** * Returns the best suitable DOM node that represents this Element wrapped as jQuery object. * I.e. the element returned by {@link sap.ui.core.Element#getDomRef} is wrapped and returned. * * If an ID suffix is given, the ID of this Element is concatenated with the suffix * (separated by a single dash) and the DOM node with that compound ID will be wrapped by jQuery. * This matches the UI5 naming convention for named inner DOM nodes of a control. * * @param {string} [sSuffix] ID suffix to get a jQuery object for * @return {jQuery} The jQuery wrapped element's DOM reference * @protected */ Element.prototype.$ = function(sSuffix) { return jQuery(this.getDomRef(sSuffix)); }; /** * Checks whether this element has an active parent. * * @returns {boolean} Whether this element has an active parent * @private */ Element.prototype.isActive = function() { return this.oParent && this.oParent.isActive(); }; /** * This function either calls set[sPropertyName] or get[sPropertyName] with the specified property name * depending if an <code>oValue</code> is provided or not. * * @param {string} sPropertyName name of the property to set * @param {any} [oValue] value to set the property to * @return {any|sap.ui.core.Element} Returns <code>this</code> to allow method chaining in case of setter and the property value in case of getter * @public * @deprecated Since 1.28.0 The contract of this method is not fully defined and its write capabilities overlap with applySettings */ Element.prototype.prop = function(sPropertyName, oValue) { var oPropertyInfo = this.getMetadata().getAllSettings()[sPropertyName]; if (oPropertyInfo) { if (arguments.length == 1) { // getter return this[oPropertyInfo._sGetter](); } else { // setter this[oPropertyInfo._sMutator](oValue); return this; } } }; Element.prototype.insertDependent = function(oElement, iIndex) { this.insertAggregation("dependents", oElement, iIndex, true); return this; // explicitly return 'this' to fix controls that override insertAggregation wrongly }; Element.prototype.addDependent = function(oElement) { this.addAggregation("dependents", oElement, true); return this; // explicitly return 'this' to fix controls that override addAggregation wrongly }; Element.prototype.removeDependent = function(vElement) { return this.removeAggregation("dependents", vElement, true); }; Element.prototype.removeAllDependents = function() { return this.removeAllAggregation("dependents", true); }; Element.prototype.destroyDependents = function() { this.destroyAggregation("dependents", true); return this; // explicitly return 'this' to fix controls that override destroyAggregation wrongly }; /// cyclic dependency //jQuery.sap.require("sap.ui.core.TooltipBase"); /// cyclic dependency /** * This triggers immediate rerendering of its parent and thus of itself and its children. * * As <code>sap.ui.core.Element</code> "bubbles up" the rerender, changes to * child-<code>Elements</code> will also result in immediate rerendering of the whole sub tree. * @protected */ Element.prototype.rerender = function() { if (this.oParent) { this.oParent.rerender(); } }; /** * Returns the UI area of this element, if any. * * @return {sap.ui.core.UIArea} The UI area of this element or null * @private */ Element.prototype.getUIArea = function() { return this.oParent ? this.oParent.getUIArea() : null; }; /** * Cleans up the resources associated with this element and all its children. * * After an element has been destroyed, it can no longer be used in the UI! * * Applications should call this method if they don't need the element any longer. * * @param {boolean} [bSuppressInvalidate=false] If <code>true</code>, this ManagedObject and all its ancestors won't be invalidated. * <br>This flag should be used only during control development to optimize invalidation procedures. * It should not be used by any application code. * @public */ Element.prototype.destroy = function(bSuppressInvalidate) { // ignore repeated calls if (this.bIsDestroyed) { return; } // determine whether parent exists or not var bHasNoParent = !this.getParent(); // update the focus information (potentially) stored by the central UI5 focus handling Element._updateFocusInfo(this); ManagedObject.prototype.destroy.call(this, bSuppressInvalidate); // wrap custom data API to avoid creating new objects this.data = noCustomDataAfterDestroy; // exit early if there is no control DOM to remove var oDomRef = this.getDomRef(); if (!oDomRef) { return; } // Determine whether to remove the control DOM from the DOM Tree or not: // If parent invalidation is not possible, either bSuppressInvalidate=true or there is no parent to invalidate then we must remove the control DOM synchronously. // Controls that implement marker interface sap.ui.core.PopupInterface are by contract not rendered by their parent so we cannot keep the DOM of these controls. // If the control is destroyed while its content is in the preserved area then we must remove DOM synchronously since we cannot invalidate the preserved area. var bKeepDom = (bSuppressInvalidate === "KeepDom"); if (bSuppressInvalidate === true || (!bKeepDom && bHasNoParent) || this.isA("sap.ui.core.PopupInterface") || RenderManager.isPreservedContent(oDomRef)) { jQuery(oDomRef).remove(); } else { // Make sure that the control DOM won't get preserved after it is destroyed (even if bSuppressInvalidate="KeepDom") oDomRef.removeAttribute("data-sap-ui-preserve"); if (!bKeepDom) { // On destroy we do not remove the control DOM synchronously and just let the invalidation happen on the parent. // At the next tick of the RenderManager, control DOM nodes will be removed via rerendering of the parent anyway. // To make this new behavior more compatible we are changing the id of the control's DOM and all child nodes that start with the control id. oDomRef.id = "sap-ui-destroyed-" + this.getId(); for (var i = 0, aDomRefs = oDomRef.querySelectorAll('[id^="' + this.getId() + '-"]'); i < aDomRefs.length; i++) { aDomRefs[i].id = "sap-ui-destroyed-" + aDomRefs[i].id; } } } }; /* * Class <code>sap.ui.core.Element</code> intercepts fireEvent calls to enforce an 'id' property * and to notify others like interaction detection etc. */ Element.prototype.fireEvent = function(sEventId, mParameters, bAllowPreventDefault, bEnableEventBubbling) { if (this.hasListeners(sEventId)) { Interaction.notifyStepStart(sEventId, this); } // get optional parameters right if (typeof mParameters === 'boolean') { bEnableEventBubbling = bAllowPreventDefault; bAllowPreventDefault = mParameters; mParameters = null; } mParameters = mParameters || {}; mParameters.id = mParameters.id || this.getId(); if (Element._interceptEvent) { Element._interceptEvent(sEventId, this, mParameters); } return ManagedObject.prototype.fireEvent.call(this, sEventId, mParameters, bAllowPreventDefault, bEnableEventBubbling); }; /** * Intercepts an event. This method is meant for private usages. Apps are not supposed to used it. * It is created for an experimental purpose. * Implementation should be injected by outside. * * @param {string} sEventId the name of the event * @param {sap.ui.core.Element} oElement the element itself * @param {object} mParameters The parameters which complement the event. Hooks must not modify the parameters. * @function * @private * @ui5-restricted * @experimental Since 1.58 */ Element._interceptEvent = undefined; /** * Adds a delegate that listens to the events of this element. * * Note that the default behavior (delegate attachments are not cloned when a control is cloned) is usually the desired behavior in control development * where each control instance typically creates a delegate and adds it to itself. (As opposed to application development where the application may add * one delegate to a template and then expects aggregation binding to add the same delegate to all cloned elements.) * * To avoid double registrations, all registrations of the given delegate are first removed and then the delegate is added. * * @param {object} oDelegate the delegate object * @param {boolean} [bCallBefore=false] if true, the delegate event listeners are called before the event listeners of the element; default is "false". In order to also set bClone, this parameter must be given. * @param {object} [oThis=oDelegate] if given, this object will be the "this" context in the listener methods; default is the delegate object itself * @param {boolean} [bClone=false] if true, this delegate will also be attached to any clones of this element; default is "false" * @returns {this} Returns <code>this</code> to allow method chaining * @private */ Element.prototype.addDelegate = function (oDelegate, bCallBefore, oThis, bClone) { assert(oDelegate, "oDelegate must be not null or undefined"); if (!oDelegate) { return this; } this.removeDelegate(oDelegate); // shift parameters if (typeof bCallBefore === "object") { bClone = oThis; oThis = bCallBefore; bCallBefore = false; } if (typeof oThis === "boolean") { bClone = oThis; oThis = undefined; } (bCallBefore ? this.aBeforeDelegates : this.aDelegates).push({oDelegate:oDelegate, bClone: !!bClone, vThis: ((oThis === this) ? true : oThis)}); // special case: if this element is the given context, set a flag, so this also works after cloning (it should be the cloned element then, not the given one) return this; }; /** * Removes the given delegate from this element. * * This method will remove all registrations of the given delegate, not only one. * If the delegate was marked to be cloned and this element has been cloned, the delegate will not be removed from any clones. * * @param {object} oDelegate the delegate object * @returns {this} Returns <code>this</code> to allow method chaining * @private */ Element.prototype.removeDelegate = function (oDelegate) { var i; for (i = 0; i < this.aDelegates.length; i++) { if (this.aDelegates[i].oDelegate == oDelegate) { this.aDelegates.splice(i, 1); i--; // One element removed means the next element now has the index of the current one } } for (i = 0; i < this.aBeforeDelegates.length; i++) { if (this.aBeforeDelegates[i].oDelegate == oDelegate) { this.aBeforeDelegates.splice(i, 1); i--; // One element removed means the next element now has the index of the current one } } return this; }; /** * Adds a delegate that can listen to the browser-, pseudo- and framework events that are handled by this * <code>Element</code> (as opposed to events which are fired by this <code>Element</code>). * * Delegates are simple objects that can have an arbitrary number of event handler methods. See the section * "Handling of Events" in the {@link #constructor} documentation to learn how events will be dispatched * and how event handler methods have to be named to be found. * * If multiple delegates are registered for the same element, they will be called in the order of their * registration. Double registrations are prevented. Before a delegate is added, all registrations of the same * delegate (no matter what value for <code>oThis</code> was used for their registration) are removed and only * then the delegate is added. Note that this might change the position of the delegate in the list of delegates. * * When an element is cloned, all its event delegates will be added to the clone. This behavior is well-suited * for applications which want to add delegates that also work with templates in aggregation bindings. * For control development, the internal <code>addDelegate</code> method may be more suitable. Delegates added * via that method are not cloned automatically, as typically each control instance takes care of adding its * own delegates. * * <strong>Important:</strong> If event delegates were added, the delegate will still be called even if * the event was processed and/or cancelled via <code>preventDefault</code> by the Element or another event delegate. * <code>preventDefault</code> only prevents the event from bubbling. * It should be checked e.g. in the event delegate's listener whether an Element is still enabled via <code>getEnabled</code>. * Additionally there might be other things that delegates need to check depending on the event * (e.g. not adding a key twice to an output string etc.). * * See {@link topic:bdf3e9818cd84d37a18ee5680e97e1c1 Event Handler Methods} for a general explanation of * event handling in controls. * * @example <caption>Adding a delegate for the keydown and afterRendering event</caption> * <pre> * var oDelegate = { * onkeydown: function(){ * // Act when the keydown event is fired on the element * }, * onAfterRendering: function(){ * // Act when the afterRendering event is fired on the element * } * }; * oElement.addEventDelegate(oDelegate); * </pre> * * @param {object} oDelegate The delegate object which consists of the event handler names and the corresponding event handler functions * @param {object} [oThis=oDelegate] If given, this object will be the "this" context in the listener methods; default is the delegate object itself * @returns {this} Returns <code>this</code> to allow method chaining * @since 1.9.0 * @public */ Element.prototype.addEventDelegate = function (oDelegate, oThis) { return this.addDelegate(oDelegate, false, oThis, true); }; /** * Removes the given delegate from this element. * * This method will remove all registrations of the given delegate, not only one. * * @example <caption>Removing a delegate for the keydown and afterRendering event. The delegate object which was used when adding the event delegate</caption> * <pre> * var oDelegate = { * onkeydown: function(){ * // Act when the keydown event is fired on the element * }, * onAfterRendering: function(){ * // Act when the afterRendering event is fired on the element * } * }; * oElement.removeEventDelegate(oDelegate); * </pre> * @param {object} oDelegate The delegate object which consists of the event handler names and the corresponding event handler functions * @returns {this} Returns <code>this</code> to allow method chaining * @since 1.9.0 * @public */ Element.prototype.removeEventDelegate = function (oDelegate) { return this.removeDelegate(oDelegate); }; /** * Returns the DOM Element that should get the focus. * * To be overwritten by the specific control method. * * @return {Element} Returns the DOM Element that should get the focus * @protected */ Element.prototype.getFocusDomRef = function () { return this.getDomRef() || null; }; function getAncestorScrollPositions(oDomRef) { var oParentDomRef, aScrollHierarchy = []; oParentDomRef = oDomRef.parentNode; while (oParentDomRef) { aScrollHierarchy.push({ node: oParentDomRef, scrollLeft: oParentDomRef.scrollLeft, scrollTop: oParentDomRef.scrollTop }); oParentDomRef = oParentDomRef.parentNode; } return aScrollHierarchy; } function restoreScrollPositions(aScrollHierarchy) { aScrollHierarchy.forEach(function(oScrollInfo) { var oDomRef = oScrollInfo.node; if (oDomRef.scrollLeft !== oScrollInfo.scrollLeft) { oDomRef.scrollLeft = oScrollInfo.scrollLeft; } if (oDomRef.scrollTop !== oScrollInfo.scrollTop) { oDomRef.scrollTop = oScrollInfo.scrollTop; } }); } /** * Sets the focus to the stored focus DOM reference. * * @param {object} [oFocusInfo={}] Options for setting the focus * @param {boolean} [oFocusInfo.preventScroll=false] @since 1.60 if it's set to true, the focused * element won't be shifted into the viewport if it's not completely visible before the focus is set * @public */ Element.prototype.focus = function (oFocusInfo) { var oFocusDomRef = this.getFocusDomRef(), aScrollHierarchy = []; oFocusInfo = oFocusInfo || {}; if (oFocusDomRef) { // save the scroll position of all ancestor DOM elements // before the focus is set, because preventScroll is not supported by the following browsers if (Device.browser.safari || Device.browser.msie || Device.browser.edge) { if (oFocusInfo.preventScroll === true) { aScrollHierarchy = getAncestorScrollPositions(oFocusDomRef); } oFocusDomRef.focus(); if (aScrollHierarchy.length > 0) { // restore the scroll position if it's changed after setting focus // Safari, IE11 and Edge need a little delay to get the scroll position updated setTimeout(restoreScrollPositions.bind(null, aScrollHierarchy), 0); } } else { oFocusDomRef.focus(oFocusInfo); } } }; /** * Returns an object representing the serialized focus information. * * To be overwritten by the specific control method. * * @returns {object} an object representing the serialized focus information * @protected */ Element.prototype.getFocusInfo = function () { return {id:this.getId()}; }; /** * Applies the focus info. * * To be overwritten by the specific control method. * * @param {object} oFocusInfo Focus info object as returned by {@link getFocusInfo} * @param {boolean} [oFocusInfo.preventScroll=false] @since 1.60 if it's set to true, the focused * element won't be shifted into the viewport if it's not completely visible before the focus is set * @returns {this} Returns <code>this</code> to allow method chaining * @protected */ Element.prototype.applyFocusInfo = function (oFocusInfo) { this.focus(oFocusInfo); return this; }; /** * @see sap.ui.core.Element#setTooltip * @private */ Element.prototype._refreshTooltipBaseDelegate = function (oTooltip) { var oOldTooltip = this.getTooltip(); // if the old tooltip was a Tooltip object, remove it as a delegate if (BaseObject.isA(oOldTooltip, "sap.ui.core.TooltipBase")) { this.removeDelegate(oOldTooltip); } // if the new tooltip is a Tooltip object, add it as a delegate if (BaseObject.isA(oTooltip, "sap.ui.core.TooltipBase")) { oTooltip._currentControl = this; this.addDelegate(oTooltip); } }; /** * Sets a new tooltip for this object. * * The tooltip can either be a simple string (which in most cases will be rendered as the * <code>title</code> attribute of this Element) or an instance of {@link sap.ui.core.TooltipBase}. * * If a new tooltip is set, any previously set tooltip is deactivated. * * @param {string|sap.ui.core.TooltipBase} vTooltip New tooltip * @returns {this} Returns <code>this</code> to allow method chaining * @public */ Element.prototype.setTooltip = function(vTooltip) { this._refreshTooltipBaseDelegate(vTooltip); this.setAggregation("tooltip", vTooltip); return this; }; /** * Returns the tooltip for this element if any or an undefined value. * The tooltip can either be a simple string or a subclass of * {@link sap.ui.core.TooltipBase}. * * Callers that are only interested in tooltips of type string (e.g. to render * them as a <code>title</code> attribute), should call the convenience method * {@link #getTooltip_AsString} instead. If they want to get a tooltip text no * matter where it comes from (be it a string tooltip or the text from a TooltipBase * instance) then they could call {@link #getTooltip_Text} instead. * * @return {string|sap.ui.core.TooltipBase} The tooltip for this Element. * @public */ Element.prototype.getTooltip = function() { return this.getAggregation("tooltip"); }; Element.runWithPreprocessors = ManagedObject.runWithPreprocessors; /** * Returns the tooltip for this element but only if it is a simple string. * Otherwise an undefined value is returned. * * @return {string} string tooltip or undefined * @public */ Element.prototype.getTooltip_AsString = function() { var oTooltip = this.getTooltip(); if (typeof oTooltip === "string" || oTooltip instanceof String ) { return oTooltip; } return undefined; }; /** * Returns the main text for the current tooltip or undefined if there is no such text. * If the tooltip is an object derived from sap.ui.core.Tooltip, then the text property * of that object is returned. Otherwise the object itself is returned (either a string * or undefined or null). * * @return {string} text of the current tooltip or undefined * @public */ Element.prototype.getTooltip_Text = function() { var oTooltip = this.getTooltip(); if (oTooltip && typeof oTooltip.getText === "function" ) { return oTooltip.getText(); } return oTooltip; }; /** * Destroys the tooltip in the aggregation * named <code>tooltip</code>. * @returns {this} <code>this</code> to allow method chaining * @public * @name sap.ui.core.Element#destroyTooltip * @function */ /** * Returns the runtime metadata for this UI element. * * When using the defineClass method, this function is automatically created and returns * a runtime representation of the design time metadata. * * @function * @name sap.ui.core.Element.prototype.getMetadata * @return {object} runtime metadata * @public */ // sap.ui.core.Element.prototype.getMetadata = sap.ui.base.Object.ABSTRACT_METHOD; // ---- data container ---------------------------------- // Note: the real class documentation can be found in sap/ui/core/CustomData so that the right module is // shown in the API reference. A reduced copy of the class documentation and the documentation of the // settings has to be provided here, close to the runtime metadata to allow extracting the metadata. /** * @class * Contains a single key/value pair of custom data attached to an <code>Element</code>. * @public * @alias sap.ui.core.CustomData * @ui5-metamodel This control/element also will be described in the UI5 (legacy) designtime metamodel * @synthetic */ var CustomData = Element.extend("sap.ui.core.CustomData", /** @lends sap.ui.core.CustomData.prototype */ { metadata : { library : "sap.ui.core", properties : { /** * The key of the data in this CustomData object. * When the data is just stored, it can be any string, but when it is to be written to HTML * (<code>writeToDom == true</code>) then it must also be a valid HTML attribute name. * It must conform to the {@link sap.ui.core.ID} type and may contain no colon. To avoid collisions, * it also may not start with "sap-ui". When written to HTML, the key is prefixed with "data-". * If any restriction is violated, a warning will be logged and nothing will be written to the DOM. */ key : {type : "string", group : "Data", defaultValue : null}, /** * The data stored in this CustomData object. * When the data is just stored, it can be any JS type, but when it is to be written to HTML * (<code>writeToDom == true</code>) then it must be a string. If this restriction is violated, * a warning will be logged and nothing will be written to the DOM. */ value : {type : "any", group : "Data", defaultValue : null}, /** * If set to "true" and the value is of type "string" and the key conforms to the documented restrictions, * this custom data is written to the HTML root element of the control as a "data-*" attribute. * If the key is "abc" and the value is "cde", the HTML will look as follows: * * <pre> * &lt;SomeTag ... data-abc="cde" ... &gt; * </pre> * * Thus the application can provide stable attributes by data binding which can be used for styling or * identification purposes. * * <b>ATTENTION:</b> use carefully to not create huge attributes or a large number of them. * @since 1.9.0 */ writeToDom : {type : "boolean", group : "Data", defaultValue : false} }, designtime: "sap/ui/core/designtime/CustomData.designtime" }}); CustomData.prototype.setValue = function(oValue) { this.setProperty("value", oValue, true); var oControl = this.getParent(); if (oControl && oControl.getDomRef()) { var oCheckResult = this._checkWriteToDom(oControl); if (oCheckResult) { // update DOM directly oControl.$().attr(oCheckResult.key, oCheckResult.value); } } return this; }; CustomData.prototype._checkWriteToDom = function(oRelated) { if (!this.getWriteToDom()) { return null; } var key = this.getKey(); var value = this.getValue(); function error(reason) { Log.error("CustomData with key " + key + " should be written to HTML of " + oRelated + " but " + reason); return null; } if (typeof value != "string") { return error("the value is not a string."); } var ID = DataType.getType("sap.ui.core.ID"); if (!(ID.isValid(key)) || (key.indexOf(":") != -1)) { return error("the key is not valid (must be a valid sap.ui.core.ID without any colon)."); } if (key == F6Navigation.fastNavigationKey) { value = /^\s*(x|true)\s*$/i.test(value) ? "true" : "false"; // normalize values } else if (key.indexOf("sap-ui") == 0) { return error("the key is not valid (may not start with 'sap-ui')."); } return {key: "data-" + key, value: value}; }; /** * Returns the data object with the given key */ function findCustomData(element, key) { var aData = element.getAggregation("customData"); if (aData) { for (var i = 0; i < aData.length; i++) { if (aData[i].getKey() == key) { return aData[i]; } } } return null; } /** * Contains the data modification logic */ function setCustomData(element, key, value, writeToDom) { // DELETE if (value === null) { // delete this property var dataObject = findCustomData(element, key); if (!dataObject) { return; } var dataCount = element.getAggregation("customData").length; if (dataCount == 1) { element.destroyAggregation("customData", true); // destroy if there is no other data } else { element.removeAggregation("customData", dataObject, true); dataObject.destroy(); } // ADD or CHANGE } else { var dataObject = findCustomData(element, key); if (dataObject) { dataObject.setValue(value); dataObject.setWriteToDom(writeToDom); } else { var dataObject = new CustomData({key:key,value:value, writeToDom:writeToDom}); element.addAggregation("customData", dataObject, true); } } } /** * Retrieves, modifies or removes custom data attached to an <code>Element</code>. * * Usages: * <h4>Setting the value for a single key</h4> * <pre> * data("myKey", myData) * </pre> * Attaches <code>myData</code> (which can be any JS data type, e.g. a number, a string, an object, or a function) * to this element, under the given key "myKey". If the key already exists,the value will be updated. * * * <h4>Setting a value for a single key (rendered to the DOM)</h4> * <pre> * data("myKey", myData, writeToDom) * </pre> * Attaches <code>myData</code> to this element, under the given key "myKey" and (if <code>writeToDom</code> * is true) writes key and value to the HTML. If the key already exists,the value will be updated. * While <code>oValue</code> can be any JS data type to be attached, it must be a string to be also * written to DOM. The key must also be a valid HTML attribute name (it must conform to <code>sap.ui.core.ID</code> * and may contain no colon) and may not start with "sap-ui". When written to HTML, the key is prefixed with "data-". * * * <h4>Getting the value for a single key</h4> * <pre> * data("myKey") * </pre> * Retrieves whatever data has been attached to this element (using the key "myKey") before. * * * <h4>Removing the value for a single key</h4> * <pre> * data("myKey", null) * </pre> * Removes whatever data has been attached to this element (using the key "myKey") before. * * * <h4>Removing all custom data for all keys</h4> * <pre> * data(null) * </pre> * * * <h4>Getting all custom data values as a plain object</h4> * <pre> * data() * </pre> * Returns all data, as a map-like object, property names are keys, property values are values. * * * <h4>Setting multiple key/value pairs in a single call</h4> * <pre> * data({"myKey1": myData, "myKey2": null}) * </pre> * Attaches <code>myData</code> (using the key "myKey1" and removes any data that had been * attached for key "myKey2". * * @see See chapter {@link topic:91f0c3ee6f4d1014b6dd926db0e91070 Custom Data - Attaching Data Objects to Controls} * in the documentation. * * @param {string|Object<string,any>|null} [vKeyOrData] * Single key to set or remove, or an object with key/value pairs or <code>null</code> to remove * all custom data * @param {string|any} [vValue] * Value to set or <code>null</code> to remove the corresponding custom data * @param {boolean} [bWriteToDom=false] * Whether this custom data entry should be written to the DOM during rendering * @returns {Object<string,any>|any|null|sap.ui.core.Element} * A map with all custom data, a custom data value for a single specified key or <code>null</code> * when no custom data exists for such a key or this element when custom data was to be removed. * @throws {TypeError} * When the type of the given parameters