UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

1,280 lines (1,213 loc) 81 kB
/*! * OpenUI5 * (c) Copyright 2009-2023 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ //Provides class sap.ui.model.odata.v4.Context sap.ui.define([ "./lib/_Helper", "sap/base/Log", "sap/ui/base/SyncPromise", "sap/ui/model/Context" ], function (_Helper, Log, SyncPromise, BaseContext) { "use strict"; var sClassName = "sap.ui.model.odata.v4.Context", // generation counter to distinguish old from new iGenerationCounter = 0, oModule, // index of virtual context used for auto-$expand/$select iVIRTUAL = -9007199254740991/*Number.MIN_SAFE_INTEGER*/, /** * @alias sap.ui.model.odata.v4.Context * @author SAP SE * @class Implementation of an OData V4 model's context. * * The context is a pointer to model data as returned by a query from an * {@link sap.ui.model.odata.v4.ODataContextBinding} or an * {@link sap.ui.model.odata.v4.ODataListBinding}. Contexts are always and only * created by such bindings. A context for a context binding points to the complete query * result. A context for a list binding points to one specific entry in the binding's * collection. A property binding does not have a context, you can access its value via * {@link sap.ui.model.odata.v4.ODataPropertyBinding#getValue}. * * Applications can access model data only via a context, either synchronously with the * risk that the values are not available yet ({@link #getProperty} and * {@link #getObject}) or asynchronously ({@link #requestProperty} and * {@link #requestObject}). * * Context instances are immutable except for their indexes. * @extends sap.ui.model.Context * @hideconstructor * @public * @since 1.39.0 * @version 1.111.5 */ Context = BaseContext.extend("sap.ui.model.odata.v4.Context", { constructor : constructor }); //********************************************************************************************* // Context //********************************************************************************************* /** * Do <strong>NOT</strong> call this private constructor. In the OData V4 model you cannot * create contexts at will: retrieve them from a binding or a view element instead. * * @param {sap.ui.model.odata.v4.ODataModel} oModel * The model * @param {sap.ui.model.odata.v4.ODataContextBinding|sap.ui.model.odata.v4.ODataListBinding} oBinding * A binding that belongs to the model * @param {string} sPath * An absolute path without trailing slash * @param {number} [iIndex] * Index of item (within the collection addressed by <code>sPath</code>) represented * by this context; used by list bindings, not context bindings * @param {sap.ui.base.SyncPromise} [oCreatePromise] * A promise which is resolved with the created entity when the POST request has been * successfully sent and the entity has been marked as non-transient; used as base for * {@link #created} * @param {number} [iGeneration=0] * The unique number for this context's generation, which can be retrieved via * {@link #getGeneration} * @param {boolean} [bInactive] * Whether this context is inactive and will only be sent to the server after the first * property update * @throws {Error} * If an invalid path is given */ function constructor(oModel, oBinding, sPath, iIndex, oCreatePromise, iGeneration, bInactive) { if (sPath[0] !== "/") { throw new Error("Not an absolute path: " + sPath); } if (sPath.endsWith("/")) { throw new Error("Unsupported trailing slash: " + sPath); } BaseContext.call(this, oModel, sPath); this.oBinding = oBinding; this.oCreatedPromise = oCreatePromise // ensure to return a promise that is resolved w/o data && Promise.resolve(oCreatePromise).then(function () {}); this.oSyncCreatePromise = oCreatePromise; // a promise waiting for the deletion, also used as indicator for #isDeleted this.oDeletePromise = null; this.iGeneration = iGeneration || 0; this.bInactive = bInactive || undefined; // be in sync with the annotation this.iIndex = iIndex; this.bKeepAlive = false; this.bSelected = false; this.fnOnBeforeDestroy = undefined; } /** * Adjusts this context's path by replacing the given transient predicate with the given * predicate. Recursively adjusts all child bindings. * * @param {string} sTransientPredicate * The transient predicate to be replaced * @param {string} sPredicate * The new predicate * @param {function} [fnPathChanged] * A function called with the old and the new path * * @private */ Context.prototype.adjustPredicate = function (sTransientPredicate, sPredicate, fnPathChanged) { var sTransientPath = this.sPath; this.sPath = sTransientPath.replace(sTransientPredicate, sPredicate); if (fnPathChanged) { fnPathChanged(sTransientPath, this.sPath); } this.oModel.getDependentBindings(this).forEach(function (oDependentBinding) { oDependentBinding.adjustPredicate(sTransientPredicate, sPredicate); }); }; /** * Updates all dependent bindings of this context. * * @private */ Context.prototype.checkUpdate = function () { this.oModel.getDependentBindings(this).forEach(function (oDependentBinding) { oDependentBinding.checkUpdate(); }); }; /** * Updates all dependent bindings of this context. * * @returns {sap.ui.base.SyncPromise} * A promise resolving without a defined result when the update is finished * @private */ Context.prototype.checkUpdateInternal = function () { return SyncPromise.all( this.oModel.getDependentBindings(this).map(function (oDependentBinding) { return oDependentBinding.checkUpdateInternal(); }) ); }; /** * Collapses the group node that this context points to. * * @throws {Error} * If the context points to a node that is not expandable, already collapsed, or * is a grand total. * * @public * @see #expand * @see #isExpanded * @since 1.83.0 */ Context.prototype.collapse = function () { switch (this.getProperty("@$ui5.node.level") === 0 ? undefined : this.isExpanded()) { case true: this.oBinding.collapse(this); break; case false: throw new Error("Already collapsed: " + this); default: throw new Error("Not expandable: " + this); } }; /** * Returns a promise that is resolved without data when the entity represented by this context * has been created in the back end and all selected properties of this entity are available. * Expanded navigation properties are only available if the context's binding is refreshable. * {@link sap.ui.model.odata.v4.ODataContextBinding#refresh} and * {@link sap.ui.model.odata.v4.ODataListBinding#refresh} describe which bindings are * refreshable. * * As long as the promise is not yet resolved or rejected, the entity represented by this * context is transient. * * Once the promise is resolved, {@link #getPath} returns a path including the key predicate * of the new entity. This requires that all key properties are available. * * @returns {Promise<void>|undefined} * A promise that is resolved without data when the entity represented by this context has * been created in the back end. It is rejected with an <code>Error</code> instance where * <code>oError.canceled === true</code> if the transient entity is deleted before it is * created in the back end, for example via {@link sap.ui.model.odata.v4.Context#delete}, * {@link sap.ui.model.odata.v4.ODataListBinding#resetChanges} or * {@link sap.ui.model.odata.v4.ODataModel#resetChanges}. It is rejected with an * <code>Error</code> instance without <code>oError.canceled</code> if loading of $metadata * fails. Returns <code>undefined</code> if the context has not been created using * {@link sap.ui.model.odata.v4.ODataListBinding#create}. * * @public * @since 1.43.0 */ Context.prototype.created = function () { return this.oCreatedPromise; }; /** * Deletes the OData entity this context points to. The context is removed from the binding * immediately, even if {@link sap.ui.model.odata.v4.SubmitMode.API} is used, and the request is * only sent later when {@link sap.ui.model.odata.v4.ODataModel#submitBatch} is called. As soon * as the context is deleted on the client, {@link #isDeleted} returns <code>true</code> and the * context must not be used anymore (except for status APIs like {@link #isDeleted}, * {@link #isKeepAlive}, {@link #hasPendingChanges}, {@link #resetChanges}), especially not as a * binding context. * * Since 1.105 such a pending deletion is a pending change. It causes * <code>hasPendingChanges</code> to return <code>true</code> for the context, the binding * containing it, and the model. The <code>resetChanges</code> method called on the context * (since 1.109.0), the binding, or the model cancels the deletion and restores the context. * * If the DELETE request succeeds, the context is destroyed and must not be used anymore. If it * fails or is canceled, the context is restored, reinserted into the list, and fully functional * again. * * If the deleted context is used as binding context of a control or view, the application is * advised to unbind it via * <code>{@link sap.ui.base.ManagedObject#setBindingContext setBindingContext(null)}</code> * before calling <code>delete</code>, and to possibly rebind it after reset or failure. The * model itself ensures that all bindings depending on this context become unresolved, but no * attempt is made to restore these bindings in case of reset or failure. * * @param {string} [sGroupId] * The group ID to be used for the DELETE request; if not specified, the update group ID for * the context's binding is used, see {@link #getUpdateGroupId}. Since 1.81, if this context * is transient (see {@link #isTransient}), no group ID needs to be specified. Since 1.98.0, * you can use <code>null</code> to prevent the DELETE request in case of a kept-alive context * that is not in the collection and of which you know that it does not exist on the server * anymore (for example, a draft after activation). Since 1.108.0 the usage of a group ID with * {@link sap.ui.model.odata.v4.SubmitMode.API} is possible. * @param {boolean} [bDoNotRequestCount] * Whether not to request the new count from the server; useful in case of * {@link #replaceWith} where it is known that the count remains unchanged (since 1.97.0). * Since 1.98.0, this is implied if a <code>null</code> group ID is used. * @returns {Promise} * A promise which is resolved without a result in case of success, or rejected with an * instance of <code>Error</code> in case of failure, for example if: * <ul> * <li> the given context does not point to an entity, * <li> the deletion on the server fails, * <li> the deletion is canceled via <code>resetChanges</code> (in this case the error * instance has the property <code>canceled</code> with value <code>true</code>). * </ul> * The error instance has the property <code>isConcurrentModification</code> with value * <code>true</code> in case a concurrent modification (e.g. by another user) of the entity * between loading and deletion has been detected; this should be shown to the user who needs * to decide whether to try deletion again. If the entity does not exist, we assume it has * already been deleted by someone else and report success. * @throws {Error} If * <ul> * <li> the given group ID is invalid, * <li> this context's root binding is suspended, * <li> a <code>null</code> group ID is used with a context which is not * {@link #isKeepAlive kept alive}, * <li> the context is already being deleted, * <li> the context's binding is a list binding with data aggregation, * </ul> * * @function * @public * @see #hasPendingChanges * @see #resetChanges * @see sap.ui.model.odata.v4.ODataContextBinding#hasPendingChanges * @see sap.ui.model.odata.v4.ODataListBinding#hasPendingChanges * @see sap.ui.model.odata.v4.ODataModel#hasPendingChanges * @see sap.ui.model.odata.v4.ODataContextBinding#resetChanges * @see sap.ui.model.odata.v4.ODataListBinding#resetChanges * @see sap.ui.model.odata.v4.ODataModel#resetChanges * @since 1.41.0 */ Context.prototype.delete = function (sGroupId, bDoNotRequestCount/*, bRejectIfNotFound*/) { var oEditUrlPromise, oGroupLock = null, that = this; if (this.isDeleted()) { throw new Error("Must not delete twice: " + this); } if (this.oBinding.mParameters.$$aggregation) { throw new Error("Cannot delete " + this + " when using data aggregation"); } this.oBinding.checkSuspended(); if (this.isTransient()) { sGroupId = null; } else if (sGroupId === null) { if (!(this.bKeepAlive && this.iIndex === undefined)) { throw new Error("Cannot delete " + this); } } if (sGroupId === null) { oEditUrlPromise = SyncPromise.resolve(); bDoNotRequestCount = true; } else { _Helper.checkGroupId(sGroupId); oEditUrlPromise = this.fetchCanonicalPath().then(function (sCanonicalPath) { return sCanonicalPath.slice(1); }); oGroupLock = this.oBinding.lockGroup(sGroupId, true, true); } return Promise.resolve( oEditUrlPromise.then(function (sEditUrl) { return that.oBinding.delete(oGroupLock, sEditUrl, that, /*oETagEntity*/null, bDoNotRequestCount, function () { that.oDeletePromise = null; } ); }).catch(function (oError) { if (oGroupLock) { oGroupLock.unlock(true); } throw oError; }) ); }; /** * Destroys this context, that is, it removes this context from all dependent bindings and drops * references to binding and model, so that the context cannot be used anymore; it keeps path * and index for debugging purposes. * * <b>BEWARE:</b> Do not call this function! The lifetime of an OData V4 context is completely * controlled by its binding. * * @public * @see sap.ui.base.Object#destroy * @since 1.41.0 */ // @override sap.ui.base.Object#destroy Context.prototype.destroy = function () { var fnOnBeforeDestroy = this.fnOnBeforeDestroy; if (fnOnBeforeDestroy) { // avoid second call through a destroy inside the callback this.fnOnBeforeDestroy = undefined; fnOnBeforeDestroy(); } this.oModel.getDependentBindings(this).forEach(function (oDependentBinding) { oDependentBinding.setContext(undefined); }); this.oBinding = undefined; this.oCreatedPromise = undefined; // keep oDeletePromise so that isDeleted does not unexpectedly become false this.oSyncCreatePromise = undefined; this.bInactive = undefined; this.bKeepAlive = undefined; this.bSelected = false; // When removing oModel, ManagedObject#getBindingContext does not return the destroyed // context although the control still refers to it this.oModel = undefined; BaseContext.prototype.destroy.call(this); }; /** * Deletes the OData entity this context points to. * * @param {sap.ui.model.odata.v4.lib._GroupLock} [oGroupLock] * A lock for the group ID to be used for the DELETE request; w/o a lock, no DELETE is sent. * For a transient entity, the lock is ignored (use NULL)! * @param {string} [sEditUrl] * The entity's edit URL to be used for the DELETE request; only required with a lock * @param {string} sPath * The path of the entity relative to this binding * @param {object} [oETagEntity] * An entity with the ETag of the binding for which the deletion was requested. This is * provided if the deletion is delegated from a context binding with empty path to a list * binding. W/o a lock, this is ignored. * @param {sap.ui.model.odata.v4.ODataParentBinding} oBinding * The binding to perform the deletion at * @param {function} fnCallback * A function which is called immediately when an entity has been deleted from the cache, or * when it was re-inserted; the index of the entity and an offset (-1 for deletion, 1 for * re-insertion) are passed as parameter * @returns {sap.ui.base.SyncPromise} * A promise which is resolved without a result in case of success, or rejected with an * instance of <code>Error</code> in case of failure * * @private * @see sap.ui.model.odata.v4.Context#delete */ Context.prototype.doDelete = function (oGroupLock, sEditUrl, sPath, oETagEntity, oBinding, fnCallback) { var oModel = this.oModel, that = this; this.oDeletePromise = oBinding.deleteFromCache( oGroupLock, sEditUrl, sPath, oETagEntity, fnCallback ).then(function () { var sResourcePathPrefix = that.sPath.slice(1); // Messages have been updated via _Cache#_delete; "that" is already destroyed; remove // all dependent caches in all bindings oModel.getAllBindings().forEach(function (oBinding) { oBinding.removeCachesAndMessages(sResourcePathPrefix, true); }); }).catch(function (oError) { oModel.reportError("Failed to delete " + that.getPath(), sClassName, oError); that.checkUpdate(); throw oError; }); if (oGroupLock && this.oModel.isApiGroup(oGroupLock.getGroupId())) { oModel.getDependentBindings(this).forEach(function (oDependentBinding) { oDependentBinding.setContext(undefined); }); } return this.oDeletePromise; }; /** * Sets the new current value and updates the cache. * * @param {string} sPath * A path relative to this context * @param {any} vValue * The new value which must be primitive * @param {sap.ui.model.odata.v4.lib._GroupLock} [oGroupLock] * A lock for the group ID to be used for the PATCH request; without a lock, no PATCH is sent * @param {boolean} [bSkipRetry] * Whether to skip retries of failed PATCH requests and instead fail accordingly, but still * fire "patchSent" and "patchCompleted" events * @param {boolean} [bUpdating] * Whether the given property will not be overwritten by a creation POST(+GET) response * @returns {sap.ui.base.SyncPromise} * A promise which is resolved without a result in case of success, or rejected with an * instance of <code>Error</code> in case of failure, for example if the annotation belongs to * the read-only namespace "@$ui5.*" * @throws {Error} If the context is deleted * * @private */ Context.prototype.doSetProperty = function (sPath, vValue, oGroupLock, bSkipRetry, bUpdating) { var oModel = this.oModel, oMetaModel = oModel.getMetaModel(), oPromise, oValue, that = this; if (this.isDeleted()) { if (oGroupLock) { oGroupLock.unlock(); } throw new Error("Must not modify a deleted entity: " + this); } if (oGroupLock && this.isTransient() && !this.isInactive()) { oValue = this.getValue(); oPromise = oValue && _Helper.getPrivateAnnotation(oValue, "transient"); if (oPromise instanceof Promise) { oGroupLock.unlock(); oGroupLock = oGroupLock.getUnlockedCopy(); this.doSetProperty(sPath, vValue, null, true, true) // early UI update .catch(this.oModel.getReporter()); return SyncPromise.resolve(oPromise).then(function (bSuccess) { // in case of success, wait until creation is completed because context path's // key predicate is adjusted return bSuccess && that.created(); }).then(function () { return that.doSetProperty(sPath, vValue, oGroupLock, bSkipRetry); }); } } if (this.oModel.bAutoExpandSelect) { sPath = oMetaModel.getReducedPath( this.oModel.resolve(sPath, this), this.oBinding.getBaseForPathReduction()); } return this.withCache(function (oCache, sCachePath, oBinding) { return oBinding.doSetProperty(sCachePath, vValue, oGroupLock) || oMetaModel.fetchUpdateData(sPath, that, !oGroupLock).then(function (oResult) { var sEntityPath = _Helper.getRelativePath(oResult.entityPath, oBinding.oReturnValueContext ? oBinding.oReturnValueContext.getPath() : oBinding.getResolvedPath()), // If a PATCH is merged into a POST request, firePatchSent is not called, // thus don't call firePatchCompleted bFirePatchCompleted = false; /* * Error callback to report the given error and fire "patchCompleted" * accordingly. * * @param {Error} oError */ function errorCallback(oError) { oModel.reportError("Failed to update path " + oModel.resolve(sPath, that), sClassName, oError); firePatchCompleted(false); } /* * Fire "patchCompleted" according to the given success flag, if needed. * * @param {boolean} bSuccess */ function firePatchCompleted(bSuccess) { if (bFirePatchCompleted) { oBinding.firePatchCompleted(bSuccess); bFirePatchCompleted = false; } } /* * Fire "patchSent" and remember to later fire "patchCompleted". */ function patchSent() { bFirePatchCompleted = true; oBinding.firePatchSent(); } if (!oGroupLock) { return oCache.setProperty(oResult.propertyPath, vValue, sEntityPath, bUpdating); } if (that.isInactive()) { // early cache update so that the new value is properly available on the // event listener // runs synchronously - setProperty calls fetchValue with $cached oCache.setProperty(oResult.propertyPath, vValue, sEntityPath, bUpdating) .catch(that.oModel.getReporter()); if (oBinding.fireCreateActivate(that)) { that.bInactive = false; } else { that.bInactive = 1; } } // if request is canceled fnPatchSent and fnErrorCallback are not called and // returned Promise is rejected -> no patch events return oCache.update(oGroupLock, oResult.propertyPath, vValue, bSkipRetry ? undefined : errorCallback, oResult.editUrl, sEntityPath, oMetaModel.getUnitOrCurrencyPath(that.oModel.resolve(sPath, that)), oBinding.isPatchWithoutSideEffects(), patchSent, that.isKeepAlive.bind(that), that.bInactive ).then(function () { firePatchCompleted(true); }, function (oError) { firePatchCompleted(false); throw oError; }); }); }, sPath, /*bSync*/false, /*bWithOrWithoutCache*/true); }; /** * Expands the group node that this context points to. * * @throws {Error} * If the context points to a node that is not expandable or already expanded * * @public * @see #collapse * @see #isExpanded * @since 1.77.0 */ Context.prototype.expand = function () { switch (this.isExpanded()) { case false: this.oBinding.expand(this).catch(this.oModel.getReporter()); break; case true: throw new Error("Already expanded: " + this); default: throw new Error("Not expandable: " + this); } }; /** * Returns a promise for the "canonical path" of the entity for this context. * * @returns {sap.ui.base.SyncPromise} * A promise which is resolved with the canonical path (e.g. "/SalesOrderList('0500000000')") * in case of success, or rejected with an instance of <code>Error</code> in case of failure, * e.g. if the given context does not point to an entity * * @private */ Context.prototype.fetchCanonicalPath = function () { return this.oModel.getMetaModel().fetchCanonicalPath(this); }; /** * Fetches and formats the primitive value at the given path. * * @param {string} sPath The requested path, absolute or relative to this context * @param {boolean} [bExternalFormat] * If <code>true</code>, the value is returned in external format using a UI5 type for the * given property path that formats corresponding to the property's EDM type and constraints. * @param {boolean} [bCached] * Whether to return cached values only and not trigger a request * @returns {sap.ui.base.SyncPromise} a promise on the formatted value * * @private */ Context.prototype.fetchPrimitiveValue = function (sPath, bExternalFormat, bCached) { var oError, aPromises = [this.fetchValue(sPath, null, bCached)], sResolvedPath = this.oModel.resolve(sPath, this); if (bExternalFormat) { aPromises.push( this.oModel.getMetaModel().fetchUI5Type(sResolvedPath)); } return SyncPromise.all(aPromises).then(function (aResults) { var oType = aResults[1], vValue = aResults[0]; if (vValue && typeof vValue === "object") { oError = new Error("Accessed value is not primitive: " + sResolvedPath); oError.isNotPrimitive = true; throw oError; } return bExternalFormat ? oType.formatValue(vValue, "string") : vValue; }); }; /** * Delegates to the <code>fetchValue</code> method of this context's binding which requests * the value for the given path. A relative path is assumed to be relative to this context and * is reduced before accessing the cache if the model uses autoExpandSelect. * * @param {string} [sPath] * A path (absolute or relative to this context) * @param {sap.ui.model.odata.v4.ODataPropertyBinding} [oListener] * A property binding which registers itself as listener at the cache * @param {boolean} [bCached] * Whether to return cached values only and not trigger a request * @returns {sap.ui.base.SyncPromise} * A promise on the outcome of the binding's <code>fetchValue</code> call; it is rejected * in case cached values are asked for, but not found * * @private */ Context.prototype.fetchValue = function (sPath, oListener, bCached) { var oBinding = this.oBinding; if (this.iIndex === iVIRTUAL) { return SyncPromise.resolve(); // no cache access for virtual contexts } if (oBinding.getHeaderContext && oBinding.getHeaderContext() === this) { if (sPath && sPath.startsWith(this.sPath)) { sPath = sPath.slice(this.sPath.length + 1); } if (!sPath) { return oBinding.fetchValue(this.sPath, oListener, bCached) .then(function (aElements) { return {$count : aElements.$count}; }); } else if (sPath !== "$count") { throw new Error("Invalid header path: " + sPath); } } if (!sPath || sPath[0] !== "/") { // Create an absolute path based on the context's path and reduce it. This is only // necessary for data access via Context APIs, bindings already use absolute paths. sPath = this.oModel.resolve(sPath, this); if (this.oModel.bAutoExpandSelect) { sPath = this.oModel.getMetaModel() .getReducedPath(sPath, this.oBinding.getBaseForPathReduction()); } } return this.oBinding.fetchValue(sPath, oListener, bCached); }; /** * Returns the binding this context belongs to. * * @returns {sap.ui.model.odata.v4.ODataContextBinding|sap.ui.model.odata.v4.ODataListBinding} * The context's binding * * @public * @since 1.39.0 */ Context.prototype.getBinding = function () { return this.oBinding; }; /** * Returns the "canonical path" of the entity for this context. According to <a href= * "https://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part2-url-conventions.html#canonical-urlurl4.1.1" * >"4.3.1 Canonical URL"</a> of the specification "OData Version 4.0 Part 2: URL Conventions", * this is the "name of the entity set associated with the entity followed by the key predicate * identifying the entity within the collection". Use the canonical path in * {@link sap.ui.core.Element#bindElement} to create an element binding. * * Note: For a transient context (see {@link #isTransient}) a wrong path is returned unless all * key properties are available within the initial data. * * @returns {string} * The canonical path (e.g. "/SalesOrderList('0500000000')") * @throws {Error} * If the canonical path cannot be determined yet or in case of failure, e.g. if the given * context does not point to an entity * * @function * @public * @since 1.39.0 */ Context.prototype.getCanonicalPath = _Helper.createGetMethod("fetchCanonicalPath", true); /** * Returns the unique number of this context's generation, or <code>0</code> if it does not * belong to any specific generation. This number can be inherited from a parent binding. * * @param {boolean} [bOnlyLocal] * Whether the local generation w/o inheritance is returned * @returns {number} * The unique number of this context's generation, or <code>0</code> * * @private * @see sap.ui.model.odata.v4.Context.createNewContext * @see #setNewGeneration */ Context.prototype.getGeneration = function (bOnlyLocal) { if (this.iGeneration || bOnlyLocal) { return this.iGeneration; } return this.oBinding.getGeneration(); }; /** * Returns the group ID of the context's binding that is used for read requests. See * {@link sap.ui.model.odata.v4.ODataListBinding#getGroupId} and * {@link sap.ui.model.odata.v4.ODataContextBinding#getGroupId}. * * @returns {string} * The group ID * * @public * @since 1.81.0 */ Context.prototype.getGroupId = function () { return this.oBinding.getGroupId(); }; /** * Returns the context's index within the binding's collection. The return value changes when a * new entity is added via {@link sap.ui.model.odata.v4.ODataListBinding#create} without * <code>bAtEnd</code>, and when a context representing a created entity is deleted again. * * @returns {number|undefined} * The context's index within the binding's collection. It is <code>undefined</code> if * <ul> * <li> it does not belong to a list binding, * <li> it is {@link #isKeepAlive kept alive}, but not in the collection currently. * </ul> * * @public * @since 1.39.0 */ Context.prototype.getIndex = function () { if (this.iIndex === undefined) { return undefined; } if (this.oBinding.isFirstCreateAtEnd()) { if (this.iIndex < 0) { // this does not include undefined for a kept-alive context return this.oBinding.bLengthFinal ? this.oBinding.iMaxLength - this.iIndex - 1 : -this.iIndex - 1; } return this.iIndex; } return this.getModelIndex(); }; /** * Returns the model index, which is the context's index in the binding's collection. This * differs from the view index if entities have been created at the end. Internally such * contexts still are kept at the start of the collection. For this reason the return value * changes if a new entity is added via {@link sap.ui.model.odata.v4.ODataListBinding#create} * or deleted again. * * @returns {number} * The context's index within the binding's collection. It is <code>undefined</code> if * <ul> * <li> it does not belong to a list binding, * <li> it is {@link #isKeepAlive kept alive}, but not in the collection currently. * </ul> * * @private */ Context.prototype.getModelIndex = function () { if (this.iIndex !== undefined && this.oBinding.iCreatedContexts) { return this.iIndex + this.oBinding.iCreatedContexts; } return this.iIndex; }; /** * Returns the value for the given path relative to this context. The function allows access to * the complete data the context points to (if <code>sPath</code> is "") or any part thereof. * The data is a JSON structure as described in <a href= * "https://docs.oasis-open.org/odata/odata-json-format/v4.0/odata-json-format-v4.0.html" * >"OData JSON Format Version 4.0"</a>. * Note that the function clones the result. Modify values via * {@link sap.ui.model.odata.v4.ODataPropertyBinding#setValue}. * * Returns <code>undefined</code> if the data is not (yet) available; no request is triggered. * Use {@link #requestObject} for asynchronous access. * * The header context of a list binding only delivers <code>$count</code> (wrapped in an object * if <code>sPath</code> is ""). * * @param {string} [sPath=""] * A path relative to this context * @returns {any} * The requested value * @throws {Error} * If the context's root binding is suspended or if the context is a header context and the * path is neither empty nor "$count". * * @public * @see sap.ui.model.Context#getObject * @since 1.39.0 */ // @override sap.ui.model.Context#getObject Context.prototype.getObject = function (sPath) { return _Helper.publicClone(this.getValue(sPath)); }; /** * Returns the property value for the given path relative to this context. The path is expected * to point to a structural property with primitive type. Returns <code>undefined</code> * if the data is not (yet) available; no request is triggered. Use {@link #requestProperty} * for asynchronous access. * * @param {string} sPath * A path relative to this context * @param {boolean} [bExternalFormat] * If <code>true</code>, the value is returned in external format using a UI5 type for the * given property path that formats corresponding to the property's EDM type and constraints. * If the type is not yet available, <code>undefined</code> is returned. * @returns {any} * The requested property value * @throws {Error} If * <ul> * <li> the context's root binding is suspended, * <li> the value is not primitive, * <li> or the context is a header context and the path is not "$count" * </ul> * * @public * @see sap.ui.model.Context#getProperty * @see sap.ui.model.odata.v4.ODataMetaModel#requestUI5Type * @since 1.39.0 */ // @override sap.ui.model.Context#getProperty Context.prototype.getProperty = function (sPath, bExternalFormat) { var oError, oSyncPromise; this.oBinding.checkSuspended(); oSyncPromise = this.fetchPrimitiveValue(sPath, bExternalFormat, true); if (oSyncPromise.isRejected()) { oSyncPromise.caught(); oError = oSyncPromise.getResult(); if (oError.isNotPrimitive) { throw oError; } else if (!oError.$cached) { // Note: errors due to data requests have already been logged Log.warning(oError.message, sPath, sClassName); } } return oSyncPromise.isFulfilled() ? oSyncPromise.getResult() : undefined; }; /** * Returns the query options from the associated binding for the given path. * * @param {string} sPath * The relative path for which the query options are requested * @returns {object} * The query options from the associated binding (live reference, no clone!) * * @private */ Context.prototype.getQueryOptionsForPath = function (sPath) { return this.oBinding.getQueryOptionsForPath(sPath); }; /** * Returns the group ID of the context's binding that is used for update requests. See * {@link sap.ui.model.odata.v4.ODataListBinding#getUpdateGroupId} and * {@link sap.ui.model.odata.v4.ODataContextBinding#getUpdateGroupId}. * * @returns {string} * The update group ID * * @public * @since 1.81.0 */ Context.prototype.getUpdateGroupId = function () { return this.oBinding.getUpdateGroupId(); }; /** * Returns the value for the given path relative to this context. The function allows access to * the complete data the context points to (if <code>sPath</code> is "") or any part thereof. * The data is a JSON structure as described in <a href= * "https://docs.oasis-open.org/odata/odata-json-format/v4.0/odata-json-format-v4.0.html" * >"OData JSON Format Version 4.0"</a>. * Note that the function returns the cache instance. Do not modify the result, use * {@link sap.ui.model.odata.v4.ODataPropertyBinding#setValue} instead. * * Returns <code>undefined</code> if the data is not (yet) available; no request is triggered. * * @param {string} [sPath=""] * A path relative to this context * @returns {any} * The requested value * @throws {Error} * If the context's root binding is suspended * * @private */ Context.prototype.getValue = function (sPath) { var oSyncPromise, that = this; this.oBinding.checkSuspended(); oSyncPromise = this.fetchValue(sPath, null, true) .catch(function (oError) { if (!oError.$cached) { that.oModel.reportError("Unexpected error", sClassName, oError); } }); if (oSyncPromise.isFulfilled()) { return oSyncPromise.getResult(); } }; /** * Returns whether there are pending changes for bindings dependent on this context, or for * unresolved bindings (see {@link sap.ui.model.Binding#isResolved}) which were dependent on * this context at the time the pending change was created. This includes the context itself * being {@link #isTransient transient} or {@link #delete deleted} on the client, but not yet on * the server. Since 1.98.0, {@link #isInactive inactive} contexts are ignored, unless their * {@link sap.ui.model.odata.v4.ODataListBinding#event:createActivate activation} has been * prevented and therefore {@link #isInactive} returns <code>1</code>. * * @returns {boolean} * Whether there are pending changes * * @public * @since 1.53.0 */ Context.prototype.hasPendingChanges = function () { return this.isTransient() && this.isInactive() !== true || this.oDeletePromise && this.oDeletePromise.isPending() || this.getBinding().hasPendingChangesForPath(this.sPath) || this.oModel.getDependentBindings(this).some(function (oDependentBinding) { return oDependentBinding.oCache ? oDependentBinding._hasPendingChanges(false, true) : oDependentBinding.hasPendingChangesInDependents(false, true); }) || this.oModel.withUnresolvedBindings("hasPendingChangesInCaches", this.sPath.slice(1)); }; /** * Returns whether this context is deleted. It becomes <code>true</code> immediately after * calling {@link #delete}, even while the request is waiting for * {@link sap.ui.model.odata.v4.ODataModel#submitBatch submitBatch} or is in process. It becomes * <code>false</code> again when the DELETE request fails or is canceled. The result of this * function can also be accessed via the "@$ui5.context.isDeleted" instance annotation at the * entity. * * @returns {boolean} <code>true</code> if this context is deleted * * @public * @see #delete * @since 1.105.0 */ Context.prototype.isDeleted = function () { return !!this.oDeletePromise; }; /** * Tells whether the group node that this context points to is expanded. * * @returns {boolean|undefined} * Whether the group node that this context points to is expanded, or <code>undefined</code> * if the node is not expandable * * @public * @see #collapse * @see #expand * @since 1.77.0 */ Context.prototype.isExpanded = function () { return this.getProperty("@$ui5.node.isExpanded"); }; /** * Returns whether this context is inactive. The result of this function can also be accessed * via instance annotation "@$ui5.context.isInactive" at the entity. * * Since 1.110.0, <code>1</code> is returned in case * {@link sap.ui.model.odata.v4.ODataListBinding#event:createActivate activation} has been * prevented. Note that * <ul> * <li> it is truthy: <code>!!1 === true</code>, * <li> it is almost like <code>true</code>: <code>1 == true</code>, * <li> but it can easily be distinguished: <code>1 !== true</code>, * <li> and <code>if (oContext.isInactive()) {...}</code> treats inactive contexts the same, * no matter whether activation has been prevented or not. * </ul> * * @returns {boolean|number|undefined} <code>true</code> if this context is inactive, * <code>false</code> if it was created in an inactive state and has been activated, * <code>1</code> in case activation has been prevented (since 1.110.0), and * <code>undefined</code> otherwise. * * @public * @see #isTransient * @see sap.ui.model.odata.v4.ODataListBinding#create * @since 1.98.0 */ Context.prototype.isInactive = function () { return this.bInactive; }; /** * Returns whether this context is kept alive even when it is removed from its binding's * collection, for example if a filter is applied and the entity represented by this context * does not match the filter criteria. * * @returns {boolean} <code>true</code> if this context is kept alive * * @public * @see #setKeepAlive * @since 1.81.0 */ Context.prototype.isKeepAlive = function () { return this.bKeepAlive; }; /** * Tells whether this context is currently selected. * * @returns {boolean} Whether this context is currently selected * * @experimental As of version 1.111.0 * @public * @see #setSelected */ Context.prototype.isSelected = function () { return this.bSelected; }; /** * For a context created using {@link sap.ui.model.odata.v4.ODataListBinding#create}, the * method returns <code>true</code> if the context is transient, meaning that the promise * returned by {@link #created} is not yet resolved or rejected, and returns <code>false</code> * if the context is not transient. The result of this function can also be accessed via * instance annotation "@$ui5.context.isTransient" at the entity. * * @returns {boolean|undefined} * Whether this context is transient if it is created using * {@link sap.ui.model.odata.v4.ODataListBinding#create}; <code>undefined</code> if it is not * created using {@link sap.ui.model.odata.v4.ODataListBinding#create} * * @public * @see #isInactive * @since 1.43.0 */ Context.prototype.isTransient = function () { return this.oSyncCreatePromise && this.oSyncCreatePromise.isPending(); }; /** * Patches the context data with the given patch data. * * @param {object} oData * The data to patch with * @returns {sap.ui.base.SyncPromise} * A promise that is resolved without a result when the patch is done. * * @private */ Context.prototype.patch = function (oData) { return this.withCache(function (oCache, sPath) { oCache.patch(sPath, oData); }, ""); }; /** * Refreshes the single entity represented by this context. Use {@link #requestRefresh} if you * want to wait for the refresh. * * @param {string} [sGroupId] * The group ID to be used for the refresh; if not specified, the group ID for the context's * binding is used, see {@link #getGroupId}. * @param {boolean} [bAllowRemoval] * If the context belongs to a list binding, the parameter allows the list binding to remove * the context from the list binding's collection because the entity does not match the * binding's filter anymore, see {@link sap.ui.model.odata.v4.ODataListBinding#filter}; * a removed context is destroyed, see {@link #destroy}. If the context belongs to a context * binding, the parameter must not be used. * Supported since 1.55.0 * * Since 1.84.0, if this context is {@link #isKeepAlive kept alive}, it is only destroyed if * the corresponding entity does no longer exist in the back end. In this case, the * <code>fnOnBeforeDestroy</code> callback passed with {@link #setKeepAlive}) is called. * @throws {Error} * If the group ID is not valid, if this context has pending changes or does not represent a * single entity (see {@link sap.ui.model.odata.v4.ODataListBinding#getHeaderContext}), if the * binding is not refreshable or is a list binding with data aggregation, if its root binding * is suspended, or if the parameter <code>bAllowRemoval</code> is set for a context belonging * to a context binding. * * @public * @since 1.53.0 */ Context.prototype.refresh = function (sGroupId, bAllowRemoval) { // eslint-disable-line no-unused-vars this.requestRefresh.apply(this, arguments).catch(this.oModel.getReporter()); }; /** * Refreshes all dependent bindings with the given parameters and waits for them to have * finished. * * @param {string} sResourcePathPrefix * The resource path prefix which is used to delete the dependent caches and corresponding * messages; may be "" but not <code>undefined</code> * @param {string} [sGroupId] * The group ID to be used for refresh * @param {boolean} [bCheckUpdate] * If <code>true</code>, a property binding is expected to check for updates * @param {boolean} [bKeepCacheOnError] * If <code>true</code>, the binding data remains unchanged if the refresh fails * @returns {sap.ui.base.SyncPromise} * A promise resolving when all dependent bindings are refreshed; it is rejected * when the refresh fails; the promise is resolved immediately on a suspended binding * @throws {Error} * If the binding's root binding is suspended and a group ID different from the binding's * group ID is given * * @private */ Context.prototype.refreshDependentBindings = function (sResourcePathPrefix, sGroupId, bCheckUpdate, bKeepCacheOnError) { return SyncPromise.all( this.oModel.getDependentBindings(this).map(function (oDependentBinding) { return oDependentBinding.refreshInternal(sResourcePathPrefix, sGroupId, bCheckUpdate, bKeepCacheOnError); }) ); }; /** * Replaces this context with the given other context "in situ", that is, at the index it * currently has in its list binding's collection. You probably want to delete this context * afterwards without requesting the new count from the server, see the * <code>bDoNotRequestCount</code> parameter of {@link #delete}. * * @param {sap.ui.model.odata.v4.Context} oOtherContext - The other context * @throws {Error} If * <ul> * <li> this context's root binding is suspended, * <li> this context is {@link #isTransient transient}, * <li> this context is {@link #isDeleted deleted}, * <li> the given other context * <ul> * <li> does not belong to the same list binding as this context, * <li> is already in the collection (has an {@link #getIndex index}), * <li> is {@link #delete deleted}, * <li> or is not {@link #isKeepAlive kept alive}. * </ul> * </ul> * * @public * @since 1.97.0 */ Context.prototype.replaceWith = function (oOtherContext) { var oElement; this.oBinding.checkSuspended(); if (this.isTransient() || this.isDeleted()) { throw new Error("Cannot replace " + this); } if (oOtherContext.oBinding !== this.oBinding || oOtherContext.iIndex !== undefined || oOtherContext.isDeleted() || !oOtherContext.bKeepAlive) { throw new Error("Cannot replace with " + oOtherContext); } oElement = oOtherContext.getValue(); this.oBinding.doReplaceWith(this, oElement, _Helper.getPrivateAnnotation(oElement, "predicate")); }; /** * Returns a promise for the "canonical path" of the entity for this context. According to * <a href= * "https://docs.oasis-open.org/odata/odata/v4.0/odata-v4.0-part2-url-conventions.html#canonical-urlurl4.1.1" * >"4.3.1 Canonical URL"</a> of the specification "OData Version 4.0 Part 2: URL Conventions", * this is the "name of the entity set associated with the entity followed by the key predicate * identifying the entity within the collection". Use the canonical path in * {@link sap.ui.core.Element#bindElement} to create an element binding. * * Note: For a transient context (see {@link #isTransient}) a wrong path is returned unless all * key properties are available within the initial data. * * @returns {Promise} * A promise which is resolved with the canonical path (e.g. "/SalesOrderList('0500000000')") * in case of success, or rejected with an instance of <code>Error</code> in case of failure, * e.g. if the given context does not point to an entity * * @function * @public * @since 1.39.0 */ Context.prototype.requestCanonicalPath = _Helper.createRequestMethod("fetchCanonicalPath"); /** * Returns a promise on the value for the given path relative to this context. The function * allows access to the complete data the context points to (if <code>sPath</code> is "") or * any part thereof. The data is a JSON structure as described in <a href= * "https://docs.oasis-open.org/odata/odata-json-format/v4.0/odata-json-format-v4.0.html" * >"OData JSON Format Version 4.0"</a>. * Note that the function clones the result. Modify values via * {@link sap.ui.model.odata.v4.Context#setProperty}. * * The header context of a list binding only delivers <code>$count</code> (wrapped in an object * if <code>sPath</code> is ""). * * If you want {@link #requestObject} to read fresh data, call {@link #refresh} first. * * @param {string} [sPath=""] * A path relative to this context * @returns {Promise} * A promise on the requested value; it is rejected if the context is a header context and the * path is neither empty nor "$count". * @throws {Error} * If the context's root binding is suspended * * @public * @see #getBinding * @see sap.ui.model.odata.v4.ODataContextBinding#refresh * @see sap.ui.model.odata.v4.ODataListBinding#refresh * @since 1.39.0 */ Context.prototype.requestObject = function (sPath) { this.oBinding.checkSuspended(); return Promise.resolve(this.fetchValue(sPath)).then(_Helper.publicClone); }; /** * Returns a promise on the property value for the given path relative to this context. The path * is expected to point to a structural property with primitive type. * Since 1.81.1 it is possible to request more than one property. Property values that are not * cached yet are requested from the back end. * * @param {string|string[]} [vPath] * One or multiple paths relative to this context * @param {boolean} [bExternalFormat] * If <code>true</code>, the values are returned in external format using UI5 types for the * given property paths that format corresponding to the properties' EDM types and constraints * @returns {Promise} * A promise on the requested value or values; it is rejected if a value is not primitive or * if the context is a header context and a path is not "$count" * @throws {Error} *