UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

1,174 lines (1,073 loc) 222 kB
/*! * OpenUI5 * (c) Copyright 2009-2021 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ // Disable some ESLint rules. camelcase (some "_" in names to indicate indexed variables (like in math)), valid-jsdoc (not completed yet), no-warning-comments (some TODOs are left) // All other warnings, errors should be resolved /*eslint camelcase:0, valid-jsdoc:0, no-warning-comments:0 */ // Provides class sap.ui.model.odata.ODataListBinding sap.ui.define([ 'sap/ui/model/TreeBinding', 'sap/ui/model/ChangeReason', 'sap/ui/model/Filter', 'sap/ui/model/FilterOperator', 'sap/ui/model/FilterProcessor', 'sap/ui/model/FilterType', 'sap/ui/model/Sorter', 'sap/ui/model/odata/CountMode', 'sap/ui/model/TreeAutoExpandMode', './odata4analytics', './BatchResponseCollector', './AnalyticalVersionInfo', 'sap/base/util/isEmptyObject', 'sap/base/util/uid', 'sap/ui/thirdparty/jquery', 'sap/base/Log' ], function( TreeBinding, ChangeReason, Filter, FilterOperator, FilterProcessor, FilterType, Sorter, CountMode, TreeAutoExpandMode, odata4analytics, BatchResponseCollector, AnalyticalVersionInfo, isEmptyObject, uid, jQuery, Log ) { "use strict"; var sClassName = "sap.ui.model.analytics.AnalyticalBinding", oLogger = Log.getLogger(sClassName); /** * Checks whether the select binding parameter fits to the current analytical info and returns * an array of properties that need to be added to leaf requests. If the select binding * parameter does not fit to the current analytical info a warning is logged and the select * binding parameter is ignored. * Select binding parameter does not fit to the analytical info, * <ul> * <li>if an additional dimension is contained in the select binding parameter * <li>if an associated property (e.g. text property or attribute) of an additional dimension * is contained in the select binding parameter * <li>if an additional measure is contained in the select binding parameter * <li>if an associated property (e.g. text property) of an additional measure is contained in * the select binding parameter * <li>if a dimension or a measure of the current analytical info is not contained in the select * binding parameter * </ul> * * @param {sap.ui.model.analytics.AnalyticalBinding} oBinding * The analytical binding instance * @returns {string[]} An array of additional properties that need to be selected or an empty * array if there are no additional select properties needed */ function getAdditionalSelects(oBinding) { var oAnalyticalQueryRequest = new odata4analytics.QueryResultRequest(oBinding.oAnalyticalQueryResult), aComputedSelect, sComputedSelect, oDimension, i, j, oMeasure, n, sPropertyName, aSelect = oBinding.mParameters.select.split(","), bError = trimAndCheckForDuplicates(aSelect, oBinding.sPath); // prepare oAnalyticalQueryRequest to be able to call getURIQueryOptionValue("$select") oAnalyticalQueryRequest.setAggregationLevel(oBinding.aMaxAggregationLevel); oAnalyticalQueryRequest.setMeasures(oBinding.aMeasureName); // update dimension's key, text and attributes as done in relevant _prepare... functions Object.keys(oBinding.oDimensionDetailsSet).forEach(function (sDimensionKey) { oDimension = oBinding.oDimensionDetailsSet[sDimensionKey]; oAnalyticalQueryRequest.includeDimensionKeyTextAttributes(sDimensionKey, true, oDimension.textPropertyName !== undefined, oDimension.aAttributeName); }); // update measure's raw value, formatted value and unit property as done in relevant // _prepare... functions Object.keys(oBinding.oMeasureDetailsSet).forEach(function (sMeasureKey) { oMeasure = oBinding.oMeasureDetailsSet[sMeasureKey]; oAnalyticalQueryRequest.includeMeasureRawFormattedValueUnit(sMeasureKey, oMeasure.rawValuePropertyName !== undefined, oMeasure.formattedValuePropertyName !== undefined, oMeasure.unitPropertyName !== undefined); }); // at least all selected properties, computed by the binding, are contained in select // binding parameter sComputedSelect = oAnalyticalQueryRequest.getURIQueryOptionValue("$select"); if (sComputedSelect) { aComputedSelect = sComputedSelect.split(","); for (i = 0, n = aComputedSelect.length; i < n; i++) { sPropertyName = aComputedSelect[i]; j = aSelect.indexOf(sPropertyName); if (j < 0) { oLogger.warning("Ignored the 'select' binding parameter, because" + " it does not contain the property '" + sPropertyName + "'", oBinding.sPath); bError = true; } else { aSelect.splice(j, 1); } } } // check additionally selected properties, no new dimensions and new measures or // associated properties for new dimensions or measures are allowed for (i = 0, n = aSelect.length; i < n; i++) { sPropertyName = aSelect[i]; oDimension = oBinding.oAnalyticalQueryResult.findDimensionByPropertyName(sPropertyName); if (oDimension && oBinding.oDimensionDetailsSet[oDimension.getName()] === undefined) { logUnsupportedPropertyInSelect(oBinding.sPath, sPropertyName, oDimension); bError = true; } oMeasure = oBinding.oAnalyticalQueryResult.findMeasureByPropertyName(sPropertyName); if (oMeasure && oBinding.oMeasureDetailsSet[oMeasure.getName()] === undefined) { logUnsupportedPropertyInSelect(oBinding.sPath, sPropertyName, oMeasure); bError = true; } } return bError ? [] : aSelect; } /** * Logs a warning that the given select property is not supported. Either it is a dimension or * a measure or it is associated with a dimension or a measure which is not part of the * analytical info. * * @param {string} sPath The binding path * @param {string} sSelectedProperty The name of the selected property * @param {sap.ui.model.analytics.odata4analytics.Dimension * |sap.ui.model.analytics.odata4analytics.Measure} oDimensionOrMeasure * The dimension or measure that causes the issue */ function logUnsupportedPropertyInSelect(sPath, sSelectedProperty, oDimensionOrMeasure) { var sDimensionOrMeasure = oDimensionOrMeasure instanceof sap.ui.model.analytics.odata4analytics.Dimension ? "dimension" : "measure"; if (oDimensionOrMeasure.getName() === sSelectedProperty) { oLogger.warning("Ignored the 'select' binding parameter, because it contains" + " the " + sDimensionOrMeasure + " property '" + sSelectedProperty + "' which is not contained in the analytical info (see updateAnalyticalInfo)", sPath); } else { oLogger.warning("Ignored the 'select' binding parameter, because the property '" + sSelectedProperty + "' is associated with the " + sDimensionOrMeasure + " property '" + oDimensionOrMeasure.getName() + "' which is not contained in the analytical" + " info (see updateAnalyticalInfo)", sPath); } } /** * Iterate over the given array, trim each value and check whether there are duplicate entries * in the array. If there are duplicate entries a warning is logged and the duplicate is removed * from the array. * * @param {string[]} aSelect An array of strings * @param {string} sPath The binding path * @returns {boolean} <code>true</code> if there is at least one duplicate entry in the array. */ function trimAndCheckForDuplicates(aSelect, sPath) { var sCurrentProperty, bError = false, i, n; // replace all white-spaces before and after the value for (i = 0, n = aSelect.length; i < n; i++) { aSelect[i] = aSelect[i].trim(); } // check for duplicate entries and remove from list for (i = aSelect.length - 1; i >= 0; i--) { sCurrentProperty = aSelect[i]; if (aSelect.indexOf(sCurrentProperty) !== i) { // found duplicate oLogger.warning("Ignored the 'select' binding parameter, because it" + " contains the property '" + sCurrentProperty + "' multiple times", sPath); aSelect.splice(i, 1); bError = true; } } return bError; } /** * @class * Tree binding implementation for OData entity sets with aggregate semantics. * * Note on the handling of different count modes: The AnalyticalBinding always uses the OData * $inlinecount system query option to determine the total count of matching entities. It * ignores the default count mode set in the ODataModel instance and the count mode specified in * the binding parameters. If the default count mode is <code>None</code>, a warning is added to * the log to remind the application that OData requests generated by the AnalyticalBinding will * include a $inlinecount. If a count mode has been specified in the binding parameters, an * error message is logged if it is <code>None</code>, because the binding still adds the * $inlinecount to OData requests. If a binding count mode is set to <code>Request</code> or * <code>Both</code>, a warning is logged to remind the application that the OData requests * generated by the AnalyticalBinding include a $inlinecount. * * @param {sap.ui.model.Model} oModel * The OData model * @param {string} sPath * The path pointing to the tree / array that should be bound * @param {object} [oContext=null] * The context object for this data binding * @param {array} [aSorter=null] * An array of predefined sorters * @param {array} [aFilters=null] * An array of predefined filters * @param {object} [mParameters=null] * A map containing additional binding parameters * @param {sap.ui.model.TreeAutoExpandMode} [mParameters.autoExpandMode=sap.ui.model.TreeAutoExpandMode.Bundled] * The auto expand mode; applying sorters to groups is only possible in auto expand mode * {@link sap.ui.model.TreeAutoExpandMode.Sequential} * @param [mParameters.entitySet] * The entity set addressed by the last segment of the given binding path * @param [mParameters.useBatchRequests=false] * Whether multiple OData requests are wrapped into a single $batch request wherever possible * @param [mParameters.provideGrandTotals=true] * Whether grand total values are provided for all bound measure properties * @param [mParameters.provideTotalResultSize=true] * Whether the total number of matching entries in the bound OData entity set is provided * @param [mParameters.reloadSingleUnitMeasures=true] * Whether the binding checks aggregated entries with multi-unit occurrences, if some measure * properties have a unique unit and will trigger separate OData requests to fetch them * @param {string} [mParameters.select] * A comma-separated list of property names that need to be selected.<br/> * If the <code>select</code> parameter is given, it has to contain all properties that are * contained in the analytical information (see * {@link sap.ui.model.analytics.AnalyticalBinding#updateAnalyticalInfo}) and their associated * dimensions and measures. It must not contain additional dimensions or measures or * associated properties for additional dimensions or measures. But it may contain additional * properties like a text property of a dimension that is also selected.<br/> * All properties of the <code>select</code> parameter are also considered in * {@link sap.ui.model.analytics.AnalyticalBinding#getDownloadUrl}.<br/> * The <code>select</code> parameter must not contain any duplicate entry.<br/> * If the <code>select</code> parameter does not fit to the analytical information or if the * <code>select</code> parameter contains duplicates, a warning is logged and the * <code>select</code> parameter is ignored. * * @throws {Error} * If no analytic query result object could be determined from the bound OData entity set, * either from an explicitly given EntitySet (via optional mParameters.entitySet argument), or * by default implicitly from the binding path (see mandatory sPath argument). * * @alias sap.ui.model.analytics.AnalyticalBinding * @extends sap.ui.model.TreeBinding * @experimental This module is only for experimental use! * @protected */ var AnalyticalBinding = TreeBinding.extend("sap.ui.model.analytics.AnalyticalBinding", /** @lends sap.ui.model.analytics.AnalyticalBinding.prototype */ { constructor : function(oModel, sPath, oContext, aSorter, aFilters, mParameters) { TreeBinding.call(this, oModel, sPath, oContext, aFilters, mParameters); this.aAdditionalSelects = []; // attribute members for addressing the requested entity set this.sEntitySetName = (mParameters && mParameters.entitySet) ? mParameters.entitySet : undefined; // attribute members for maintaining aggregated OData requests this.bArtificalRootContext = false; // Note: aApplicationFilter is used by sap.ui.comp.smarttable.SmartTable this.aApplicationFilter = this._convertDeprecatedFilterObjects(aFilters); this.aControlFilter = undefined; this.aSorter = aSorter ? aSorter : []; this.aMaxAggregationLevel = []; this.aAggregationLevel = []; this.oPendingRequests = {}; this.oPendingRequestHandle = []; this.oGroupedRequests = {}; this.bUseBatchRequests = (mParameters && mParameters.useBatchRequests === true) ? true : false; this.bProvideTotalSize = (mParameters && mParameters.provideTotalResultSize === false) ? false : true; this.bProvideGrandTotals = (mParameters && mParameters.provideGrandTotals === false) ? false : true; this.bReloadSingleUnitMeasures = (mParameters && mParameters.reloadSingleUnitMeasures === false) ? false : true; this.bUseAcceleratedAutoExpand = (mParameters && mParameters.useAcceleratedAutoExpand === false) ? false : true; this.bNoPaging = (mParameters && mParameters.noPaging === true) ? true : false; // attribute members for maintaining loaded data; mapping from groupId to related information this.iTotalSize = -1; /* data loaded from OData service */ this.mServiceKey = {}; // keys of loaded entities belonging to group with given ID this.mServiceLength = {}; // number of currently loaded entities this.mServiceFinalLength = {}; // true iff all entities of group with given ID have been loaded (keys in mServiceKey) /* consolidated view on loaded data */ this.mKeyIndex = {}; // consumer view: group entries are index positions in mServiceKey this.mFinalLength = this.mServiceFinalLength; // true iff all entities of group with given ID have been loaded (keys in mKey) this.mLength = {}; // number of currently loaded entities /* locally created multi-currency entities */ this.mMultiUnitKey = {}; // keys of multi-currency entities this.aMultiUnitLoadFactor = {}; // compensate discarded multi-unit entities by a load factor per aggregation level to increase number of loaded entities this.bNeedsUpdate = false; // use this.aSorter to sort the groups (only for non multi-unit cases) this.bApplySortersToGroups = true; // Content of this._autoExpandMode during last call of _canApplySortersToGroups; // used for logging a warning if auto expand mode is bundled this.sLastAutoExpandMode = undefined; /* entity keys of loaded group Id's */ this.mEntityKey = {}; /* increased load factor due to ratio of non-multi-unit entities versus loaded entities */ // custom parameters which will be send with every request // the custom parameters are extracted from the mParameters object, because the SmartTable does some weird things to the parameters this.sCustomParams = this.oModel.createCustomParams({custom: this.mParameters.custom}); // attribute members for maintaining structure details requested by the binding consumer this.oAnalyticalQueryResult = null; //will be initialized via the "initialize" function of the binding this.aAnalyticalInfo = []; this.mAnalyticalInfoByProperty = {}; // maintaining request to be bundled in a single $batch request this.aBatchRequestQueue = []; // considering different count mode settings if (mParameters && mParameters.countMode == CountMode.None) { oLogger.fatal("requested count mode is ignored; OData requests will include $inlinecout options"); } else if (mParameters && (mParameters.countMode == CountMode.Request || mParameters.countMode == CountMode.Both)) { oLogger.warning("default count mode is ignored; OData requests will include $inlinecout options"); } else if (this.oModel.sDefaultCountMode == CountMode.Request) { oLogger.warning("default count mode is ignored; OData requests will include $inlinecout options"); } // detect ODataModel version this.iModelVersion = AnalyticalVersionInfo.getVersion(this.oModel); if (this.iModelVersion === null) { oLogger.error("The AnalyticalBinding does not support Models other than sap.ui.model.odata.ODataModel version 1 or 2."); return; } // list of sorted dimension names as basis for later calculations, initialized via "initialize" function this.aAllDimensionSortedByName = null; //Some setup steps have to be deferred, until the metadata was loaded by the model: // - updateAnalyticalInfo, the parameters given in the constructor are kept though // - fetch the oAnalyticalQueryResult this.aInitialAnalyticalInfo = (mParameters == undefined ? [] : mParameters.analyticalInfo); //this flag indicates if the analytical binding was initialized via initialize(), called either via bindAggregation or the Model this.bInitial = true; } }); // Creates Information for SupportTool (see e.g. library.support.js of sap.ui.table library) function createSupportInfo(oAnalyticalBinding, sErrorId) { return function() { if (!oAnalyticalBinding.__supportUID) { oAnalyticalBinding.__supportUID = uid(); } return { type: sClassName, analyticalError: sErrorId, analyticalBindingId: oAnalyticalBinding.__supportUID }; }; } /** * Setter for context * @param {Object} oContext the new context object */ AnalyticalBinding.prototype.setContext = function (oContext) { var sResolvedPath; if (this.oContext !== oContext) { this.oContext = oContext; if (!this.isRelative()) { // If binding is not a relative binding, nothing to do here return; } this.oDataState = null; this.bApplySortersToGroups = true; this.iTotalSize = -1; // invalidate last row counter this._abortAllPendingRequests(); // resolving the path makes sure that we can safely analyze the metadata, // as we have a resourcepath for the QueryResult sResolvedPath = this.oModel.resolve(this.sPath, this.oContext); if (sResolvedPath) { this.resetData(); this._initialize(); // triggers metadata/annotation check this._fireChange({ reason: ChangeReason.Context }); } else { this.bInitial = true; } } }; /** * Initialize binding. Fires a change if data is already available ($expand) or a refresh. * If metadata is not yet available, do nothing, method will be called again when * metadata is loaded. * * The ODataModel will call this on all bindings as soon as the metadata was loaded * * @public * @name sap.ui.model.analytics.v2.AnalyticalBinding#initialize * @function */ AnalyticalBinding.prototype.initialize = function() { if (this.oModel.oMetadata && this.oModel.oMetadata.isLoaded() && this.isInitial()) { // relative bindings will be properly initialized once the context is set var bIsRelative = this.isRelative(); if (!bIsRelative || (bIsRelative && this.oContext)) { this._initialize(); } this._fireRefresh({reason: ChangeReason.Refresh}); } return this; }; /** * Performs the actual initialization. * Called either by sap.ui.model.analytics.v2.AnalyticalBinding#initialize or * sap.ui.model.analytics.v2.AnalyticalBinding#setContext. */ AnalyticalBinding.prototype._initialize = function() { if (this.oModel.oMetadata && this.oModel.oMetadata.isLoaded()) { this.bInitial = false; //first fetch the analyticalQueryResult object from the adapted Model (see ODataModelAdapter.js) this.oAnalyticalQueryResult = this.oModel.getAnalyticalExtensions().findQueryResultByName(this._getEntitySet()); // Sanity check: If the AnalyticalQueryResult could not be retrieved, the AnalyticalBinding will not work correctly, // and it will sooner or later break when accessing the AnalyticalQueryResult object. if (!this.oAnalyticalQueryResult) { throw ("Error in AnalyticalBinding - The QueryResult '" + this._getEntitySet() + "' could not be retrieved. Please check your service definition."); } //afterwards update the analyticalInfo with the initial parameters given in the constructor this.updateAnalyticalInfo(this.aInitialAnalyticalInfo); //initialize the list of sorted dimension names this.aAllDimensionSortedByName = this.oAnalyticalQueryResult.getAllDimensionNames().concat([]).sort(); this._fireRefresh({reason: ChangeReason.Refresh}); } }; /* ******************************* *** API - Public methods ********************************/ /** * Gets the context for the root aggregation level representing the grand total for all bound measure properties. * * The context is assigned to parent group ID <code>null</code>. If the binding is configured not to provide a grand total, * this context is empty. If data for this context is not locally available yet, an OData request will be triggered to load it. * * This function must be called whenever the bound set of OData entities changes, e.g., by changing selected dimensions, * modifying filter conditions, etc. * * @function * @name sap.ui.model.analytics.AnalyticalBinding.prototype.getRootContexts * @param {object} * mParameters specifying how the top-most aggregation level shall be fetched. Supported parameters are: * <ul> * <li>numberOfExpandedLevels: number of child levels that shall be fetched automatically</li> * <li>startIndex: index of first entry to return from parent group ID <code>"/"</code> (zero-based)</li> * <li>length: number of entries to return at and after the given start index</li> * <li>threshold: number of additional entries that shall be locally available in the binding for subsequent * accesses to contexts of parent group ID <code>"/"</code> or below, if auto-expanding is selected</li> * </ul> * @return {array} * Array with a single object of class sap.ui.model.Context for the root context, * or an empty array if an OData request is pending to fetch requested contexts that are not yet locally available. * * @public */ AnalyticalBinding.prototype.getRootContexts = function(mParameters) { if (this.isInitial()) { return []; } var iAutoExpandGroupsToLevel = (mParameters && mParameters.numberOfExpandedLevels ? mParameters.numberOfExpandedLevels + 1 : 1); // this._trace_enter("API", "getRootContexts", "", mParameters, ["numberOfExpandedLevels", "startIndex","length","threshold"]); // DISABLED FOR PRODUCTION var aRootContext = null; var sRootContextGroupMembersRequestId = this._getRequestId(AnalyticalBinding._requestType.groupMembersQuery, {groupId: null}); // if the root context is artificial (i.e. no grand total requested), then delay its return until all other related requests have been completed if (this.bArtificalRootContext && !this._cleanupGroupingForCompletedRequest(sRootContextGroupMembersRequestId)) { // this._trace_leave("API", "getRootContexts", "delay until related requests have been completed"); // DISABLED FOR PRODUCTION return aRootContext; } aRootContext = this._getContextsForParentContext(null); if (aRootContext.length == 1) { // this._trace_leave("API", "getRootContexts", "", aRootContext, ["length"]); // DISABLED FOR PRODUCTION return aRootContext; } if (iAutoExpandGroupsToLevel <= 1) { if (iAutoExpandGroupsToLevel == 1) { this._considerRequestGrouping([ sRootContextGroupMembersRequestId, this._getRequestId(AnalyticalBinding._requestType.groupMembersQuery, {groupId: "/"}) ]); this.getNodeContexts(this.getModel().getContext("/"), { startIndex : mParameters.startIndex, length : mParameters.length, threshold : mParameters.threshold, level : 0, numberOfExpandedLevels : 0 }); } } else { var aRequestId = this._prepareGroupMembersAutoExpansionRequestIds("/", mParameters.numberOfExpandedLevels); aRequestId.push(sRootContextGroupMembersRequestId); this._considerRequestGrouping(aRequestId); this.getNodeContexts(this.getModel().getContext("/"), { startIndex : mParameters.startIndex, length : mParameters.length, threshold : mParameters.threshold, level : 0, numberOfExpandedLevels : mParameters.numberOfExpandedLevels }); /* oLogger.fatal("not yet implemented: number of initially expanded levels may be 0 or 1, but not " + mParameters.numberOfExpandedLevels); */ } if (aRootContext.length > 1) { oLogger.fatal("assertion failed: grand total represented by a single entry"); } // this._trace_leave("API", "getRootContexts", "", aRootContext, ["length"]); // DISABLED FOR PRODUCTION return aRootContext; }; /** * Gets child contexts for a specified parent context. * * Contexts are returned in a stable order imposed by the * dimension property that defines this aggregation level beneath the parent context: Either a sort order has been specified for this property, * or the entries are returned in ascending order of the values of this dimension property by default. * * If any of the requested data is missing, an OData request will be triggered to load it. * * @function * @name sap.ui.model.analytics.AnalyticalBinding.prototype.getNodeContexts * @param {object} * mParameters specifying the aggregation level for which contexts shall be fetched. Supported parameters are: * <ul> * <li>oContext: parent context identifying the requested group of child contexts</li> * <li>level: level number for oContext, because it might occur at multiple levels; context with group ID <code>"/"</code> has level 0</li> * <li>numberOfExpandedLevels: number of child levels that shall be fetched automatically</li> * <li>startIndex: index of first child entry to return from the parent context (zero-based)</li> * <li>length: number of entries to return; counting begins at the given start index</li> * <li>threshold: number of additional entries that shall be locally available in the binding for subsequent * accesses to child entries of the given parent context. </li> * </ul> * @return {array} * Array containing the requested contexts of class sap.ui.model.Context, limited by the number of entries contained * in the entity set at that aggregation level. * The array will contain less than the requested number of contexts, if some are not locally available and an OData request is * pending to fetch them. In this case, if the parameter numberOfExpandedLevels > 0, the array will be completely empty. * * @public */ AnalyticalBinding.prototype.getNodeContexts = function(oContext, mParameters) { if (this.isInitial()) { return []; } // this._trace_enter("API", "getNodeContexts", "groupId=" + this._getGroupIdFromContext(oContext, mParameters.level), mParameters,["startIndex","length","threshold"]); // DISABLED FOR PRODUCTION var iStartIndex, iLength, iThreshold, iLevel, iNumberOfExpandedLevels, bSupressRequest; if (typeof mParameters == "object") { iStartIndex = mParameters.startIndex; iLength = mParameters.length; iThreshold = mParameters.threshold; iLevel = mParameters.level; iNumberOfExpandedLevels = mParameters.numberOfExpandedLevels; bSupressRequest = mParameters.supressRequest; } else { // due to compatibility; can be removed if table is adapted iStartIndex = arguments[1]; iLength = arguments[2]; iThreshold = arguments[3]; iLevel = arguments[4]; iNumberOfExpandedLevels = arguments[5]; bSupressRequest = arguments[6]; } var aContext = this._getContextsForParentContext(oContext, iStartIndex, iLength, iThreshold, iLevel, iNumberOfExpandedLevels, bSupressRequest); // this._trace_leave("API", "getNodeContexts", "", aContext, ["length"]); // DISABLED FOR PRODUCTION return aContext; }; AnalyticalBinding.prototype.ContextsAvailabilityStatus = { ALL: 2, SOME: 1, NONE: 0 }; /** * Determines if the binding has the entries of a given aggregation level locally available. * * If so, no further OData request is required to fetch any of them. * @function * @name sap.ui.model.analytics.AnalyticalBinding.prototype.hasAvailableNodeContexts * @param {sap.ui.model.Context} * oContext the parent context identifying the aggregation level. * @param {int} * iLevel the level number of oContext (because the context might occur at multiple levels). * @return {boolean} * property of sap.ui.model.analytics.AnalyticalBinding.ContextsAvailabilityStatus, * indicating whether all, some, or none of the entries are locally available. * @public */ AnalyticalBinding.prototype.hasAvailableNodeContexts = function(oContext, iLevel) { var sGroupId = this._getGroupIdFromContext(oContext, iLevel); if (this._getKeys(sGroupId) != undefined) { if (this.mFinalLength[sGroupId] == true) { return AnalyticalBinding.prototype.ContextsAvailabilityStatus.ALL; } else { return AnalyticalBinding.prototype.ContextsAvailabilityStatus.SOME; } } else { return AnalyticalBinding.prototype.ContextsAvailabilityStatus.NONE; } }; /** * Gets the total number of contexts contained in a group, if known. * * For a given group, be aware that the group size might vary over time. In principle, this can happen if the * bound set of OData entities includes measure properties with amount or quantity values. The AnalyticalBinding * recognizes situations where the OData service returns multiple entries for a single group entry due to the fact that a * measure property cannot be aggregated properly, because an amount exists in multiple currencies or a quantity exists * in multiple units. In such situations, the AnalyticalBinding substitutes these entries by a single representative, and * the group size gets reduced by the count of duplicate entries. Finally, since the Binding does not always fetch all children of * a group at once, but only a page with a certain range, such size changes might happen after every page access. * @function * @name sap.ui.model.analytics.AnalyticalBinding.prototype.getGroupSize * @param {sap.ui.model.Context} * oContext the parent context identifying the requested group of child contexts. * @param {int} * iLevel the level number of oContext (because the context might occur at multiple levels) * @return {int} * The currently known group size, or -1, if not yet determined * @public */ AnalyticalBinding.prototype.getGroupSize = function(oContext, iLevel) { if (oContext === undefined) { return 0; // API robustness } var sGroupId = this._getGroupIdFromContext(oContext, iLevel); return this.mFinalLength[sGroupId] ? this.mLength[sGroupId] : -1; }; /** * Gets the total number of entities in the bound OData entity set. * * Counting takes place at the lowest aggregation level defined by the possible value * combinations for the complete set of dimension properties included in the bound entity set. * This means that intermediate aggregate entities with sub-totals at higher aggregation levels * are not counted. * * @return {number} * The total number of addressed entities in the OData entity set, or -1 if the number is * not yet known or if the binding parameter <code>provideTotalResultSize</code> is set to * <code>false</code> * * @public */ AnalyticalBinding.prototype.getTotalSize = function() { if (!this.bProvideTotalSize) { oLogger.fatal("total size of result explicitly turned off, but getter invoked"); } return +this.iTotalSize; }; /** * Determines if the contexts in a specified group have further children. If so, * any of these group contexts can be a parent context of a nested sub-group in * a subsequent aggregation level. * * @function * @name sap.ui.model.analytics.AnalyticalBinding.prototype.hasChildren * @param {sap.ui.model.Context} * oContext the parent context identifying the requested group of child contexts. * @param {object} * mParameters The only supported parameter is level as the level number of oContext (because the context might occur at multiple levels) * @return {boolean} * true if and only if the contexts in the specified group have further children. * @public */ AnalyticalBinding.prototype.hasChildren = function(oContext, mParameters) { if (oContext === undefined) { return false; // API robustness } if (oContext == null) { return true; } var iContextLevel = mParameters.level; if (iContextLevel == 0) { return true; } if (this.aAggregationLevel.length < iContextLevel) { return false; } // children exist if it is not the rightmost grouped column or there is at least one further level with an ungrouped groupable column. return this.aMaxAggregationLevel.indexOf(this.aAggregationLevel[iContextLevel - 1]) < this.aMaxAggregationLevel.length - 1; }; /** * Determines if any of the properties included in the bound OData entity set is a measure property. * * @function * @name sap.ui.model.analytics.AnalyticalBinding.prototype.hasMeasures * @return {boolean} * true if and only one or more properties are measure properties. * * @public */ AnalyticalBinding.prototype.hasMeasures = function() { var bHasMeasures = false; for (var p in this.oMeasureDetailsSet) { if (this.oMeasureDetailsSet.hasOwnProperty(p)) { bHasMeasures = true; break; } } return bHasMeasures; }; /** * Gets details about the dimension properties included in the bound OData entity set. * * @function * @name sap.ui.model.analytics.AnalyticalBinding.prototype.getDimensionDetails * @return {object} * details for every dimension property addressed by its name. The details object provides these properties: name of the dimension, * keyPropertyName for the name of the property holding the dimension key, textPropertyName for the name of the property holding the * text for the dimension, aAttributeName listing all properties holding dimension attributes, grouped as indicator whether or not this * dimension is currently grouped, and analyticalInfo, which contains the binding information for this dimension passed from the * AnalyticalBinding's consumer via call to function updateAnalyticalInfo. * * @public */ AnalyticalBinding.prototype.getDimensionDetails = function() { return this.oDimensionDetailsSet; }; /** * Gets details about the measure properties included in the bound OData entity set. * * @function * @name sap.ui.model.analytics.AnalyticalBinding.prototype.getMeasureDetails * @return {object} * details for every measure property addressed by its name. The details object provides these properties: name of the measure, * rawValuePropertyName for the name of the property holding the raw value, unitPropertyName for the name of the property holding the related * value unit or currency, if any, and analyticalInfo, which contains the binding information for this measure passed from the * AnalyticalBinding's consumer via call to function updateAnalyticalInfo. * * @public */ AnalyticalBinding.prototype.getMeasureDetails = function() { return this.oMeasureDetailsSet; }; /** * Determines if the binding has been configured to provide a grand total for the selected measure properties. * * @function * @name sap.ui.model.analytics.AnalyticalBinding.prototype.providesGrandTotal * @return {boolean} * true if and only if the binding provides a context for the grand totals of all selected measure properties. * * @public */ AnalyticalBinding.prototype.providesGrandTotal = function() { return this.bProvideGrandTotals; }; /** * Gets the metadata of a property with a given name. * * @function * @name sap.ui.model.analytics.AnalyticalBinding.prototype.getProperty * @param {string} * sPropertyName The property name. * @return {object} * OData metadata of this property or null if it does not exist. * @public */ AnalyticalBinding.prototype.getProperty = function(sPropertyName) { if (this.isInitial()) { return {}; } return this.oAnalyticalQueryResult.getEntityType().findPropertyByName(sPropertyName); }; /** * Gets the names of the filterable properties in the bound OData entity set. * * @function * @name sap.ui.model.analytics.AnalyticalBinding.prototype.getFilterablePropertyNames * @returns {array} * names of properties that can be filtered. * * @public */ AnalyticalBinding.prototype.getFilterablePropertyNames = function() { if (this.isInitial()) { return []; } return this.oAnalyticalQueryResult.getEntityType().getFilterablePropertyNames(); }; /** * Gets the names of the sortable properties in the bound OData entity set. * * @function * @name sap.ui.model.analytics.AnalyticalBinding.prototype.getSortablePropertyNames * @returns {array} * names of properties that can be used for sorting the result entities. * * @public */ AnalyticalBinding.prototype.getSortablePropertyNames = function() { if (this.isInitial()) { return []; } return this.oAnalyticalQueryResult.getEntityType().getSortablePropertyNames(); }; /** * Gets the label of a property with a given name. * * @function * @name sap.ui.model.analytics.AnalyticalBinding.prototype.getPropertyLabel * @param {string} * sPropertyName The property name. * @returns {string} * The label maintained for this property or null if it does not exist. * * @public */ AnalyticalBinding.prototype.getPropertyLabel = function(sPropertyName) { if (this.isInitial()) { return ""; } return this.oAnalyticalQueryResult.getEntityType().getLabelOfProperty(sPropertyName); }; /** * Gets the label of a property with a given name. * * @function * @name sap.ui.model.analytics.AnalyticalBinding.prototype.getPropertyHeading * @param {string} * sPropertyName The property name. * @returns {string} * The heading maintained for this property or null if it does not exist. * * @public */ AnalyticalBinding.prototype.getPropertyHeading = function(sPropertyName) { if (this.isInitial()) { return ""; } return this.oAnalyticalQueryResult.getEntityType().getHeadingOfProperty(sPropertyName); }; /** * Gets the quick info of a property with a given name. * * @function * @name sap.ui.model.analytics.AnalyticalBinding.prototype.getPropertyQuickInfo * @param {string} * sPropertyName The property name. * @returns {string} * The quick info maintained for this property or null if it does not exist. * * @public */ AnalyticalBinding.prototype.getPropertyQuickInfo = function(sPropertyName) { if (this.isInitial()) { return ""; } return this.oAnalyticalQueryResult.getEntityType().getQuickInfoOfProperty(sPropertyName); }; /** * Determines if a given name refers to a measure property * * @function * @name sap.ui.model.analytics.AnalyticalBinding.prototype.isMeasure * @param {string} * sPropertyName The property name. * @return {boolean} * true if and only if the bound OData entity set includes a measure property with this name. * * @public */ AnalyticalBinding.prototype.isMeasure = function(sPropertyName) { return this.aMeasureName && this.aMeasureName.indexOf(sPropertyName) !== -1; }; /** * Sets filters for matching only a subset of the entities in the bound OData entity set. * * Invoking this function resets the state of the binding. Subsequent data requests such as calls to getNodeContexts() will * need to trigger OData requests in order to fetch the data that are in line with these filters. * * @function * @name sap.ui.model.analytics.AnalyticalBinding.prototype.filter * @param {sap.ui.model.Filter[]|sap.ui.model.Filter} * aFilter an Array of sap.ui.model.Filter objects or a single Filter instance. * @param {sap.ui.model.FilterType} * [sFilterType=sap.ui.model.FilterType.Control] Type of the filter which should be adjusted. * @return {this} * returns <code>this</code> to facilitate method chaining * * @public */ AnalyticalBinding.prototype.filter = function(aFilter, sFilterType) { //ensure at least an empty array, so the later validation of odata4analytics.js does not fail if (!aFilter) { aFilter = []; } // wrap filter argument in an array if it's a single instance if (aFilter instanceof Filter) { aFilter = [aFilter]; } aFilter = this._convertDeprecatedFilterObjects(aFilter); if (sFilterType == FilterType.Application) { this.aApplicationFilter = aFilter; } else { this.aControlFilter = aFilter; } this.iTotalSize = -1; // invalidate last row counter this._abortAllPendingRequests(); this.resetData(); // resets the flag to sort groups by this.aSorter; a new filter might resolve a multi-unit // case; do it before refresh event is fired this.bApplySortersToGroups = true; this._fireRefresh({ reason : ChangeReason.Filter }); return this; }; /** * Returns the filter information as an abstract syntax tree. * Consumers must not rely on the origin information to be available, future filter * implementations will not provide this information. * * @param {boolean} [bIncludeOrigin=false] whether to include information about the filter * objects from which the tree has been created * @returns {object} The AST of the filter tree or null if no filters are set * @private * @ui5-restricted sap.ui.table, sap.ui.export */ //@override AnalyticalBinding.prototype.getFilterInfo = function(bIncludeOrigin) { var oCombinedFilter = FilterProcessor.combineFilters(this.aControlFilter, this.aApplicationFilter); if (oCombinedFilter) { return oCombinedFilter.getAST(bIncludeOrigin); } return null; }; /** * Sets sorters for retrieving the entities in the bound OData entity set in a specific order. * * Invoking this function resets the state of the binding. Subsequent data requests such as calls to getNodeContexts() will * need to trigger OData requests in order to fetch the data that are in line with these sorters. * * @function * @name sap.ui.model.analytics.AnalyticalBinding.prototype.sort * @param {sap.ui.model.Sorter|array} * aSorter a sorter object or an array of sorter objects which define the sort order. * @return {this} * returns <code>this</code> to facilitate method chaining. * * @public */ AnalyticalBinding.prototype.sort = function(aSorter) { if (aSorter instanceof Sorter) { aSorter = [ aSorter ]; } this.aSorter = aSorter ? aSorter : []; this._abortAllPendingRequests(); this.resetData(undefined, {reason: ChangeReason.Sort}); this._fireRefresh({ reason : ChangeReason.Sort }); return this; }; /** * Gets a printable name for a group. * * The printable name follows the pattern is <code>&lt;label&gt;:&lt;key-value&gt;[-&lt;text-value&gt;]</code>, * where <code>label</code> is the label of the dimension property used at the aggregation level for the group, * <code>key-value</code> is the key value of that dimension for the group, and <code>text-value</code> is the * value of the associated text property, if it is also used in the binding. * * Whenever a formatter function has been defined for a column displaying the key or text of this dimension, the return value * of this function is applied for the group name instead of the respective key or text value. * * @function * @name sap.ui.model.analytics.AnalyticalBinding.prototype.getGroupName * @param {sap.ui.model.Context} * oContext the parent context identifying the requested group. * @param {int} * iLevel the level number of oContext (because the context might occur at multiple levels) * @return {string} a printable name for the group. * @public */ AnalyticalBinding.prototype.getGroupName = function(oContext, iLevel) { if (oContext === undefined) { return ""; // API robustness } var sGroupProperty = this.aAggregationLevel[iLevel - 1], oDimension = this.oAnalyticalQueryResult.findDimensionByPropertyName(sGroupProperty), // it might happen that grouped property is not contained in the UI (e.g. if grouping is // done with a dimension's text property) fValueFormatter = this.mAnalyticalInfoByProperty[sGroupProperty] && this.mAnalyticalInfoByProperty[sGroupProperty].formatter, sPropertyValue = oContext.getProperty(sGroupProperty), sFormattedPropertyValue, sFormattedTextPropertyValue, sGroupName, sLabelText, oTextProperty, sTextProperty, sTextPropertyValue, fTextValueFormatter; if (oDimension && this.oDimensionDetailsSet[sGroupProperty].textPropertyName) { oTextProperty = oDimension.getTextProperty(); } if (oTextProperty) { sTextProperty = oTextProperty.name; // it might happen that text property is not contained in the UI fTextValueFormatter = this.mAnalyticalInfoByProperty[sTextProperty] && this.mAnalyticalInfoByProperty[sTextProperty].formatter; sTextPropertyValue = oContext.getProperty(sTextProperty); sFormattedPropertyValue = fValueFormatter ? fValueFormatter(sPropertyValue, sTextPropertyValue) : sPropertyValue; sFormattedTextPropertyValue = fTextValueFormatter ? fTextValueFormatter(sTextPropertyValue, sPropertyValue) : sTextPropertyValue; } else { sFormattedPropertyValue = fValueFormatter ? fValueFormatter(sPropertyValue) : sPropertyValue; } sLabelText = oDimension.getLabelText && oDimension.getLabelText(); sGroupName = (sLabelText ? sLabelText + ': ' : '') + sFormattedPropertyValue; if (sFormattedTextPropertyValue) { sGroupName += ' - ' + sFormattedTextPropertyValue; } return sGroupName; }; /** * Updates the binding's structure with new analytical information. * * Analytical information is the mapping of UI columns to properties in the bound OData entity set. Every column object contains * the name of the bound property and in addition: * <ol> * <li>A column bound to a dimension property has further boolean properties: * <ul> * <li>grouped: dimension will be used for building groups</li> * <li>visible: if the column is visible, values for the related property will be fetched from the OData service</li> * <li>inResult: if the column is not visible, but declared to be part of the result, values for the related property * will also be fetched from the OData service</li> * </ul> * </li> * <li>A column bound to a measure property has further boolean properties: * <ul> * <li>total: totals and sub-totals will be provided for the measure at all aggregation levels</li> * </ul> * </li> * <li>A column bound to a hierarchy property has further properties: * <ul> * <li>grouped: boolean value; indicates whether the hierarchy will be used for building * groups</li> * <li>level: integer value; the hierarchy level is mandatory for at least one of those * columns that represent the same hierarchy.</li> * </ul> * </li> * </ol> * * Invoking this function resets the state of the binding and subsequent data requests such as calls to getNodeContexts() will * need to trigger OData requests in order to fetch the data that are in line with this analytical information. * * Please be aware that a call of this function might lead to additional back-end requests, as well as a control re-rendering later on. * Whenever possible use the API of the analytical control, instead of relying on the binding. * * @function * @name sap.ui.model.analytics.AnalyticalBinding.prototype.updateAnalyticalInfo * @param {array} * aColumns an array with objects holding the analytical information for every column, from left to right. * @protected */ AnalyticalBinding.prototype.updateAnalyticalInfo = function(aColumns, bForceChange) { var iDiff, oDimensionDetails, oEntityType, aHierarchyProperties, that = this; /* * If the given analytical column is related to a hierarchy, add or update the corresponding * entry in <code>that.mHierarchyDetailsByName</code>. * @param {object} The analytical info for an analytical column */ function addOrUpdateHierarchy(oColumn) { var iLevel = oColumn.level, sName = oColumn.name; aHierarchyProperties = aHierarchyProperties || oEntityType.getAllHierarchyPropertyNames(); aHierarchyProperties.forEach(function (sHierarchyName) { var oHierarchy = that.oAnalyticalQueryResult .findDimensionByPropertyName(sHierarchyName).getHierarchy(), oHierarchyDetails = null, // each hierarchy has a node ID property, see post processing in // sap.ui.model.analytics.odata4analytics.EntityType.prototype._init sNodeIDName = oHierarchy.getNodeIDProperty().name, oProperty; if (sNodeIDName === sName) { oHierarchyDetails = getOrCreateHierarchyDetails(oHierarchy); } else { oProperty = oHierarchy.getNodeExternalKeyProperty(); if (oP