UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

370 lines (323 loc) 15.2 kB
/*! * OpenUI5 * (c) Copyright 2026 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ // Provides class sap.ui.base.EventProvider sap.ui.define(['./Event', './Object', "sap/base/assert", "sap/base/Log"], function(Event, BaseObject, assert, Log) { "use strict"; /** * Creates an instance of EventProvider. * * @class Provides eventing capabilities for objects like attaching or detaching event handlers for events which are notified when events are fired. * * @extends sap.ui.base.Object * @author SAP SE * @version 1.147.0 * @public * @alias sap.ui.base.EventProvider */ var EventProvider = BaseObject.extend("sap.ui.base.EventProvider", /** @lends sap.ui.base.EventProvider.prototype */ { constructor : function() { BaseObject.call(this); /** * A map of arrays of event registrations keyed by the event names * @private */ this.mEventRegistry = {}; } }); var EVENT__LISTENERS_CHANGED = "EventHandlerChange"; /** * Attaches an event handler to the event with the given identifier. * * @param {string} * sEventId The identifier of the event to listen for * @param {object} * [oData] An object that will be passed to the handler along with the event object when the event is fired * @param {function} * fnFunction The handler function to call when the event occurs. This function will be called in the context of the * <code>oListener</code> instance (if present) or on the event provider instance. The event * object ({@link sap.ui.base.Event}) is provided as first argument of the handler. Handlers must not change * the content of the event. The second argument is the specified <code>oData</code> instance (if present). * @param {object} * [oListener] The object that wants to be notified when the event occurs (<code>this</code> context within the * handler function). If it is not specified, the handler function is called in the context of the event provider. * @returns {this} Returns <code>this</code> to allow method chaining * @public */ EventProvider.prototype.attachEvent = function(sEventId, oData, fnFunction, oListener) { var mEventRegistry = this.mEventRegistry; assert(typeof (sEventId) === "string" && sEventId, "EventProvider.attachEvent: sEventId must be a non-empty string"); if (typeof (oData) === "function") { //one could also increase the check in the line above //if(typeof(oData) === "function" && oListener === undefined) { oListener = fnFunction; fnFunction = oData; oData = undefined; } assert(typeof (fnFunction) === "function", "EventProvider.attachEvent: fnFunction must be a function"); assert(!oListener || typeof (oListener) === "object", "EventProvider.attachEvent: oListener must be empty or an object"); oListener = oListener === this ? undefined : oListener; var aEventListeners = mEventRegistry[sEventId]; if ( !Array.isArray(aEventListeners) ) { aEventListeners = mEventRegistry[sEventId] = []; } aEventListeners.push({oListener:oListener, fFunction:fnFunction, oData: oData}); // Inform interested parties about changed EventHandlers if ( mEventRegistry[EVENT__LISTENERS_CHANGED] ) { this.fireEvent(EVENT__LISTENERS_CHANGED, {EventId: sEventId, type: 'listenerAttached', listener: oListener, func: fnFunction, data: oData}); } return this; }; /** * Attaches an event handler, called one time only, to the event with the given identifier. * * When the event occurs, the handler function is called and the handler registration is automatically removed afterwards. * * @param {string} * sEventId The identifier of the event to listen for * @param {object} * [oData] An object that will be passed to the handler along with the event object when the event is fired * @param {function} * fnFunction The handler function to call when the event occurs. This function will be called in the context of the * <code>oListener</code> instance (if present) or on the event provider instance. The event * object ({@link sap.ui.base.Event}) is provided as first argument of the handler. Handlers must not change * the content of the event. The second argument is the specified <code>oData</code> instance (if present). * @param {object} * [oListener] The object that wants to be notified when the event occurs (<code>this</code> context within the * handler function). If it is not specified, the handler function is called in the context of the event provider. * @returns {this} Returns <code>this</code> to allow method chaining * @public */ EventProvider.prototype.attachEventOnce = function(sEventId, oData, fnFunction, oListener) { if (typeof (oData) === "function") { oListener = fnFunction; fnFunction = oData; oData = undefined; } assert(typeof (fnFunction) === "function", "EventProvider.attachEventOnce: fnFunction must be a function"); var fnOnce = function() { this.detachEvent(sEventId, fnOnce); // ‘this’ is always the control, due to the context ‘undefined’ in the attach call below fnFunction.apply(oListener || this, arguments); // needs to do the same resolution as in fireEvent }; fnOnce.oOriginal = { fFunction: fnFunction, oListener: oListener, oData: oData }; this.attachEvent(sEventId, oData, fnOnce, undefined); // a listener of ‘undefined’ enforce a context of ‘this’ even after clone return this; }; /** * Removes a previously attached event handler from the event with the given identifier. * * The passed parameters must match those used for registration with {@link #attachEvent} beforehand. * * @param {string} * sEventId The identifier of the event to detach from * @param {function} * fnFunction The handler function to detach from the event * @param {object} * [oListener] The object that wanted to be notified when the event occurred * @returns {this} Returns <code>this</code> to allow method chaining * @public */ EventProvider.prototype.detachEvent = function(sEventId, fnFunction, oListener) { var mEventRegistry = this.mEventRegistry; assert(typeof (sEventId) === "string" && sEventId, "EventProvider.detachEvent: sEventId must be a non-empty string" ); assert(typeof (fnFunction) === "function", "EventProvider.detachEvent: fnFunction must be a function"); assert(!oListener || typeof (oListener) === "object", "EventProvider.detachEvent: oListener must be empty or an object"); var aEventListeners = mEventRegistry[sEventId]; if ( !Array.isArray(aEventListeners) ) { return this; } var oFound, oOriginal; oListener = oListener === this ? undefined : oListener; //PERFOPT use array. remember length to not re-calculate over and over again for (var i = 0, iL = aEventListeners.length; i < iL; i++) { //PERFOPT check for identity instead of equality... avoid type conversion if (aEventListeners[i].fFunction === fnFunction && aEventListeners[i].oListener === oListener) { oFound = aEventListeners[i]; aEventListeners.splice(i,1); break; } } // If no listener was found, look for original listeners of attachEventOnce if (!oFound) { for (var i = 0, iL = aEventListeners.length; i < iL; i++) { oOriginal = aEventListeners[i].fFunction.oOriginal; if (oOriginal && oOriginal.fFunction === fnFunction && oOriginal.oListener === oListener) { oFound = oOriginal; aEventListeners.splice(i,1); break; } } } // If we just deleted the last registered EventHandler, remove the whole entry from our map. if (aEventListeners.length == 0) { delete mEventRegistry[sEventId]; } if (oFound && mEventRegistry[EVENT__LISTENERS_CHANGED] ) { // Inform interested parties about changed EventHandlers this.fireEvent(EVENT__LISTENERS_CHANGED, {EventId: sEventId, type: 'listenerDetached', listener: oFound.oListener, func: oFound.fFunction, data: oFound.oData}); } return this; }; /** * Fires an {@link sap.ui.base.Event event} with the given settings and notifies all attached event handlers. * * @param {string} * sEventId The identifier of the event to fire * @param {object} * [oParameters] Parameters which should be carried by the event * @param {boolean} * [bAllowPreventDefault] Defines whether function <code>preventDefault</code> is supported on the fired event * @param {boolean} * [bEnableEventBubbling] Defines whether event bubbling is enabled on the fired event. Set to <code>true</code> the event is also forwarded to the parent(s) * of the event provider ({@link #getEventingParent}) until the bubbling of the event is stopped or no parent is available anymore. * @return {this|boolean} Returns <code>this</code> to allow method chaining. When <code>preventDefault</code> is supported on the fired event * the function returns <code>true</code> if the default action should be executed, <code>false</code> otherwise. * @protected */ EventProvider.prototype.fireEvent = function(sEventId, oParameters, bAllowPreventDefault, bEnableEventBubbling) { // get optional parameters right if (typeof oParameters === "boolean") { bEnableEventBubbling = bAllowPreventDefault; bAllowPreventDefault = oParameters; } /* eslint-disable consistent-this */ var oProvider = this, /* eslint-enable consistent-this */ bPreventDefault = false, aEventListeners, oEvent, i, iL, oInfo; do { aEventListeners = oProvider.mEventRegistry[sEventId]; if ( Array.isArray(aEventListeners) ) { // avoid issues with 'concurrent modification' (e.g. if an event listener unregisters itself). aEventListeners = aEventListeners.slice(); oEvent = new Event(sEventId, this, oParameters); for (i = 0, iL = aEventListeners.length; i < iL; i++) { oInfo = aEventListeners[i]; const vResult = oInfo.fFunction.call(oInfo.oListener || oProvider, oEvent, oInfo.oData); // proper error handling for rejected promises if (typeof vResult?.then === "function") { vResult.catch?.((err) => { Log.error(`EventProvider.fireEvent: Event Listener for event '${sEventId}' failed during execution.`, err); }); } } bEnableEventBubbling = bEnableEventBubbling && !oEvent.bCancelBubble; } oProvider = oProvider.getEventingParent(); } while (bEnableEventBubbling && oProvider); if ( oEvent ) { // remember 'prevent default' state before returning event to the pool bPreventDefault = oEvent.bPreventDefault; } // return 'execute default' flag only when 'prevent default' has been enabled, otherwise return 'this' (for compatibility) return bAllowPreventDefault ? !bPreventDefault : this; }; /** * Returns whether there are any registered event handlers for the event with the given identifier. * * @param {string} sEventId The identifier of the event * @return {boolean} Whether there are any registered event handlers * @protected */ EventProvider.prototype.hasListeners = function(sEventId) { return !!this.mEventRegistry[sEventId]; }; /** * Returns the list of events currently having listeners attached. * * Introduced for lightspeed support to ensure that only relevant events are attached to the LS-world. * * This is a static method to avoid the pollution of the Element/Control namespace. * As the callers are limited and known and for performance reasons the internal event registry * is returned. It contains more information than necessary, but needs no expensive conversion. * * @param {sap.ui.base.EventProvider} oEventProvider The event provider to get the registered events for * @return {object} the list of events currently having listeners attached * @private * @static */ EventProvider.getEventList = function(oEventProvider) { return oEventProvider.mEventRegistry; }; /** * Checks whether the given event provider has the given listener registered for the given event. * * Returns true if function and listener object both match the corresponding parameters of * at least one listener registered for the named event. * * @param {sap.ui.base.EventProvider} * oEventProvider The event provider to get the registered events for * @param {string} * sEventId The identifier of the event to check listeners for * @param {function} * fnFunction The handler function to check for * @param {object} * [oListener] The listener object to check for * @return {boolean} Returns whether a listener with the same parameters exists * @private * @ui5-restricted sap.ui.base, sap.ui.core */ EventProvider.hasListener = function (oEventProvider, sEventId, fnFunction, oListener) { assert(typeof (sEventId) === "string" && sEventId, "EventProvider.hasListener: sEventId must be a non-empty string" ); assert(typeof (fnFunction) === "function", "EventProvider.hasListener: fnFunction must be a function"); assert(!oListener || typeof (oListener) === "object", "EventProvider.hasListener: oListener must be empty or an object"); var aEventListeners = oEventProvider && oEventProvider.mEventRegistry[sEventId]; if ( aEventListeners ) { for (var i = 0, iL = aEventListeners.length; i < iL; i++) { if (aEventListeners[i].fFunction === fnFunction && aEventListeners[i].oListener === oListener) { return true; } } } return false; }; /** * Returns the parent in the eventing hierarchy of this object. * * Per default this returns null, but if eventing is used in objects, which are hierarchically * structured, this can be overwritten to make the object hierarchy visible to the eventing and * enables the use of event bubbling within this object hierarchy. * * @return {sap.ui.base.EventProvider|null} The parent event provider * @protected */ EventProvider.prototype.getEventingParent = function() { return null; }; /** * Returns a string representation of this object. * * In case there is no class or id information, a simple static string is returned. * Subclasses should override this method. * * @return {string} A string description of this event provider * @public */ EventProvider.prototype.toString = function() { if ( this.getMetadata ) { return "EventProvider " + this.getMetadata().getName(); } else { return "EventProvider"; } }; /** * Cleans up the internal structures and removes all event handlers. * * The object must not be used anymore after destroy was called. * * @see sap.ui.base.Object#destroy * @public */ EventProvider.prototype.destroy = function() { this.mEventRegistry = {}; BaseObject.prototype.destroy.apply(this, arguments); }; return EventProvider; });