UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

1,282 lines (1,142 loc) 82.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.ManagedObjectMetadata sap.ui.define([ './DataType', './Metadata', './Object', 'sap/base/Log', 'sap/base/assert', 'sap/base/config', 'sap/base/strings/capitalize', 'sap/base/strings/escapeRegExp', 'sap/base/util/merge', 'sap/base/util/isPlainObject' ], function( DataType, Metadata, BaseObject, Log, assert, BaseConfig, capitalize, escapeRegExp, merge, isPlainObject ) { "use strict"; /** * Creates a new metadata object that describes a subclass of ManagedObject. * * <b>Note:</b> Code outside the <code>sap.ui.base</code> namespace must not call this * constructor directly. Instances will be created automatically when a new class is * defined with one of the {@link sap.ui.base.ManagedObject.extend <i>SomeClass</i>.extend} * methods. * * <b>Note</b>: throughout this class documentation, the described subclass of ManagedObject * is referenced as <i>the described class</i>. * * @param {string} sClassName fully qualified name of the described class * @param {object} oClassInfo static info to construct the metadata from * @param {sap.ui.base.ManagedObject.MetadataOptions} [oClassInfo.metadata] * The metadata object describing the class * * @class * @classdesc * * <strong>Note about Info Objects</strong> * * Several methods in this class return info objects that describe a property, * aggregation, association or event of the class described by this metadata object. * The type, structure and behavior of these info objects is not yet documented and * not part of the stable, public API. * * Code using such methods and the returned info objects therefore needs to be aware * of the following restrictions: * * <ul> * <li>the set of properties exposed by each info object, their type and value * might change as well as the class of the info object itself. * * Properties that represent settings provided during class definition * (in the oClassInfo parameter of the 'extend' call, e.g. 'type', 'multiple' * of an aggregation) are more likely to stay the same than additional, derived * properties like '_iKind'.</li> * * <li>info objects must not be modified / enriched although they technically could.</li> * * <li>the period of validity of info objects is not defined. They should be * referenced only for a short time and not be kept as members of long living * objects or closures.</li> * * </ul> * * * @author Frank Weigel * @version 1.147.0 * @since 0.8.6 * @alias sap.ui.base.ManagedObjectMetadata * @extends sap.ui.base.Metadata * @public */ var ManagedObjectMetadata = function(sClassName, oClassInfo) { // call super constructor Metadata.apply(this, arguments); }; var Element; // lazy dependency to sap/ui/core/Element // chain the prototypes ManagedObjectMetadata.prototype = Object.create(Metadata.prototype); ManagedObjectMetadata.prototype.constructor = ManagedObjectMetadata; var rPlural = /(children|ies|ves|oes|ses|ches|shes|xes|s)$/i; var mSingular = {'children' : -3, 'ies' : 'y', 'ves' : 'f', 'oes' : -2, 'ses' : -2, 'ches' : -2, 'shes' : -2, 'xes' : -2, 's' : -1 }; function guessSingularName(sName) { return sName.replace(rPlural, function($,sPlural) { var vRepl = mSingular[sPlural.toLowerCase()]; return typeof vRepl === "string" ? vRepl : sPlural.slice(0,vRepl); }); } function deprecation(fn, name) { return function() { Log.warning("[DEPRECATED] Usage of deprecated feature: " + name); return fn.apply(this, arguments); }; } function remainder(obj, info) { var result = null; for (var n in info) { if ( Object.hasOwn(info, n) && typeof obj[n] === 'undefined' ) { result = result || {}; result[n] = info[n]; } } return result; } /** * Validates the given default class against its aggregation type. */ function validateDefaultClass(oAggregation, oOriginalAggregationInfo, fnClass) { const fnDefaultClass = oOriginalAggregationInfo.defaultClass; // we check if: // 1. the defaultClass matches the aggregation type // 2. the defaultClass matches the altTypes ('object' must not be included) // 3. the defaultClass defined with a nullish value if (fnDefaultClass) { if (!BaseObject.isObjectA(fnDefaultClass.prototype, oAggregation.type)) { throw new TypeError(`The 'defaultClass' of the aggregation '${oAggregation.name}' in '${fnClass.getName()}' is not of type '${oAggregation.type}'.`); } else if (oAggregation.altTypes?.includes("object")) { throw new TypeError(`The aggregation '${oAggregation.name}' in '${fnClass.getName()}' must not defined a 'defaultClass' together with the altType 'object'.`); } } else if (oOriginalAggregationInfo.hasOwnProperty("defaultClass")) { throw new TypeError(`The 'defaultClass' of the aggregation '${oAggregation.name}' in '${fnClass.getName()}' is defined with a nullish value (${fnDefaultClass}).`); } return fnDefaultClass; } var Kind = { SPECIAL_SETTING : -1, PROPERTY : 0, SINGLE_AGGREGATION : 1, MULTIPLE_AGGREGATION : 2, SINGLE_ASSOCIATION : 3, MULTIPLE_ASSOCIATION : 4, EVENT : 5 }; /** * Guess a singular name for a given plural name. * * This method is not guaranteed to return a valid result. If the result is not satisfying, * the singular name for an aggregation/association should be specified in the class metadata. * * @private * @function */ ManagedObjectMetadata._guessSingularName = guessSingularName; // ---- SpecialSetting -------------------------------------------------------------------- /** * SpecialSetting info object * @private * @since 1.27.1 */ function SpecialSetting(oClass, name, info) { info = typeof info !== 'object' ? { type: info } : info; this.name = name; this.type = info.type || 'any'; this.visibility = info.visibility || 'public'; this.defaultValue = info.defaultValue; this.appData = remainder(this, info); this._oParent = oClass; this._sUID = "special:" + name; this._iKind = Kind.SPECIAL_SETTING; } // ---- Property -------------------------------------------------------------------------- /** * Property info object * @private * @since 1.27.1 */ function Property(oClass, name, info) { info = typeof info !== 'object' ? { type: info } : info; this.name = name; this.type = info.type || 'string'; this.group = info.group || 'Misc'; this.defaultValue = info.defaultValue !== null ? info.defaultValue : null; this.bindable = !!info.bindable; this.deprecated = !!info.deprecated || false; this.visibility = info.visibility || 'public'; this.byValue = info.byValue === true; // non-boolean values reserved for the future this.selector = typeof info.selector === "string" ? info.selector : null; this.appData = remainder(this, info); this._oParent = oClass; this._sUID = name; this._iKind = Kind.PROPERTY; var N = capitalize(name); this._sMutator = 'set' + N; this._sGetter = 'get' + N; if ( this.bindable ) { this._sBind = 'bind' + N; this._sUnbind = 'unbind' + N; } else { this._sBind = this._sUnbind = undefined; } this._oType = null; } /** * @private */ Property.prototype.generate = function(add) { var that = this, n = that.name; add(that._sGetter, function() { return this.getProperty(n); }); add(that._sMutator, function(v) { this.setProperty(n,v); return this; }, that); if ( that.bindable ) { add(that._sBind, function(p,fn,m) { this.bindProperty(n,p,fn,m); return this; }, that); add(that._sUnbind, function(p) { this.unbindProperty(n,p); return this; }); } }; Property.prototype.getType = function() { if (!this._oType) { this._oType = DataType.getType(this.type); } return this._oType; }; Property.prototype.getDefaultValue = function() { var oDefaultValue = this.defaultValue, oType; if ( oDefaultValue === null ) { oType = this.getType(); if ( oType instanceof DataType ) { oDefaultValue = oType.getDefaultValue(); } } return oDefaultValue; }; Property.prototype.get = function(instance) { if ( this.visibility !== 'public' ) { return instance.getProperty(this.name); } return instance[this._sGetter](); }; Property.prototype.set = function(instance, oValue) { if ( this.visibility !== 'public' ) { return instance.setProperty(this.name, oValue); } return instance[this._sMutator](oValue); }; // ---- Aggregation ----------------------------------------------------------------------- /** * Aggregation info object * @private * @since 1.27.1 */ function Aggregation(oClass, name, info) { info = typeof info !== 'object' ? { type: info } : info; this.name = name; this.type = info.type || 'sap.ui.core.Control'; this.altTypes = Array.isArray(info.altTypes) ? info.altTypes : undefined; this.defaultClass = validateDefaultClass(this, info, oClass) || null; this.multiple = typeof info.multiple === 'boolean' ? info.multiple : true; this.singularName = this.multiple ? info.singularName || guessSingularName(name) : undefined; this.bindable = !!info.bindable; this.deprecated = info.deprecated || false; this.visibility = info.visibility || 'public'; this.selector = info.selector || null; this.forwarding = info.forwarding; this._doesNotRequireFactory = !!info._doesNotRequireFactory; // TODO clarify if public this.appData = remainder(this, info); this._oParent = oClass; this._sUID = 'aggregation:' + name; this._iKind = this.multiple ? Kind.MULTIPLE_AGGREGATION : Kind.SINGLE_AGGREGATION; this._oForwarder = this.forwarding ? new AggregationForwarder(this) : undefined; var N = capitalize(name); this._sGetter = 'get' + N; if ( this.multiple ) { var N1 = capitalize(this.singularName); this._sMutator = 'add' + N1; this._sInsertMutator = 'insert' + N1; this._sRemoveMutator = 'remove' + N1; this._sRemoveAllMutator = 'removeAll' + N; this._sIndexGetter = 'indexOf' + N1; this._sUpdater = 'update' + N; this._sRefresher = 'refresh' + N; } else { this._sMutator = 'set' + N; this._sInsertMutator = this._sRemoveMutator = this._sRemoveAllMutator = this._sIndexGetter = this._sUpdater = this._sRefresher = undefined; } this._sDestructor = 'destroy' + N; if ( this.bindable ) { this._sBind = 'bind' + N; this._sUnbind = 'unbind' + N; } else { this._sBind = this._sUnbind = undefined; } } /** * @private */ Aggregation.prototype.generate = function(add) { var that = this, n = that.name; if ( !that.multiple ) { add(that._sGetter, function() { return this.getAggregation(n); }); add(that._sMutator, function(v) { this.setAggregation(n,v); return this; }, that); } else { add(that._sGetter, function() { return this.getAggregation(n,[]); }); add(that._sMutator, function(a) { this.addAggregation(n,a); return this; }, that); add(that._sInsertMutator, function(i,a) { this.insertAggregation(n,i,a); return this; }, that); add(that._sRemoveMutator, function(a) { return this.removeAggregation(n,a); }); add(that._sRemoveAllMutator, function() { return this.removeAllAggregation(n); }); add(that._sIndexGetter, function(a) { return this.indexOfAggregation(n,a); }); } add(that._sDestructor, function() { this.destroyAggregation(n); return this; }); if ( that.bindable ) { add(that._sBind, function(p,t,s,f) { this.bindAggregation(n,p,t,s,f); return this; }, that); add(that._sUnbind, function(p) { this.unbindAggregation(n,p); return this; }); } }; Aggregation.prototype.getType = function() { if (!this._oType) { this._oType = DataType.getType(this.type); } return this._oType; }; Aggregation.prototype.get = function(instance) { if ( this.visibility !== 'public' ) { return instance.getAggregation(this.name, this.multiple ? [] : undefined); } return instance[this._sGetter](); }; Aggregation.prototype.set = function(instance, oValue) { if ( this.visibility !== 'public' ) { return instance.setAggregation(this.name, oValue); } return instance[this._sMutator](oValue); }; Aggregation.prototype.add = function(instance, oValue) { if ( this.visibility !== 'public' ) { return instance.addAggregation(this.name, oValue); } return instance[this._sMutator](oValue); }; Aggregation.prototype.insert = function(instance, oValue, iPos) { if ( this.visibility !== 'public' ) { return instance.insertAggregation(this.name, oValue, iPos); } return instance[this._sInsertMutator](oValue, iPos); }; Aggregation.prototype.remove = function(instance, vValue) { if ( this.visibility !== 'public' ) { return instance.removeAggregation(this.name, vValue); } return instance[this._sRemoveMutator](vValue); }; Aggregation.prototype.removeAll = function(instance) { if ( this.visibility !== 'public' ) { return instance.removeAllAggregation(this.name); } return instance[this._sRemoveAllMutator](); }; Aggregation.prototype.indexOf = function(instance, oValue) { if ( this.visibility !== 'public' ) { return instance.indexOfAggregation(this.name, oValue); } return instance[this._sIndexGetter](oValue); }; Aggregation.prototype.destroy = function(instance) { return instance[this._sDestructor](); }; Aggregation.prototype.update = function(instance, sChangeReason, oEventInfo) { if (instance[this._sUpdater]) { instance[this._sUpdater](sChangeReason, oEventInfo); } else { instance.updateAggregation(this.name, sChangeReason, oEventInfo); } }; Aggregation.prototype.refresh = function(instance, sChangeReason) { if (instance[this._sRefresher]) { instance[this._sRefresher](sChangeReason); } else { //fallback there was no refresher before this.update(instance, sChangeReason); } }; /** * Creates a new aggregation forwarder for the given aggregation. * @param {object} oAggregation Aggregation info object * * @class Class to manage the forwarding of an aggregation. * @alias sap.ui.base.ManagedObjectMetadata.AggregationForwarder * @private * @ui5-restricted sap.ui.base */ function AggregationForwarder(oAggregation) { var oForwardTo = oAggregation.forwarding; this.aggregation = oAggregation; // source aggregation info this.targetAggregationName = oForwardTo.aggregation; this.forwardBinding = oForwardTo.forwardBinding; this.targetAggregationInfo = null; // resolve lazily // make sure we have a way to get the target control if (oForwardTo.getter) { if (typeof oForwardTo.getter === "function") { this._getTarget = oForwardTo.getter; } else { // name of the function which returns the target element this._getTarget = (function(sGetterName) { return function() { return this[sGetterName](); // "this" context is the ManagedObject instance }; })(oForwardTo.getter); } } else if (oForwardTo.idSuffix) { // target given by ID this._getTarget = (function(sIdSuffix) { return function() { Element = Element || sap.ui.require("sap/ui/core/Element"); return Element && Element.getElementById(this.getId() + sIdSuffix); // "this" context is the ManagedObject instance }; })(oForwardTo.idSuffix); } else { throw new Error("Either getter or idSuffix must be given for forwarding the aggregation " + oAggregation.name + " to the aggregation " + oForwardTo.aggregation + " in " + oAggregation._oParent.getName()); } } AggregationForwarder.prototype._getTargetAggregationInfo = function(oTarget) { var oTargetAggregationInfo = this.targetAggregationInfo; if (!oTargetAggregationInfo && oTarget) { oTargetAggregationInfo = this.targetAggregationInfo = oTarget.getMetadata().getAggregation(this.targetAggregationName); if (!oTargetAggregationInfo) { throw new Error("Target aggregation " + this.targetAggregationName + " not found on " + oTarget); } if (this.aggregation.multiple && !oTargetAggregationInfo.multiple) { // cannot forward multi-to-single throw new Error("Aggregation " + this.aggregation + " (multiple: " + this.aggregation.multiple + ") cannot be forwarded to aggregation " + this.targetAggregationName + " (multiple: " + oTargetAggregationInfo.multiple + ")"); } if (!this.aggregation.multiple && oTargetAggregationInfo.multiple && this.aggregation.forwarding.forwardBinding) { // cannot forward bindings for single-to-multi throw new Error("Aggregation " + this.aggregation + " (multiple: " + this.aggregation.multiple + ") cannot be forwarded to aggregation " + this.targetAggregationName + " (multiple: " + oTargetAggregationInfo.multiple + ") with 'forwardBinding' set to 'true'"); } } return oTargetAggregationInfo; }; /* * Returns the forwarding target instance and ensures that this.targetAggregationInfo is available * @returns {sap.ui.core.Control} */ AggregationForwarder.prototype.getTarget = function(oInstance, bConnectTargetInfo) { var oTarget = this._getTarget.call(oInstance); this._getTargetAggregationInfo(oTarget); if (oTarget) { oInstance.mForwardedAggregations = oInstance.mForwardedAggregations || {}; if (oInstance.mForwardedAggregations[this.aggregation.name] === undefined || bConnectTargetInfo) { // once the target is there, connect the aggregations: // Make mForwardedAggregations[name] a pointer to mAggregations[name] of the target, so the former always has the same elements, // without the need to update when elements are added/removed and without increasing memory for pointers per aggregated element // which would be required in a copy of the map var vTargetAggregation = oTarget.mAggregations[this.targetAggregationInfo.name]; if (vTargetAggregation // target aggregation may not exist yet ... but an empty array is ok && !bConnectTargetInfo && !this.aggregation.forwarding.forwardBinding && !(Array.isArray(vTargetAggregation) && vTargetAggregation.length === 0)) { // there should not be any content in the target at the time when the target has been found for the first time throw new Error("There is already content in aggregation " + this.targetAggregationInfo.name + " of " + oTarget + " to which forwarding is being set up now."); } else { var vInitial = oTarget.mAggregations[this.targetAggregationInfo.name] || (this.targetAggregationInfo.multiple ? [] : null); // initialize aggregation for the target oInstance.mForwardedAggregations[this.aggregation.name] = oTarget.mAggregations[this.targetAggregationInfo.name] = vInitial; } } } return oTarget; }; AggregationForwarder.prototype.get = function(oInstance) { var oTarget = this.getTarget(oInstance); if (oTarget) { var result = this.targetAggregationInfo.get(oTarget); if (!this.aggregation.multiple && this.targetAggregationInfo.multiple) { // single-to-multi forwarding result = result[0]; // unwrap the element or return undefined if empty array was returned } return result; } else { // before target of forwarding exists return this.aggregation.multiple ? [] : null; } }; AggregationForwarder.prototype.indexOf = function(oInstance, oAggregatedObject) { var oTarget = this.getTarget(oInstance); return this.targetAggregationInfo.indexOf(oTarget, oAggregatedObject); }; AggregationForwarder.prototype.set = function(oInstance, oAggregatedObject) { var oTarget = this.getTarget(oInstance); // TODO oInstance.observer oInstance.mForwardedAggregations[this.aggregation.name] = oAggregatedObject; if (this.targetAggregationInfo.multiple) { // target aggregation is multiple, but should behave like single (because the source aggregation is single) var oPreviousElement = this.targetAggregationInfo.get(oTarget); if (oPreviousElement && oPreviousElement[0]) { if (oPreviousElement[0] === oAggregatedObject) { // no modification if same element is set return oInstance; } this.targetAggregationInfo.removeAll(oTarget); } ManagedObjectMetadata.addAPIParentInfoBegin(oAggregatedObject, oInstance, this.aggregation.name); this.targetAggregationInfo.add(oTarget, oAggregatedObject); } else { ManagedObjectMetadata.addAPIParentInfoBegin(oAggregatedObject, oInstance, this.aggregation.name); this.targetAggregationInfo.set(oTarget, oAggregatedObject); } ManagedObjectMetadata.addAPIParentInfoEnd(oAggregatedObject); return oInstance; }; AggregationForwarder.prototype.add = function(oInstance, oAggregatedObject) { var oTarget = this.getTarget(oInstance); // TODO oInstance.observer ManagedObjectMetadata.addAPIParentInfoBegin(oAggregatedObject, oInstance, this.aggregation.name); this.targetAggregationInfo.add(oTarget, oAggregatedObject); ManagedObjectMetadata.addAPIParentInfoEnd(oAggregatedObject); return oInstance; }; AggregationForwarder.prototype.insert = function(oInstance, oAggregatedObject, iIndex) { var oTarget = this.getTarget(oInstance); // TODO oInstance.observer ManagedObjectMetadata.addAPIParentInfoBegin(oAggregatedObject, oInstance, this.aggregation.name); this.targetAggregationInfo.insert(oTarget, oAggregatedObject, iIndex); ManagedObjectMetadata.addAPIParentInfoEnd(oAggregatedObject); return oInstance; }; /** * Adds information to the given oAggregatedObject about its original API parent (or a subsequent API parent in case of multiple forwarding). * MUST be called before an element is forwarded to another internal aggregation (in case forwarding is done explicitly/manually without using * the declarative mechanism introduced in UI5 1.56). * * CAUTION: ManagedObjectMetadata.addAPIParentInfoEnd(...) MUST be called AFTER the element has been forwarded (set to an aggregation of an * internal control). These two calls must wrap the forwarding. * * @param {sap.ui.base.ManagedObject} oAggregatedObject Object to which the new API parent info should be added * @param {sap.ui.base.ManagedObject} oParent Object that is a new API parent * @param {string} sAggregationName the name of the aggregation under which oAggregatedObject is aggregated by the API parent * @protected */ ManagedObjectMetadata.addAPIParentInfoBegin = function(oAggregatedObject, oParent, sAggregationName) { if (!oAggregatedObject) { return; } var oNewAPIParentInfo = {parent: oParent, aggregationName: sAggregationName}; if (oAggregatedObject.aAPIParentInfos) { if (oAggregatedObject.aAPIParentInfos.forwardingCounter) { // defined and >= 1 // this is another forwarding step from an element that was already the target of forwarding oAggregatedObject.aAPIParentInfos.forwardingCounter++; } else { // this is a fresh new round of aggregation forwarding, remove any previous forwarding info delete oAggregatedObject.aAPIParentInfos; } } // update API parent of oAggregatedObject if (!oAggregatedObject.aAPIParentInfos) { oAggregatedObject.aAPIParentInfos = [oNewAPIParentInfo]; oAggregatedObject.aAPIParentInfos.forwardingCounter = 1; } else { oAggregatedObject.aAPIParentInfos.push(oNewAPIParentInfo); } }; /** * Completes the information about the original API parent of the given element. * MUST be called after an element is forwarded to another internal aggregation. For every call to * ManagedObjectMetadata.addAPIParentInfoBegin(...) this method here must be called as well. * * @param {sap.ui.base.ManagedObject} oAggregatedObject Object to which the new API parent info should be added * @protected */ ManagedObjectMetadata.addAPIParentInfoEnd = function(oAggregatedObject) { oAggregatedObject && oAggregatedObject.aAPIParentInfos && oAggregatedObject.aAPIParentInfos.forwardingCounter--; }; AggregationForwarder.prototype.remove = function(oInstance, vAggregatedObject) { var oTarget = this.getTarget(oInstance); // TODO oInstance.observer var result = this.targetAggregationInfo.remove(oTarget, vAggregatedObject); // remove API parent of removed element (if any) if (result /* && result.aAPIParentInfos */) { // the second part should always be true when added via forwarding, but MultiInput still has a function "setTokens" // that forwards directly. That one now also sets the API parent info. // When aAPIParentInfos is there, then the other conditions are always true: // && result.aAPIParentInfos.length && result.aAPIParentInfos[result.aAPIParentInfos.length-1].parent === oInstance result.aAPIParentInfos && result.aAPIParentInfos.pop(); } return result; }; AggregationForwarder.prototype.removeAll = function(oInstance) { var oTarget = this.getTarget(oInstance); // TODO oInstance.observer delete oInstance.mForwardedAggregations[this.aggregation.name]; var aRemoved = this.targetAggregationInfo.removeAll(oTarget); // update API parent of removed objects for (var i = 0; i < aRemoved.length; i++) { if (aRemoved[i].aAPIParentInfos) { aRemoved[i].aAPIParentInfos.pop(); } } return aRemoved; }; AggregationForwarder.prototype.destroy = function(oInstance) { var oTarget = this.getTarget(oInstance); // TODO oInstance.observer delete oInstance.mForwardedAggregations[this.aggregation.name]; if (oTarget) { this.targetAggregationInfo.destroy(oTarget); } // API parent info of objects being destroyed is removed in ManagedObject.prototype.destroy() return oInstance; }; // ---- Association ----------------------------------------------------------------------- /** * Association info object * @private * @since 1.27.1 */ function Association(oClass, name, info) { info = typeof info !== 'object' ? { type: info } : info; this.name = name; this.type = info.type || 'sap.ui.core.Control'; this.multiple = info.multiple || false; this.singularName = this.multiple ? info.singularName || guessSingularName(name) : undefined; this.deprecated = info.deprecated || false; this.visibility = info.visibility || 'public'; this.appData = remainder(this, info); this._oParent = oClass; this._sUID = 'association:' + name; this._iKind = this.multiple ? Kind.MULTIPLE_ASSOCIATION : Kind.SINGLE_ASSOCIATION; var N = capitalize(name); this._sGetter = 'get' + N; if ( this.multiple ) { var N1 = capitalize(this.singularName); this._sMutator = 'add' + N1; this._sRemoveMutator = 'remove' + N1; this._sRemoveAllMutator = 'removeAll' + N; } else { this._sMutator = 'set' + N; this._sRemoveMutator = this._sRemoveAllMutator = undefined; } } /** * @private */ Association.prototype.generate = function(add) { var that = this, n = that.name; if ( !that.multiple ) { add(that._sGetter, function() { return this.getAssociation(n); }); add(that._sMutator, function(v) { this.setAssociation(n,v); return this; }, that); } else { add(that._sGetter, function() { return this.getAssociation(n,[]); }); add(that._sMutator, function(a) { this.addAssociation(n,a); return this; }, that); add(that._sRemoveMutator, function(a) { return this.removeAssociation(n,a); }); add(that._sRemoveAllMutator, function() { return this.removeAllAssociation(n); }); if ( n !== that.singularName ) { add('removeAll' + capitalize(that.singularName), function() { Log.warning("[DEPRECATED] Usage of deprecated method " + that._oParent.getName() + ".prototype." + 'removeAll' + capitalize(that.singularName) + "," + " use method " + that._sRemoveAllMutator + " (plural) instead."); return this[that._sRemoveAllMutator](); }); } } }; Association.prototype.getType = function() { if (!this._oType) { this._oType = DataType.getType(this.type); } return this._oType; }; Association.prototype.get = function(instance) { if ( this.visibility !== 'public' ) { return instance.getAssociation(this.name, this.multiple ? [] : undefined); } return instance[this._sGetter](); }; Association.prototype.set = function(instance, oValue) { if ( this.visibility !== 'public' ) { return instance.setAssociation(this.name, oValue); } return instance[this._sMutator](oValue); }; Association.prototype.add = function(instance, oValue) { if ( this.visibility !== 'public' ) { return instance.addAssociation(this.name, oValue); } return instance[this._sMutator](oValue); }; Association.prototype.remove = function(instance, vValue) { if ( this.visibility !== 'public' ) { return instance.removeAssociation(this.name, vValue); } return instance[this._sRemoveMutator](vValue); }; Association.prototype.removeAll = function(instance) { if ( this.visibility !== 'public' ) { return instance.removeAllAssociation(this.name); } return instance[this._sRemoveAllMutator](); }; // ---- Event ----------------------------------------------------------------------------- /** * Event info object * @private * @since 1.27.1 */ function Event(oClass, name, info) { this.name = name; this.allowPreventDefault = info.allowPreventDefault || false; this.deprecated = info.deprecated || false; this.visibility = 'public'; this.allowPreventDefault = !!info.allowPreventDefault; this.enableEventBubbling = !!info.enableEventBubbling; this.appData = remainder(this, info); this._oParent = oClass; this._sUID = 'event:' + name; this._iKind = Kind.EVENT; var N = capitalize(name); this._sMutator = 'attach' + N; this._sDetachMutator = 'detach' + N; this._sTrigger = 'fire' + N; } /** * @private */ Event.prototype.generate = function(add) { var that = this, n = that.name, allowPreventDefault = that.allowPreventDefault, enableEventBubbling = that.enableEventBubbling; add(that._sMutator, function(d,f,o) { this.attachEvent(n,d,f,o); return this; }, that); add(that._sDetachMutator, function(f,o) { this.detachEvent(n,f,o); return this; }); add(that._sTrigger, function(p) { return this.fireEvent(n,p, allowPreventDefault, enableEventBubbling); }); }; Event.prototype.attach = function(instance,data,fn,listener) { return instance[this._sMutator](data,fn,listener); }; Event.prototype.detach = function(instance,fn,listener) { return instance[this._sDetachMutator](fn,listener); }; Event.prototype.fire = function(instance,params) { return instance[this._sTrigger](params, this.allowPreventDefault, this.enableEventBubbling); }; // ---------------------------------------------------------------------------------------- ManagedObjectMetadata.prototype.metaFactorySpecialSetting = SpecialSetting; ManagedObjectMetadata.prototype.metaFactoryProperty = Property; ManagedObjectMetadata.prototype.metaFactoryAggregation = Aggregation; ManagedObjectMetadata.prototype.metaFactoryAssociation = Association; ManagedObjectMetadata.prototype.metaFactoryEvent = Event; /** * @private */ ManagedObjectMetadata.prototype.applySettings = function(oClassInfo) { var that = this, oStaticInfo = oClassInfo.metadata; Metadata.prototype.applySettings.call(this, oClassInfo); function normalize(mInfoMap, FNClass) { var mResult = {}, sName; if ( mInfoMap ) { for (sName in mInfoMap) { if ( Object.hasOwn(mInfoMap, sName) ) { mResult[sName] = new FNClass(that, sName, mInfoMap[sName]); } } } return mResult; } function filter(mInfoMap, bPublic) { var mResult = {},sName; for (sName in mInfoMap) { if ( bPublic === (mInfoMap[sName].visibility === 'public') ) { mResult[sName] = mInfoMap[sName]; } } return mResult; } var rLibName = /([a-z][^.]*(?:\.[a-z][^.]*)*)\./; function defaultLibName(sName) { var m = rLibName.exec(sName); return (m && m[1]) || ""; } // init basic metadata from static information and fallback to defaults this._sLibraryName = oStaticInfo.library || defaultLibName(this.getName()); this._mSpecialSettings = normalize(oStaticInfo.specialSettings, this.metaFactorySpecialSetting); var mAllProperties = normalize(oStaticInfo.properties, this.metaFactoryProperty); this._mProperties = filter(mAllProperties, true); this._mPrivateProperties = filter(mAllProperties, false); var mAllAggregations = normalize(oStaticInfo.aggregations, this.metaFactoryAggregation); this._mAggregations = filter(mAllAggregations, true); this._mPrivateAggregations = filter(mAllAggregations, false); this._sDefaultAggregation = oStaticInfo.defaultAggregation || null; this._sDefaultProperty = oStaticInfo.defaultProperty || null; var mAllAssociations = normalize(oStaticInfo.associations, this.metaFactoryAssociation); this._mAssociations = filter(mAllAssociations, true); this._mPrivateAssociations = filter(mAllAssociations, false); this._mEvents = normalize(oStaticInfo.events, this.metaFactoryEvent); // as oClassInfo is volatile, we need to store the info this._oDesignTime = oClassInfo.metadata["designtime"] || oClassInfo.metadata["designTime"]; this._sProvider = oClassInfo.metadata["provider"]; if ( oClassInfo.metadata.__version > 1.0 ) { this.generateAccessors(); } }; /** * @private */ ManagedObjectMetadata.prototype.afterApplySettings = function() { Metadata.prototype.afterApplySettings.call(this); // if there is a parent class, produce the flattened "all" views for the element specific metadata // PERFOPT: this could be done lazily var oParent = this.getParent(); if ( oParent instanceof ManagedObjectMetadata ) { this._mAllEvents = Object.assign({}, oParent._mAllEvents, this._mEvents); this._mAllPrivateProperties = Object.assign({}, oParent._mAllPrivateProperties, this._mPrivateProperties); this._mAllProperties = Object.assign({}, oParent._mAllProperties, this._mProperties); this._mAllPrivateAggregations = Object.assign({}, oParent._mAllPrivateAggregations, this._mPrivateAggregations); this._mAllAggregations = Object.assign({}, oParent._mAllAggregations, this._mAggregations); this._mAllPrivateAssociations = Object.assign({}, oParent._mAllPrivateAssociations, this._mPrivateAssociations); this._mAllAssociations = Object.assign({}, oParent._mAllAssociations, this._mAssociations); this._sDefaultAggregation = this._sDefaultAggregation || oParent._sDefaultAggregation; this._sDefaultProperty = this._sDefaultProperty || oParent._sDefaultProperty; this._mAllSpecialSettings = Object.assign({}, oParent._mAllSpecialSettings, this._mSpecialSettings); this._sProvider = this._sProvider || oParent._sProvider; } else { this._mAllEvents = this._mEvents; this._mAllPrivateProperties = this._mPrivateProperties; this._mAllProperties = this._mProperties; this._mAllPrivateAggregations = this._mPrivateAggregations; this._mAllAggregations = this._mAggregations; this._mAllPrivateAssociations = this._mPrivateAssociations; this._mAllAssociations = this._mAssociations; this._mAllSpecialSettings = this._mSpecialSettings; } }; ManagedObjectMetadata.Kind = Kind; /** * Returns the name of the library that contains the described UIElement. * @return {string} the name of the library * @public */ ManagedObjectMetadata.prototype.getLibraryName = function() { return this._sLibraryName; }; // ---- properties ------------------------------------------------------------------------ /** * Declares an additional property for the described class. * * Any property declaration via this method must happen before the described class * is subclassed, or the added property will not be visible in the subclass. * * Typically used to enrich UIElement classes in an aspect oriented manner. * @param {string} sName name of the property to add * @param {sap.ui.base.ManagedObject.MetadataOptions.Property} oInfo metadata for the property * @private * @ui5-restricted sap.ui.core * @see sap.ui.core.EnabledPropagator */ ManagedObjectMetadata.prototype.addProperty = function(sName, oInfo) { var oProp = this._mProperties[sName] = new Property(this, sName, oInfo); if (!this._mAllProperties[sName]) {// ensure extended AllProperties meta-data is also enriched this._mAllProperties[sName] = oProp; } if (this._fnPropertyBagFactory) { // after the property bag class is already created that has the default values of the properties, the // default value of the added property needs to be added to the property bag class as well this._fnPropertyBagFactory.prototype[sName] = oProp.getDefaultValue(); } // TODO notify listeners (subclasses) about change }; /** * Checks the existence of the given public property by its name * @param {string} sName name of the property * @return {boolean} true, if the property exists * @public */ ManagedObjectMetadata.prototype.hasProperty = function(sName) { return !!this._mAllProperties[sName]; }; /** * Returns an info object for the named public property of the described class, * no matter whether the property was defined by the class itself or by one of its * ancestor classes. * * If neither the described class nor its ancestor classes define a property with the * given name, <code>undefined</code> is returned. * * <b>Warning:</b> Type, structure and behavior of the returned info objects is not documented * and therefore not part of the API. See the {@link #constructor Notes about Info objects} * in the constructor documentation of this class. * * @param {string} sName name of the property * @returns {sap.ui.base.ManagedObject.MetadataOptions.Property|undefined} An info object describing the property or <code>undefined</code> * @public * @since 1.27.0 */ ManagedObjectMetadata.prototype.getProperty = function(sName) { var oProp = this._mAllProperties[sName]; // typeof is used as a fast (but weak) substitute for hasOwnProperty return typeof oProp === 'object' ? oProp : undefined; }; /** * Returns a map of info objects for the public properties of the described class. * Properties declared by ancestor classes are not included. * * The returned map keys the property info objects by their name. * * <b>Warning:</b> Type, structure and behavior of the returned info objects is not documented * and therefore not part of the API. See the {@link #constructor Notes about Info objects} * in the constructor documentation of this class. * * @return {Object<string,sap.ui.base.ManagedObject.MetadataOptions.Property>} Map of property info objects keyed by the property names * @public */ ManagedObjectMetadata.prototype.getProperties = function() { return this._mProperties; }; /** * Returns a map of info objects for all public properties of the described class, * including public properties from the ancestor classes. * * The returned map keys the property info objects by their name. * * <b>Warning:</b> Type, structure and behavior of the returned info objects is not documented * and therefore not part of the API. See the {@link #constructor Notes about Info objects} * in the constructor documentation of this class. * * @return {Object<string,sap.ui.base.ManagedObject.MetadataOptions.Property>} Map of property info objects keyed by the property names * @public */ ManagedObjectMetadata.prototype.getAllProperties = function() { return this._mAllProperties; }; /** * Returns a map of info objects for all private (hidden) properties of the described class, * including private properties from the ancestor classes. * * The returned map contains property info objects keyed by the property name. * * <b>Warning:</b> Type, structure and behavior of the returned info objects is not documented * and therefore not part of the API. See the {@link #constructor Notes about Info objects} * in the constructor documentation of this class. * * @return {Object<string,sap.ui.base.ManagedObject.MetadataOptions.Property>} Map of property info objects keyed by property names * @protected */ ManagedObjectMetadata.prototype.getAllPrivateProperties = function() { return this._mAllPrivateProperties; }; /** * Returns the info object for the named public or private property declared by the * described class or by any of its ancestors. * * If the name is not given (or has a falsy value), then it is substituted by the * name of the default property of the described class (if it is defined). * * <b>Warning:</b> Type, structure and behavior of the returned info objects is not documented * and therefore not part of the API. See the {@link #constructor Notes about Info objects} * in the constructor documentation of this class. * * @param {string} sName name of the property to be retrieved or empty * @returns {sap.ui.base.ManagedObject.MetadataOptions.Property|undefined} property info object or <code>undefined</code> * @protected */ ManagedObjectMetadata.prototype.getManagedProperty = function(sName) { sName = sName || this._sDefaultProperty; var oProp = sName ? this._mAllProperties[sName] || this._mAllPrivateProperties[sName] : undefined; // typeof is used as a fast (but weak) substitute for hasOwnProperty return typeof oProp === 'object' ? oProp : undefined; }; /** * Returns the name of the default property of the described class. * * If the class itself does not define a default property, then the default property * of the parent is returned. If no class in the hierarchy defines a default property, * <code>undefined</code> is returned. * * @return {string} Name of the default property */ ManagedObjectMetadata.prototype.getDefaultPropertyName = function() { return this._sDefaultProperty; }; /** * Returns an info object for the default property of the described class. * * If the class itself does not define a default property, then the * info object for the default property of the parent class is returned. * * @return {sap.ui.base.ManagedObject.MetadataOptions.Property|undefined} An info object for the default property */ ManagedObjectMetadata.prototype.getDefaultProperty = function() { return this.getProperty(this.getDefaultPropertyName()); }; // ---- aggregations ---------------------------------------------------------------------- /** * Checks the existence of the given public aggregation by its name. * @param {string} sName name of the aggregation * @return {boolean} true, if the aggregation exists * @public */ ManagedObjectMetadata.prototype.hasAggregation = function(sName) { return !!this._mAllAggregations[sName]; }; /** * Returns an info object for the named public aggregation of the described class * no matter whether the aggregation was defined by the class itself or by one of its * ancestor classes. * * If neither the class nor its ancestor classes define a public aggregation with the given * name, <code>undefined</code> is returned. * * If the name is not given (or has a falsy value), then it is substituted by the * name of the default aggregation of the 'described class' (if any). * * <b>Warning:</b> Type, structure and behavior of the returned info objects is not documented * and therefore not part of the API. See the {@link #constructor Notes about Info objects} * in the constructor documentation of this class. * * @param {string} [sName] name of the aggregation or empty * @returns {sap.ui.base.ManagedObject.MetadataOptions.Aggregation|undefined} An info object describing the aggregation or <code>undefined</code> * @public * @since 1.27.0 */ ManagedObjectMetadata.prototype.getAggregation = function(sName) { sName = sName || this._sDefaultAggregation; var oAggr = sName ? this._mAllAggregations[sName] : undefined; // typeof is used as a fast (but weak) substitute for hasOwnProperty return typeof oAggr === 'object' ? oAggr : undefined; }; /** * Returns a map of info objects for the public aggregations of the described class. * Aggregations declared by ancestor classes are not included. * * The returned map keys the aggregation info objects by their name. * In case of 0..1 aggregations this is the singular name, otherwise it is the plural name. * * <b>Warning:</b> Type, structure and behavior of the returned info objects is not documented * and therefore not part of the API. See the {@link #constructor Notes about Info objects} * in the constructor documentation of this class. * * @return {Object<string,sap.ui.base.ManagedObject.MetadataOptions.Aggregation>} Map of aggregation info objects keyed by aggregation names * @public */ ManagedObjectMetadata.prototype.getAggregations = function() { return this._mAggregations; }; /** * Returns a map of info objects for all public aggregations of the described class, * including public aggregations form the ancestor classes. * * The returned map keys the aggregation info objects by their name. * In case of 0..1 aggregations this is the singular name, otherwise it is the plural * name. * * <b>Warning:</b> Type, structure and behavior of the returned info objects is not documented * and therefore not part of the API. See the {@link #constructor Notes about Info objects} * in the constructor documentation of this class. * * @return {Object<string,sap.ui.base.ManagedObject.MetadataOptions.Aggregation>} Map of aggregation info objects keyed by aggregation names * @public */ ManagedObjectMetadata.prototype.getAllAggregations = function() { return this._mAllAggregations; }; /** * Returns a map of info objects for all private (hidden) aggregations of the described class, * including private aggregations from the ancestor classes. * * The returned map contains aggregation info objects keyed by the aggregation name. * In case of 0..1 aggregations this is the singular name, otherwise it is the plural name. * * <b>Warning:</b> Type, structure and behavior of the returned info objects is not documented * and therefore not part of the API. See the {@link #constructor Notes about Info objects} * in the constructor documentation of this class. * * @return {Object<string,sap.ui.base.ManagedObject.MetadataOptions.Aggregation>} Map of aggregation info objects keyed by aggregation names * @protected */ ManagedObjectMetadata.prototype.getAllPrivateAggregations = function() { return this._mAllPrivateAggregations; }; /** * Returns the info object for the named public or private aggregation declared by the * described class or by any of its ancestors. * * If the name is not given (or has a falsy value), then it is substituted by the * name of the default aggregation of the described class (if it is defined). * * <b>Warning:</b> Type, structure and behavior of the returned info objects is not documented * and therefore not part of the API. See the {@link #constructor Notes about Info objects} * in the constructor documentation of this class. * * @param {string} sAggregationName name of the aggregation to be retrieved or empty * @returns {sap.ui.base.ManagedObject.MetadataOptions.Aggregation|undefined} aggregation info object or <code>undefined</code> * @protected */ ManagedObjectMetadata.prototype.getManagedAggregation = function(sAggregationName) { sAggregationName = sAggregationName || this._sDefaultAggregation; var oAggr = sAggregationName ? this._mAllAggregations[sAggregationName] || this._mAllPrivateAggregations[sAggregationName] : undefined; // typeof is used as a fast (but weak) substitute for hasOwnProperty return typeof oAggr === 'object' ? oAggr : undefined; }; /** * Returns the name of the default aggregation of the described class. * * If the class itself does not define a default aggregation, then the default aggregation * of the parent is returned. If no class in the hierarchy defines a default aggregation, * <code>undefined</code> is returned. * * @return {string} Name of the default aggregation * @public * @since 1.73 */ ManagedObjectMetadata.prototype.getDefaultAggregationName = function() { return this._sDefaultAggregation; }; /** * Returns an info object for the default aggregation of the described class. * * If the class itself does not define a default aggregation, then the * info object for the default aggregation of the parent class is returned. * * @return {sap.ui.base.ManagedObject.MetadataOptions.Aggregation} An info object for the default aggregation * @public * @since 1.73 */ ManagedObjectMetadata.prototype.getDefaultAggregation = function() { return this.getAggregation(); }; /** * Defines that an aggregation <code>sForwardedSourceAggregation</code> of the ManagedObject described by this metadata * should be "forwarded" to an aggregation of an internal element within the composite. * * This means that all adding, removal, or other operations happening on the source aggregation are actually called on the target instance. * All elements added to the source aggregation will be located at the target aggregation (this means the target instance is their parent). * Both, source and target element will return the added elements when asked for the content of the respective aggregation. * If present, the named (non-generic) aggregation methods will be called for the target aggregation. * * When the source aggregation is bound, the binding will by default take place there and the add/remove operations will be forwarded to the * target. However, optionally the binding can also be forwarded. The result is similar - all added/bound items will reside at the target - * but when the binding is forwarded, the updateAggregation method is called on the target element and the add/remove methods are only called * on the target element as well. * * Aggregations can only be forw