UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

714 lines (657 loc) 27.2 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 class sap.ui.base.ManagedObjectObserver. sap.ui.define([ 'sap/ui/base/Object', 'sap/ui/base/ManagedObject', 'sap/ui/base/EventProvider', "sap/base/util/array/uniqueSort", 'sap/base/util/deepExtend' ], function(BaseObject, ManagedObject, EventProvider, uniqueSort, deepExtend) { "use strict"; /** * Constructor for a new ManagedObjectObserver. * * @classdesc * Use the <code>ManagedObjectObserver</code> to get notified when properties, aggregations or associations of a * <code>ManagedObject</code> instance have changed. * * Use the {@link #observe} method to add instances of ManagedObject that should be observed or to enhance * the set of observed properties, aggregations etc. for an already observed instance. * * Use the {@link #unobserve} method to stop observing an instance of ManagedObject or to reduce the set of * observed properties, aggregations etc. for an observed instance. * * Use the {@link #disconnect} method to completely stop observing all instances of ManagedObject hat previously * had been added to this observer. * * The only parameter to the constructor is a function <code>fnCallback</code> which will be called for every * observed change. Depending on the type of the change, different change objects are passed to the callback: * * <h4>Property Change</h4> * {string} * change.name the name of the property that changed<br> * {string} * change.type 'property'<br> * {object} * change.object the managed object instance on which the change occurred<br> * {any} * change.old the old value<br> * {any} * change.current the new value<br> * * <h4>Aggregation Change</h4> * {string} * change.name the name of the aggregation that changed<br> * {string} * change.type 'aggregation'<br> * {object} * change.object the managed object instance on which the change occurred<br> * {any} * change.mutation 'remove' or 'insert'<br> * {sap.ui.base.ManagedObject} * change.child the child managed object instance<br> * * <h4>Association Change</h4> * {string} * change.name the name of the association that changed<br> * {string} * change.type 'association'<br> * {object} * change.object the managed object instance on which the change occurred<br> * {any} * change.mutation 'remove' or 'insert'<br> * {string|string[]} * change.ids the ids that changed<br> * * <h4>Event Registry Change</h4> * {string} * change.name the name of the event that changed<br> * {string} * change.type 'event'<br> * {object} * change.object the managed object instance on which the change occurred<br> * {any} * change.mutation 'remove' or 'insert'<br> * {object} * change.listener the listener object<br> *{object} * change.func the listeners function<br> *{object} * change.data the events data<br> * * <h4>Binding Change</h4> * {string} * change.name the name of the binding that changed<br> * {string} * change.type 'binding'<br> * {object} * change.object the managed object instance on which the change occurred<br> * {any} * change.mutation 'prepare', 'ready' or 'remove'<br> * {object} * change.bindingInfo the binding info object<br> * {string} * change.memberType 'property' or 'aggregation'<br> * * <h4>destroy managed Object</h4> * {string} * change.type 'destroy'<br> * {object} * change.object the managed object instance on which the change occurred<br> * * @param {function} fnCallback Callback function for this observer, to be called whenever a change happens * * @since 1.50.0 * @private * @ui5-restricted sap.ui.model.base * @constructor * @alias sap.ui.base.ManagedObjectObserver */ var ManagedObjectObserver = BaseObject.extend("sap.ui.base.ManagedObjectObserver", { constructor: function (fnCallback) { if (!fnCallback && typeof fnCallback !== "function") { throw new Error("Missing callback function in ManagedObjectObserver constructor"); } this._fnCallback = fnCallback; } }); /** * Starts observing the given object. A configuration is used to specify the meta data settings that should be observed. * Configuration should be as specific as possible to avoid negative performance impact. * Observing all settings (properties, aggregations, associations) should be avoided. * * @param {sap.ui.base.ManagedObject} * oObject the managed object instance to be observed * @param {object} * oConfiguration a mandatory configuration specifying the settings to observe for the object * @param {boolean|string[]} [oConfiguration.properties] * true if all properties should be observed or list of the property names to observe * @param {boolean|string[]} [oConfiguration.aggregations] * true if all aggregations should be observed or list of the aggregation names to observe * @param {boolean|string[]} [oConfiguration.associations] * true if all associations should be observed or list of the association names to observe * @param {boolean|string[]} [oConfiguration.bindings] * true if all bindings should be observed or list of the binding names to observe * @param {boolean|string[]} [oConfiguration.events] * true if all events should be observed or list of the event names to observe * @param {boolean} [oConfiguration.destroy] * true if destroy should be observed * @param {boolean} [oConfiguration.parent] * true if an API parent change should be observed * @throws {TypeError} if the given object is not a ManagedObject and not <code>null</code> or <code>undefined</code> * * @private * @ui5-restricted sap.ui.model.base */ ManagedObjectObserver.prototype.observe = function (oObject, oConfiguration) { if (!(oObject instanceof ManagedObject)) { // silently ignore calls with null or undefined if (oObject == null) { return; } throw new TypeError("ManagedObjectObserver can only handle ManagedObjects, but observe was called for " + oObject); } normalizeConfiguration(oObject, oConfiguration); create(oObject, this, oConfiguration); }; /** * Stops observing the given object. A configuration is used to specify the meta data settings that should be ignored. * Configuration should be as specific as possible to avoid negative performance impact. * * @param {sap.ui.base.ManagedObject} oObject * the managed object instance that was observed * @param {object} oConfiguration * a configuration specifying the settings to stop observing for the object. * If no configuration is provided, the object is unobserved completely * @param {boolean|string[]} [oConfiguration.properties] * true if all properties should be stopped observing or list of the property names to stop observing * @param {boolean|string[]} [oConfiguration.aggregations] * true if all aggregations should be stopped observing or list of the aggregation names to stop observing * @param {boolean|string[]} [oConfiguration.associations] * true if all associations should be stopped observing or list of the association names to stop observing * @param {boolean|string[]} [oConfiguration.bindings] * true if all bindings should be stopped observing or list of the binding names to stop observing * @param {boolean|string[]} [oConfiguration.events] * true if all events should be stopped observing or list of the event names to stop observing * @param {boolean} [oConfiguration.destroy] * true if destroy should be stopped observing * @param {boolean} [oConfiguration.parent] * true if a parent change should be stopped observing * @throws {TypeError} if the given object is not a ManagedObject and not <code>null</code> or <code>undefined</code> * * @private * @ui5-restricted sap.ui.model.base */ ManagedObjectObserver.prototype.unobserve = function (oObject, oConfiguration) { if (!(oObject instanceof ManagedObject)) { // silently ignore calls with null or undefined if (oObject == null) { return; } throw new TypeError("ManagedObjectObserver can only handle ManagedObjects, but unobserve was called for " + oObject); } if (oConfiguration) { normalizeConfiguration(oObject, oConfiguration); } remove(oObject, this, oConfiguration); }; /** * Checks whether a given configuration set for a control is observed. * * All given settings must be observed for the method to return true. * * @param {sap.ui.base.ManagedObject} oObject * the managed object instance that was observed * @param {object} oConfiguration * a configuration specifying the settings to check for the object. * If no configuration is provided it checks if the object observes at least for one property, etc. * @param {boolean|string[]} [oConfiguration.properties] * true if all properties should be checked or list of the property names to check * @param {boolean|string[]} [oConfiguration.aggregations] * true if all aggregations should be checked or list of the aggregation names to check * @param {boolean|string[]} [oConfiguration.associations] * true if all associations should be checked or list of the association names to check * @return {boolean} <code>true</code> if configuration is observed * @throws {TypeError} if the given object is not a ManagedObject and not <code>null</code> or <code>undefined</code> * * @private * @ui5-restricted sap.ui.model.base */ ManagedObjectObserver.prototype.isObserved = function (oObject, oConfiguration) { if (!(oObject instanceof ManagedObject)) { // silently ignore calls with null or undefined if (oObject == null) { return false; } throw new TypeError("ManagedObjectObserver can only handle ManagedObjects, but isObserved was called for " + oObject); } return isObjectObserved(oObject, this, oConfiguration); }; /** * Disconnect the observer from all objects. * @private * @ui5-restricted sap.ui.model.base */ ManagedObjectObserver.prototype.disconnect = function () { destroy(this); }; /** * Returns the current configuration of a given control * * @param {sap.ui.base.ManagedObject} oObject the managed object instance that is observed * @return {object} oConfiguration the configuration of the given object */ ManagedObjectObserver.prototype.getConfiguration = function (oObject) { return getConfiguration(oObject, this); }; // private implementation var Observer = {}, mTargets = Object.create(null); // observer interface for ManagedObject implementation. /** * Called from sap.ui.base.ManagedObject if a property is changed. * * @param {sap.ui.base.ManagedObject} oManagedObject Object that reports a change * @param {string} sName the name of the property that changed * @param {any} vOld the old value of the property * @param {any} vNew the new value of the property * @private * @ui5-restricted sap.ui.base.ManagedObject */ Observer.propertyChange = function (oManagedObject, sName, vOld, vNew) { // managed object does a propertyChange.call(this, sName, vOld, vNew) handleChange("properties", oManagedObject, sName, function () { return { type: "property", old: vOld, current: vNew }; }); }; /** * Called from sap.ui.base.ManagedObject if an aggregation is changed. * * @param {sap.ui.base.ManagedObject} oManagedObject Object that reports a change * @param {string} sName the name of the aggregation that changed * @param {string} sMutation "remove" or "insert" * @param {sap.ui.base.ManagedObject|sap.ui.base.ManagedObject[]} vObjects the removed or inserted object or objects array * @private * @ui5-restricted sap.ui.base.ManagedObject */ Observer.aggregationChange = function (oManagedObject, sName, sMutation, vObjects) { // managed object does an aggregationChange.call(this, sName, sMutation, vObjects) handleChange("aggregations", oManagedObject, sName, function () { return { type: "aggregation", mutation: sMutation, children: Array.isArray(vObjects) ? vObjects : null, child: !Array.isArray(vObjects) ? vObjects : null }; }); }; /** * Called from sap.ui.base.ManagedObject if a parent is changed. * * @param {sap.ui.base.ManagedObject} oManagedObject Object that reports a change * @param {string} sName the name of the triggering parent aggregation * @param {string} sMutation "set" or "unset" * @param {sap.ui.base.ManagedObject} oParent the changed parent * @private * @ui5-restricted sap.ui.base.ManagedObject */ Observer.parentChange = function(oManagedObject, sName, sMutation, oParent) { //managed object does a parent change handle this handleChange("parent", oManagedObject, sName, function () { return { type: "parent", mutation: sMutation, parent: oParent }; }); //As the change of a parent is only possible when an aggregation change is applied //handle this change var sParentMutation = sMutation === "unset" ? "remove" : "insert"; Observer.aggregationChange(oParent, sName, sParentMutation, oManagedObject); }; /** * Called from sap.ui.base.ManagedObject if an association is changed. * * @param {sap.ui.base.ManagedObject} oManagedObject Object that reports a change * @param {string} sName the name of the association that changed * @param {string} sMutation "remove" or "insert" * @param {string|string[]} vIds the removed or inserted id or list of ids * @private * @ui5-restricted sap.ui.base.ManagedObject */ Observer.associationChange = function (oManagedObject, sName, sMutation, vIds) { // managed object does an associationChange.call(this, sName, sMutation, vIds) handleChange("associations", oManagedObject, sName, function () { return { type: "association", mutation: sMutation, ids: vIds }; }); }; /** * Called from sap.ui.base.ManagedObject if an event registration is changed. * * @param {string} sName the name of the event that changed * @param {string} sMutation "remove" or "insert" * @param {object} vListener the removed or inserted listener * @param {function} fnFunc the removed or inserted handler function * @param {object} oData the removed or inserted event data * @private */ Observer.eventChange = function(oManagedObject, sName, sMutation, vListener, fnFunc, oData) { //managed object does a eventChange.call(this, sName, sMutation, vListener, fnFunc, oData) handleChange("events", oManagedObject, sName, function() { return { type: "event", mutation: sMutation, listener: vListener, func: fnFunc, data: oData }; }); }; /** * Called from sap.ui.base.ManagedObject if a binding changed for a property or aggregation. * * @param {string} sName The name of the property or aggregation of which the binding is changed * @param {string} sMutation "prepared", "ready", "removed" * @param {object} oBindingInfo the binding info * @param {string} sMemberType 'aggregation' or 'property' * @private */ Observer.bindingChange = function(oManagedObject, sName, sMutation, oBindingInfo, sMemberType) { //managed object does a bindingChange.call(this, sName, sMutation, oBindingInfo, sType) handleChange("bindings", oManagedObject, sName, function() { return { type: "binding", mutation: sMutation, bindingInfo: oBindingInfo, memberType: sMemberType }; }); }; /** * Called from sap.ui.base.ManagedObject if object is destroyed * * @param {sap.ui.base.ManagedObject} oManagedObject Object that reports a change * @private * @ui5-restricted sap.ui.base.ManagedObject */ Observer.objectDestroyed = function(oManagedObject) { handleChange("destroy", oManagedObject, null, function () { return { type: "destroy" }; }); var sId = oManagedObject.getId(); if (mTargets[sId]) { // detachEvent doesn't fail if the listener is not registered oManagedObject.detachEvent("EventHandlerChange", fnHandleEventChange); delete mTargets[sId]; } delete oManagedObject._observer; }; // handles the change event and pipelines it to the ManagedObjectObservers that are attached as listeners function handleChange(sType, oObject, sName, fnCreateChange) { var sId = oObject.getId(), oTargetConfig = mTargets[sId]; if (oTargetConfig) { var oChange; for (var i = 0; i < oTargetConfig.listeners.length; i++) { if (isObserving(oTargetConfig.configurations[i], sType, sName)) { if (!oChange) { oChange = fnCreateChange(); oChange.name = sName; oChange.object = oObject; } var oListener = oTargetConfig.listeners[i]; oListener._fnCallback(oChange); } } } } // checks whether the type and name is part of the given configuration. // if true is returned a change needs to be processed. function isObserving(oConfiguration, sType, sName) { // no configuration, listen to all types if (oConfiguration == null || !sType) { return false; } if (sType != "destroy" && sType != "parent" && !sName) { return false; } // either all (true) properties/aggregations/associations are relevant or a specific list or names is provided return oConfiguration[sType] === true || (Array.isArray(oConfiguration[sType]) && oConfiguration[sType].indexOf(sName) > -1); } // adds a listener and its configuration to the internal list of observed targets mTargets. // if the listener is already registered to the target, only its configuration is updated. // adds the observer to the target managed object if an observer is missing. function create(oTarget, oListener, oConfiguration) { updateConfiguration(oTarget, oListener, oConfiguration, false); } function getConfiguration(oTarget, oListener) { var sId = oTarget.getId(); var oTargetConfig = mTargets[sId]; if (oTargetConfig && oTargetConfig.listeners) { var iIndex = oTargetConfig.listeners.indexOf(oListener); if (iIndex >= 0) { //return the current configuration var oConfiguration = deepExtend({}, oTargetConfig.configurations[iIndex]); return oConfiguration; } } return null; } // removes the given configuration for the given listener from the internal list of observed targets mTargets. // removes the observer from the target managed object if the target hasn't to be observed any longer function remove(oTarget, oListener, oConfiguration) { oConfiguration = oConfiguration || getConfiguration(oTarget, oListener); updateConfiguration(oTarget, oListener, oConfiguration, true); } function isObjectObserved(oTarget, oListener, oConfiguration) { var sId = oTarget.getId(), oTargetConfig = mTargets[sId]; //in case no configuration is given take the current configuration oConfiguration = oConfiguration || getConfiguration(oTarget, oListener); if (!oTargetConfig) { return false; } var iIndex = oTargetConfig.listeners.indexOf(oListener); if (iIndex === -1) { return false; } else { //make a subset check return isSubArray(oTargetConfig.configurations[iIndex].properties, oConfiguration.properties) && isSubArray(oTargetConfig.configurations[iIndex].aggregations, oConfiguration.aggregations) && isSubArray(oTargetConfig.configurations[iIndex].associations, oConfiguration.associations) && isSubArray(oTargetConfig.configurations[iIndex].bindings, oConfiguration.bindings) && isSubArray(oTargetConfig.configurations[iIndex].events, oConfiguration.events) && isBooleanEqual(oTargetConfig.configurations[iIndex].destroy, oConfiguration.destroy) && isBooleanEqual(oTargetConfig.configurations[iIndex].parent, oConfiguration.parent); } } // removes a given listener by looking at all registered targets and their listeners. // if there are no more listeners to a target, the registered target is removed from the mTargets map. function destroy(oListener) { for (var n in mTargets) { var oTargetConfig = mTargets[n]; for (var i = 0; i < oTargetConfig.listeners.length; i++) { if (oTargetConfig.listeners[i] === oListener) { oTargetConfig.listeners.splice(i, 1); oTargetConfig.configurations.splice(i, 1); } } if (oTargetConfig.listeners && oTargetConfig.listeners.length === 0) { delete mTargets[n]; oTargetConfig.object._observer = undefined; } } } // update a complete configuration, create one if needed or remove it function updateConfiguration(oTarget, oListener, oConfiguration, bRemove) { var sId = oTarget.getId(), oTargetConfig = mTargets[sId], oCurrentConfig, iIndex; if (bRemove) { if (!oTargetConfig) { // no registration so far, nothing to remove return; } iIndex = oTargetConfig.listeners.indexOf(oListener); if (iIndex >= 0) { // already registered, update the configuration oCurrentConfig = oTargetConfig.configurations[iIndex]; } } else { if (!oTargetConfig) { oTargetConfig = mTargets[sId] = { listeners: [], configurations: [], object: oTarget }; } iIndex = oTargetConfig.listeners.indexOf(oListener); if (iIndex === -1) { // not registered, push listener and configuration oTargetConfig.listeners.push(oListener); oTargetConfig.configurations.push(oConfiguration); } else { oCurrentConfig = oTargetConfig.configurations[iIndex]; } } if (oCurrentConfig) { oCurrentConfig.properties = oCurrentConfig.properties || []; updateSingleArray(oCurrentConfig.properties, oConfiguration.properties, bRemove); oCurrentConfig.aggregations = oCurrentConfig.aggregations || []; updateSingleArray(oCurrentConfig.aggregations, oConfiguration.aggregations, bRemove); oCurrentConfig.associations = oCurrentConfig.associations || []; updateSingleArray(oCurrentConfig.associations, oConfiguration.associations, bRemove); oCurrentConfig.bindings = oCurrentConfig.bindings || []; updateSingleArray(oCurrentConfig.bindings, oConfiguration.bindings, bRemove); oCurrentConfig.events = oCurrentConfig.events || []; updateSingleArray(oCurrentConfig.events, oConfiguration.events, bRemove); if (oConfiguration.destroy != null) { if (bRemove) { delete oCurrentConfig.destroy; } else { oCurrentConfig.destroy = oConfiguration.destroy; } } if (oConfiguration.parent != null) { if (bRemove) { delete oCurrentConfig.parent; } else { oCurrentConfig.parent = oConfiguration.parent; } } } var bEventsObserved = hasObserverFor(oTarget, "events"); if (oTarget._observer && bRemove) { //delete oTarget._observer; if (!bEventsObserved && EventProvider.hasListener(oTarget, "EventHandlerChange", fnHandleEventChange)) { oTarget.detachEvent("EventHandlerChange", fnHandleEventChange); } if (!bEventsObserved && !hasObserverFor(oTarget, "properties") && !hasObserverFor(oTarget, "aggregations") && !hasObserverFor(oTarget, "associations") && !hasObserverFor(oTarget, "destroy") && !hasObserverFor(oTarget, "parent") && !hasObserverFor(oTarget, "bindings")) { delete oTarget._observer; delete mTargets[sId]; } } else if (!oTarget._observer && !bRemove) { //is any config listening to events if (bEventsObserved && !EventProvider.hasListener(oTarget, "EventHandlerChange", fnHandleEventChange)) { oTarget.attachEvent("EventHandlerChange", fnHandleEventChange); } oTarget._observer = Observer; } } //checks whether a given type (events, aggregations, associations, properties, bindings, destroy) is //currently observed on the given target function hasObserverFor(oTarget, sType) { var sId = oTarget.getId(), oTargetConfig = mTargets[sId]; if (oTargetConfig) { var aConfigs = oTargetConfig.configurations.filter(function(oEntry) { return oEntry.hasOwnProperty(sType) && oEntry[sType] && (oEntry[sType] === true || oEntry[sType].length > 0); }); return aConfigs.length > 0; } return false; } function fnHandleEventChange(oEvent) { var oTarget = oEvent.getSource(), sEventId = oEvent.mParameters.EventId; if (oTarget.getMetadata().hasEvent(sEventId)) { if (oEvent.mParameters.type === "listenerAttached") { Observer.eventChange(oTarget, sEventId, "insert", oEvent.mParameters.listener, oEvent.mParameters.func, oEvent.mParameters.data); } else if (oEvent.mParameters.type === "listenerDetached") { Observer.eventChange(oTarget, sEventId, "remove", oEvent.mParameters.listener, oEvent.mParameters.func, oEvent.mParameters.data); } } } // update the single array for observing and unobserving function updateSingleArray(aOrig, aAdditional, bRemove) { if (!aAdditional) { return; } for (var i = 0; i < aAdditional.length; i++) { var iIndex = aOrig.indexOf(aAdditional[i]); if (iIndex > -1 && bRemove) { aOrig.splice(iIndex, 1); } else if (iIndex === -1 && !bRemove) { aOrig.push(aAdditional[i]); } } } function isSubArray(aFullArray, aSubArray) { if (!Array.isArray(aSubArray) || aSubArray.length == 0) { // empty array is contained in 'anything' return true; } if (!Array.isArray(aFullArray) || aFullArray.length == 0) { // empty array contains no other (non-empty) array return false; } var aUnion = uniqueSort(aFullArray.concat(aSubArray)); // merge arrays, remove duplicates //in case aSubArray is inside aFullArray the length did not change return aFullArray.length === aUnion.length; } function isBooleanEqual(bOriginal,bCompare) { if (bCompare == null) { return true; } return bOriginal === bCompare; } // in case the configuration for a specific type is set to true translate this to the complete array in order not to get in trouble // when deregistering properties function normalizeConfiguration(oObject, oConfiguration) { var oMetadata = oObject.getMetadata(), aProperties = Object.keys(oMetadata.getAllProperties()), aAggregations = Object.keys(oMetadata.getAllAggregations()), aAssociations = Object.keys(oMetadata.getAllAssociations()), aBindings = uniqueSort(aProperties.concat(aAggregations)), aEvents = Object.keys(oMetadata.getAllEvents()); oConfiguration.properties = oConfiguration.properties === true ? aProperties : oConfiguration.properties; oConfiguration.aggregations = oConfiguration.aggregations === true ? aAggregations : oConfiguration.aggregations; oConfiguration.associations = oConfiguration.associations === true ? aAssociations : oConfiguration.associations; oConfiguration.bindings = oConfiguration.bindings === true ? aBindings : oConfiguration.bindings; oConfiguration.events = oConfiguration.events === true ? aEvents : oConfiguration.events; oConfiguration.destroy = (oConfiguration.destroy == null) ? false : oConfiguration.destroy; oConfiguration.parent = (oConfiguration.parent == null) ? false : oConfiguration.parent; } return ManagedObjectObserver; });