UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

559 lines (519 loc) 19.8 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.v2.Context sap.ui.define([ "sap/ui/base/SyncPromise", "sap/ui/model/Context", "sap/ui/model/_Helper" ], function (SyncPromise, BaseContext, _Helper) { "use strict"; var aDeleteParametersAllowList = ["changeSetId", "groupId", "refreshAfterChange"]; /** * Do <strong>NOT</strong> call this private constructor. * * @param {sap.ui.model.odata.v2.ODataModel} oModel * The OData V2 model * @param {string} sPath * An absolute path without trailing slash, for example "/Products(1)/ToSupplier" * @param {string} [sDeepPath=sPath] * The absolute deep path including all intermediate paths of the binding hierarchy * @param {sap.ui.base.SyncPromise} [oCreatePromise] * A sync promise that is given when this context has been created by * {@link sap.ui.model.odata.v2.ODataModel#createEntry} or * {@link sap.ui.model.odata.v2.ODataListBinding#create}; ignored if the parameter * <code>oTransientParent</code> is given. * * When the entity represented by this context has been successfully persisted in the back * end, the given promise resolves. * * When the entity is deleted before it has been persisted in the back end via * {@link sap.ui.model.odata.v2.ODataModel#resetChanges} with the * <code>bDeleteCreatedEntities</code> parameter set to <code>true</code>, the given promise * rejects with an object <code>oError</code> containing the error information, where * <code>oError.aborted === true</code>. * @param {boolean} [bInactive] * Whether the created context is inactive * @param {boolean} [oTransientParent] * The transient parent context of this created context for the case of deep-create; if given, * the created promise of the parent context is also used for this context. * @alias sap.ui.model.odata.v2.Context * @author SAP SE * @class Implementation of an OData V2 model's context. * * The context is a pointer to model data. 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 context for the OData V2 model cannot be created at will, it has to be retrieved via: * <ul> * <li>an OData binding</li> * <li>a view element</li> * <li>{@link sap.ui.model.odata.v2.ODataListBinding#create}</li> * <li>{@link sap.ui.model.odata.v2.ODataModel#callFunction}</li> * <li>{@link sap.ui.model.odata.v2.ODataModel#createBindingContext}</li> * <li>{@link sap.ui.model.odata.v2.ODataModel#createEntry}</li> * </ul> * * @extends sap.ui.model.Context * @hideconstructor * @public * @since 1.93.0 * @version 1.111.5 */ var Context = BaseContext.extend("sap.ui.model.odata.v2.Context", { constructor : function (oModel, sPath, sDeepPath, oCreatePromise, bInactive, oTransientParent) { var that = this; BaseContext.call(this, oModel, sPath); // Promise returned by #created for a context of a newly created entity which // resolves when the entity is persisted or rejects if the creation is aborted; set // it lazily to avoid "Uncaught (in promise)" errors this.oCreatePromise = undefined; // the absolute path including all intermediate paths of the binding hierarchy; // used to compute the full target of messages this.sDeepPath = sDeepPath || sPath; // whether dependent bindings need to be refreshed this.bForceRefresh = false; // whether this context's path may be used to create the request URL for dependent // bindings even if no data has been loaded for the context's entity this.bPreliminary = false; // SyncPromise for a context created via // sap.ui.model.odata.v2.ODataModel#createEntry; used internally to detect // synchronously whether the promise is already fulfilled this.oSyncCreatePromise = oTransientParent ? oTransientParent.oSyncCreatePromise : oCreatePromise; // whether the context is updated, e.g. path changed from a preliminary path to the // canonical one this.bUpdated = false; // whether the context is inactive this.bInactive = !!bInactive; // the function to activate this context this.fnActivate = undefined; // the promise on activation of this context this.oActivatedPromise = bInactive ? new SyncPromise(function (resolve) { that.fnActivate = resolve; }) : SyncPromise.resolve(); // for a transient context, maps navigation property name to (array of) // sub-context(s) for deep create this.mSubContexts = undefined; this.oTransientParent = oTransientParent; } }); /** * Activates this context. * * @private */ Context.prototype.activate = function () { this.bInactive = false; if (this.fnActivate) { this.fnActivate(); } }; /** * Adds the given transient context as sub-context to this transient context under the given * navigation property. * * @param {string} sNavProperty The name of the navigation property * @param {sap.ui.model.odata.v2.Context} oSubContext The sub-context to be added * @param {boolean} bIsCollection Whether the navigation property is a collection * * @private */ Context.prototype.addSubContext = function (sNavProperty, oSubContext, bIsCollection) { this.mSubContexts = this.mSubContexts || {}; if (bIsCollection) { this.mSubContexts[sNavProperty] = this.mSubContexts[sNavProperty] || []; this.mSubContexts[sNavProperty].push(oSubContext); } else { this.mSubContexts[sNavProperty] = oSubContext; } }; /** * Returns a promise on the creation state of this context if it has been created via * {@link sap.ui.model.odata.v2.ODataModel#createEntry} or * {@link sap.ui.model.odata.v2.ODataListBinding#create}; otherwise returns * <code>undefined</code>. * * As long as the promise is not yet resolved or rejected, the entity represented by this * context is transient. * * Once the promise is resolved, the entity for this context is stored in the back end and * {@link #getPath} returns a path including the key predicate of the new entity. * * If the context has been created via {@link sap.ui.model.odata.v2.ODataListBinding#create} and * the entity for this context has been stored in the back end, {@link #created} returns * <code>undefined</code> after the data has been re-read from the back end and inserted at the * right position based on the list binding's filters and sorters. * If the context has been created via {@link sap.ui.model.odata.v2.ODataModel#createEntry} and * the entity for this context has been stored in the back end, {@link #created} returns * <code>undefined</code>. * * @returns {Promise<any|undefined>|undefined} * A promise for a context which has been created via * {@link sap.ui.model.odata.v2.ODataModel#createEntry} or * {@link sap.ui.model.odata.v2.ODataListBinding#create}, otherwise <code>undefined</code>. * * When the entity represented by this context has been persisted in the back end, the promise * resolves without data. * * When the entity is deleted before it has been persisted in the back end via * {@link sap.ui.model.odata.v2.ODataModel#resetChanges} with the * <code>bDeleteCreatedEntities</code> parameter set to <code>true</code>, the promise rejects * with an object <code>oError</code> containing the error information, where * <code>oError.aborted === true</code>. * * @public * @since 1.96.0 */ Context.prototype.created = function () { if (this.oSyncCreatePromise && !this.oCreatePromise) { // ensure to return a promise that is resolved w/o data this.oCreatePromise = Promise.resolve(this.oSyncCreatePromise).then(function () {}); } return this.oCreatePromise; }; /** * Deletes the OData entity this context points to. * <b>Note:</b> The context must not be used anymore after successful deletion. * * @param {object} [mParameters] * For a persistent context, a map of parameters as specified for * {@link sap.ui.model.odata.v2.ODataModel#remove}, except that the <code>groupId</code> and * <code>changeSetId</code> parameters default to the values set via * {@link sap.ui.model.odata.v2.ODataModel#setChangeGroups} for the type of the entity to be * deleted. * @param {string} [mParameters.groupId] * ID of a request group; requests belonging to the same group will be bundled in one batch * request. If not provided, the <code>groupId</code> defined for the type of the entity to be * deleted is used. * @param {string} [mParameters.changeSetId] * ID of the <code>ChangeSet</code> that this request should belong to. If not provided, the * <code>changeSetId</code> defined for the type of the entity to be deleted is used. * @param {boolean} [mParameters.refreshAfterChange] * Defines whether to update all bindings after submitting this change operation, * see {@link #setRefreshAfterChange}. If given, this overrules the model-wide * <code>refreshAfterChange</code> flag for this operation only. * @returns {Promise<undefined>} A promise resolving with <code>undefined</code> in case of * successful deletion or rejecting with an error in case the deletion failed * @throws {Error} * If the given parameter map contains any other parameter than those documented above in case * of a persistent context * * @public * @since 1.101 */ Context.prototype.delete = function (mParameters) { var sParameterKey, oModel = this.getModel(), that = this; mParameters = mParameters || {}; for (sParameterKey in mParameters) { if (!aDeleteParametersAllowList.includes(sParameterKey)) { throw new Error("Parameter '" + sParameterKey + "' is not supported"); } } if (this.isInactive()) { oModel._discardEntityChanges(oModel._getKey(this), true); oModel.checkUpdate(); return Promise.resolve(); } else if (this.isTransient()) { return oModel.resetChanges([this.getPath()], /*bAll=abort deferred requests*/false, /*bDeleteCreatedEntities*/true); } return new Promise(function (resolve, reject) { var oGroupInfo = oModel._resolveGroup(that.getPath()); oModel.remove("", _Helper.merge({ changeSetId : oGroupInfo.changeSetId, context : that, error : reject, groupId : oGroupInfo.groupId, success : function () {resolve();} }, mParameters)); }); }; /** * Returns the promise which resolves with <code>undefined</code> on activation of this context * or if this context is already active; the promise never rejects. * * @return {sap.ui.base.SyncPromise} The promise on activation of this context * * @private */ Context.prototype.fetchActivated = function () { return this.oActivatedPromise; }; /** * Gets the absolute deep path including all intermediate paths of the binding hierarchy. This * path is used to compute the full target of messages. * * @return {string} The deep path * @private */ Context.prototype.getDeepPath = function () { return this.sDeepPath; }; /** * Gets the map of sub-contexts for a deep create. * * @returns {Object<string,sap.ui.model.odata.v2.Context|sap.ui.model.odata.v2.Context[]>} * The map of sub-contexts * * @private */ Context.prototype.getSubContexts = function () { return this.mSubContexts; }; /** * Gets a flat array of the sub-contexts map for a deep create. * * @param {boolean} [bRecursive] Whether to recursively collect all deep sub-contexts * @returns {sap.ui.model.odata.v2.Context[]} The array of sub-contexts * * @private */ Context.prototype.getSubContextsArray = function (bRecursive) { var sNavProperty, vSubContexts, aSubContexts = []; function collectContexts(oSubContext) { aSubContexts.push(oSubContext); if (bRecursive) { aSubContexts = aSubContexts.concat(oSubContext.getSubContextsArray(bRecursive)); } } for (sNavProperty in this.mSubContexts) { vSubContexts = this.mSubContexts[sNavProperty]; if (Array.isArray(vSubContexts)) { vSubContexts.forEach(collectContexts); } else { collectContexts(vSubContexts); } } return aSubContexts; }; /** * Gets an array of keys to the sub-contexts for a deep create. * * @param {boolean} [bRecursive] Whether to recursively collect all deep sub-context keys * @returns {string[]} The array of sub-context keys * * @private */ Context.prototype.getSubContextsAsKey = function (bRecursive) { return this.getSubContextsArray(bRecursive).map(function (oContext) { return oContext.getPath().slice(1); }); }; /** * Gets an array of paths to the sub-contexts for a deep create. * * @param {boolean} [bRecursive] Whether to recursively collect all deep sub-context paths * @returns {string[]} The array of sub-context paths * * @private */ Context.prototype.getSubContextsAsPath = function (bRecursive) { return this.getSubContextsArray(bRecursive).map(function (oContext) { return oContext.getPath(); }); }; /** * Whether this context has changed, which means it has been updated or a refresh of dependent * bindings needs to be enforced. * * @return {boolean} Whether this context has changed * @private * @see sap.ui.model.odata.v2.Context#isUpdated * @see sap.ui.model.odata.v2.Context#isRefreshForced */ Context.prototype.hasChanged = function () { return this.bUpdated || this.bForceRefresh; }; /** * Returns whether this context has at least one sub-context. * * @return {boolean} Whether this context has at least one sub-context * * @private */ Context.prototype.hasSubContexts = function () { return !!this.mSubContexts; }; /** * Returns whether this context has a transient parent context. * * @return {boolean} Whether this context has a transient parent context * * @private */ Context.prototype.hasTransientParent = function () { return !!this.oTransientParent; }; /** * Returns whether this context is inactive. An inactive context will only be sent to the * server after the first property update. From then on it behaves like any other created * context. The result of this function can also be accessed via the * "@$ui5.context.isInactive" instance annotation at the entity, see * {@link sap.ui.model.odata.v2.ODataModel#getProperty} for details. * * @return {boolean} Whether this context is inactive * * @public * @see sap.ui.model.odata.v2.ODataListBinding#create * @see sap.ui.model.odata.v2.ODataModel#createEntry * @since 1.98.0 */ Context.prototype.isInactive = function () { return this.bInactive; }; /** * Whether this context's path may be used to create the request URL for dependent bindings even * if no data has been loaded for the context's entity. This can be used by dependent bindings * to send their requests in parallel to the request of the context binding. * * @return {boolean} Whether this context is preliminary * @private * @ui5-restricted sap.suite.ui.generic */ Context.prototype.isPreliminary = function () { return this.bPreliminary; }; /** * Whether dependent bindings of this context need to be refreshed, when the context is * propagated. * * @return {boolean} Whether dependent bindings need to be refreshed * @private */ Context.prototype.isRefreshForced = function () { return this.bForceRefresh; }; /** * For a context created using {@link sap.ui.model.odata.v2.ODataModel#createEntry} or * {@link sap.ui.model.odata.v2.ODataListBinding#create}, the method returns <code>true</code> * if the context is transient or <code>false</code> if the context is not transient. A * transient context represents an entity created on the client which has not been persisted in * the back end. The result of this function can also be accessed via the * "@$ui5.context.isTransient" instance annotation at the entity, see * {@link sap.ui.model.odata.v2.ODataModel#getProperty} for details. * * @returns {boolean|undefined} * <ul> * <li><code>true</code>: if the context has been created via * {@link sap.ui.model.odata.v2.ODataModel#createEntry} or * {@link sap.ui.model.odata.v2.ODataListBinding#create} and is not yet persisted in the * back end,</li> * <li><code>false</code>: if the context has been created via * {@link sap.ui.model.odata.v2.ODataListBinding#create}, data has been successfully * persisted in the back end and the data is still displayed in the area of the inline * creation rows, and</li> * <li><code>undefined</code>: otherwise</li> * </ul> * * @public * @since 1.94.0 */ Context.prototype.isTransient = function () { return this.oSyncCreatePromise && this.oSyncCreatePromise.isPending(); }; /** * Whether this context was updated. For example the path changed from a preliminary path to the * canonical one. * * @return {boolean} Whether the context is updated * @private */ Context.prototype.isUpdated = function () { return this.bUpdated; }; /** * Removes this context from its transient parent. * * @private */ Context.prototype.removeFromTransientParent = function () { if (this.oTransientParent) { this.oTransientParent.removeSubContext(this); delete this.oTransientParent; } }; /** * Removes the given sub-context from this context. * * @param {sap.ui.model.odata.v2.Context} oSubContext The context to remove * * @private */ Context.prototype.removeSubContext = function (oSubContext) { var iIndex, sNavProperty, vSubContexts; for (sNavProperty in this.mSubContexts) { vSubContexts = this.mSubContexts[sNavProperty]; if (Array.isArray(vSubContexts)) { iIndex = vSubContexts.indexOf(oSubContext); if (iIndex > -1) { vSubContexts.splice(iIndex, 1); } if (!vSubContexts.length) { delete this.mSubContexts[sNavProperty]; } } else if (vSubContexts === oSubContext) { delete this.mSubContexts[sNavProperty]; } } if (this.mSubContexts && !Object.keys(this.mSubContexts).length) { this.mSubContexts = undefined; } }; /** * Resets the created promise to indicate that the entity has been re-read from the back end. * * @private */ Context.prototype.resetCreatedPromise = function () { this.oCreatePromise = undefined; this.oSyncCreatePromise = undefined; }; /** * Sets the absolute deep path; see {@link #getDeepPath} for details. * * @param {string} sDeepPath The absolute deep path * @private */ Context.prototype.setDeepPath = function (sDeepPath) { this.sDeepPath = sDeepPath; }; /** * Sets whether dependent bindings need to be refreshed; see {@link #isRefreshForced} for * details. * * @param {boolean} bForceRefresh Whether dependent bindings need to be refreshed * @private */ Context.prototype.setForceRefresh = function (bForceRefresh) { this.bForceRefresh = bForceRefresh; }; /** * Sets whether this context is preliminary; see {@link #isPreliminary} for details. * * @param {boolean} bPreliminary Whether this context is preliminary * @private */ Context.prototype.setPreliminary = function (bPreliminary) { this.bPreliminary = bPreliminary; }; /** * Sets whether this context was updated; see {@link #isUpdated} for details. * * @param {boolean} bUpdated Whether this context is updated * @private */ Context.prototype.setUpdated = function (bUpdated) { this.bUpdated = bUpdated; }; return Context; });