UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

897 lines (772 loc) 33.9 kB
/*! * OpenUI5 * (c) Copyright 2026 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ /*eslint-disable max-len */ // Provides class sap.ui.model.odata.ODataAnnotations sap.ui.define([ "./AnalyticalBinding", "sap/base/assert", "sap/base/Log", "sap/base/util/each", "sap/ui/model/ChangeReason", "sap/ui/model/TreeAutoExpandMode", "sap/ui/model/TreeBinding", "sap/ui/model/TreeBindingAdapter", "sap/ui/model/odata/ODataTreeBindingAdapter" ], function(AnalyticalBinding, assert, Log, each, ChangeReason, TreeAutoExpandMode, TreeBinding, TreeBindingAdapter, ODataTreeBindingAdapter) { "use strict"; /** * Adapter for TreeBindings to add the ListBinding functionality and use the * tree structure in list based controls. * * @alias sap.ui.model.analytics.AnalyticalTreeBindingAdapter * @class * @deprecated As of version 1.138.0, will be replaced by OData V4 data aggregation, see * {@link topic:7d914317c0b64c23824bf932cc8a4ae1 Extension for Data Aggregation} * @protected */ var AnalyticalTreeBindingAdapter = function() { // ensure only TreeBindings are enhanced which have not been enhanced yet if (!(this instanceof TreeBinding) || this._bIsAdapted) { return; } ODataTreeBindingAdapter.apply(this); // apply the methods of the adapters prototype to the TreeBinding instance for (var fn in AnalyticalTreeBindingAdapter.prototype) { if (AnalyticalTreeBindingAdapter.prototype.hasOwnProperty(fn)) { this[fn] = AnalyticalTreeBindingAdapter.prototype[fn]; } } //set the default auto expand mode this.setAutoExpandMode(this.mParameters.autoExpandMode || TreeAutoExpandMode.Bundled); }, sClassName = "sap.ui.model.analytics.AnalyticalTreeBindingAdapter"; /* * Returns the Root context of our tree, which is actually the context for the Grand Total * Used by the AnalyticalTable to display the sum row. */ AnalyticalTreeBindingAdapter.prototype.getGrandTotalContext = function () { return this._oRootNode && this._oRootNode.context; }; /* * Returns the Root node of our tree, which is actually the context for the Grand Total * Used by the AnalyticalTable to display the sum row. */ AnalyticalTreeBindingAdapter.prototype.getGrandTotalNode = function () { return this._oRootNode; }; /* * Returns the Root node, which contains the rendering information. */ AnalyticalTreeBindingAdapter.prototype.getGrandTotalContextInfo = function () { return this._oRootNode; }; AnalyticalTreeBindingAdapter.prototype.getLength = function() { if (!this._oRootNode) { return 0; } // The length is the sum of the trees magnitue + all sum rows (from expanded nodes) return this._oRootNode.magnitude + this._oRootNode.numberOfTotals // with a watermark, there is missing data -> add 1 to be able to scroll into that area + (this._oWatermark ? 1 : 0); }; AnalyticalTreeBindingAdapter.prototype.getContextByIndex = function (iIndex) { // If we have fewer root level entries than the table has rows, // the table expectes the last entry in the flat nodes array to be the root node (sum row) if (this._oRootNode && iIndex === (this.getLength() - 1) && this.providesGrandTotal() && this.hasTotaledMeasures()) { return this._oRootNode.context; } var oNode = this.findNode(iIndex); // if the node was not found in the current tree snapshot -> rebuild the tree, and if necessary request the node if (!oNode || !oNode.context) { //Beware: getContexts might return nothing, if the context was not yet loaded! oNode = {context: this.getContexts(iIndex, 1, 0)[0]}; } return oNode ? oNode.context : undefined; }; AnalyticalTreeBindingAdapter.prototype.getNodeByIndex = function(iIndex) { // If we have fewer root level entries than the table has rows, // the table expectes the last entry in the flat nodes array to be the root node (sum row) if (iIndex === (this.getLength() - 1) && this.providesGrandTotal() && this.hasTotaledMeasures()) { return this._oRootNode; } // if the requested index is bigger than the magnitude of the tree, the index can never // be inside the tree. The tree may only shrink due to multi-unit merging if (iIndex >= this.getLength()) { return undefined; } return this.findNode(iIndex); }; /* * Checks if the given node can be selected. * In the AnalyticalTable, only leaf nodes can be selected. */ AnalyticalTreeBindingAdapter.prototype._isNodeSelectable = function (oNode) { if (!oNode) { return false; } return oNode.isLeaf && !oNode.isArtificial; }; /** * Gets an array of either node objects or contexts for the requested part of the tree. * * @param {boolean} bReturnNodes * Whether to return node objects or contexts * @param {number} [iStartIndex=0] * The index of the first requested node or context * @param {number} [iLength] * The maximum number of returned nodes or contexts; if not given the model's size limit is * used; see {@link sap.ui.model.Model#setSizeLimit} * @param {number} [iThreshold=0] * The maximum number of nodes or contexts to read additionally as buffer * @return {object[]|sap.ui.model.Context[]} * The requested tree nodes or contexts * * @private */ AnalyticalTreeBindingAdapter.prototype._getContextsOrNodes = function (bReturnNodes, iStartIndex, iLength, iThreshold) { var i, mMissingSections, oNode, oParentNode, aContexts = [], aNodes = [], that = this; if (!this.isResolved()) { return aNodes; } iStartIndex = iStartIndex || 0; iLength = iLength || this.oModel.iSizeLimit; iThreshold = iThreshold || 0; this._iPageSize = iLength; this._iThreshold = Math.max(this._iThreshold, iThreshold); // clear the overall rowIndex to tree node map this._aRowIndexMap = []; this._buildTree(iStartIndex, iLength); // retrieve the requested/visible section of nodes from the tree if (this._oRootNode) { aNodes = this._retrieveNodeSection(this._oRootNode, iStartIndex, iLength); } // keep a map between Table.RowIndex and tree nodes this._updateRowIndexMap(aNodes, iStartIndex); if (this._oWatermark) { // check whether the user scrolled into the area of the watermark node, which is the // last guaranteed node of the loaded page; remove nodes that are after the last element // of the watermark node (that means their absolute node indices are greater than the // absolute node index of the last known element below the watermark node), because // there are missing nodes in between -> avoids jumping rows for (i = 0; i < aNodes.length; i += 1) { if (aNodes[i].absoluteNodeIndex > this._oWatermark.lastWatermarkNodeIndex) { aNodes.length = i + 1; break; } } if (aNodes.length < iLength) { this._autoExpandPaging(); } } //find missing spots in our visible section for (i = 0; i < aNodes.length; i++) { oNode = aNodes[i]; // we found a gap because the node is empty (context is undefined) if (!oNode.context) { mMissingSections = mMissingSections || {}; // check if we already build a missing section oParentNode = oNode.parent; mMissingSections[oParentNode.groupID] = oParentNode; this._updateNodeSections(oParentNode.groupID, {startIndex: oNode.positionInParent, length: 1}); } aContexts.push(oNode.context); } // trigger load for nodes with missing sections if (mMissingSections) { //if we have a missing section inside a subtree, we need to reload this subtree each(mMissingSections, function (sGroupID, oNode) { // reset the root of the subtree oNode.magnitude = 0; oNode.numberOfTotals = 0; that._loadChildContexts(oNode, {absoluteNodeIndex: oNode.absoluteNodeIndex}); }); // try to fill gaps in our return array if we already have new data (thanks to thresholding) aContexts = []; for (i = 0; i < aNodes.length; i += 1) { oNode = aNodes[i]; aContexts.push(oNode.context); } } if (bReturnNodes) { return aNodes; } else { return aContexts; } }; /* * Trigger paging requests in auto expand mode */ AnalyticalTreeBindingAdapter.prototype._autoExpandPaging = function () { assert(this._oWatermark, "No watermark was set!"); assert(this._isRunningInAutoExpand(TreeAutoExpandMode.Bundled), "Optimised AutoExpand Paging can only be used with TreeAutoExpandMode.Bundled!"); // paging in the autoexpand case var aChildContexts = this.getNodeContexts(this._oWatermark.context, { startIndex: this._oWatermark.startIndex, //this._oWatermark.children.length, length: this._iPageSize, threshold: this._iThreshold, level: this._oWatermark.level, numberOfExpandedLevels: this._oWatermark.autoExpand }); return aChildContexts; }; /* * This hook is called after all children of a node are matched. * Used to insert sum row nodes (if any). * @param {object} oNode the node which is currently matched (and whose children have already been matched) * @see ODataTreeBindingAdapter#_match for further API Documentation, all other parameters are defined there */ AnalyticalTreeBindingAdapter.prototype._afterMatchHook = function (oNode, aResults, iMaxNumberOfMatches, fnMatchFunction, iPositionInParent, oParentNode) { if (oNode.sumNode && oNode !== this._oRootNode) { if (aResults.length === iMaxNumberOfMatches) { return true; } // make sure the additional sum node is also matched (and possibly pushed to the aResults array) var bNodeMatches = fnMatchFunction.call(this, oNode.sumNode, oNode.sumNode.positionInParent, oParentNode); if (bNodeMatches) { aResults.push(oNode.sumNode); } } return undefined; }; /* * This hook is called after all children of "oNode" have been mapped. * Used to also map on sum nodes (if any). * @param {object} oNode the node which will be mapped again */ AnalyticalTreeBindingAdapter.prototype._afterMapHook = function (oNode, fnMapFunction) { if (oNode.sumNode && oNode !== this._oRootNode) { fnMapFunction.call(this, oNode.sumNode); } }; /* * Creates a sum node. * The sum node is later stored inside the tree structure, but is not part of the children array of a node. * A sum node is a copy of the given node, minus the children and with a relative "positionInParent" to it's children. * This means the sum node comes after the children of the given node. * @see AnalyticalTreeBindingAdapter#_loadChildContexts */ AnalyticalTreeBindingAdapter.prototype._createSumNode = function (oNode) { var sumNode; // check for grand totals requested if (this.bProvideGrandTotals && !this.mParameters.sumOnTop && this.hasTotaledMeasures() && oNode.children.length > 1) { sumNode = this._createNode({ parent: oNode.parent, positionInParent: oNode.children.length, //sum row has position after every child in the parent node context: oNode.context, level: oNode.level }); //an empty node state, only used to store rendering infos sumNode.nodeState = this._createNodeState({groupID: sumNode.groupID, sum: true, expanded: false}); } return sumNode; }; /** * Builds the tree starting from the root node. * @param {int} iStartIndex used as the startIndex of the initial page for the root node * @param {int} iLength used as the length of the initial page for the root node */ AnalyticalTreeBindingAdapter.prototype._buildTree = function(iStartIndex, iLength) { //throw away our tree this._oRootNode = undefined; this._oWatermark = undefined; //Try to load the root context(s) var iNumberOfExpandedLevels = this.mParameters && this.getNumberOfExpandedLevels(); var aRootContext = this.getRootContexts({ startIndex: 0, length: this._iPageSize, threshold: this._iThreshold, numberOfExpandedLevels: this._autoExpandMode === TreeAutoExpandMode.Bundled ? iNumberOfExpandedLevels : undefined }); var oRootContext; // sanity check for the binding // if aRootContexts is null -> no $select is given, because no dimensions are part of the aggregation-level if (aRootContext == null) { Log.warning("AnalyticalTreeBindingAdapter: No Dimensions given. An artificial rootContext has be created. Please check your Table/Service definition for dimension columns!"); } else { // if aRootContexts is empty [] -> request sent and everything is ok oRootContext = aRootContext[0]; } //If we have no root contexts we cannot load any children if (!oRootContext) { return; } var oRootNodeState = this._getNodeState("/"); //the root node is always expanded if (!oRootNodeState) { oRootNodeState = this._updateTreeState({groupID: "/", expanded: true, sum: true}); // in this case initially the root does not have any section (of its children) this._updateNodeSections("/", { startIndex: 0, length: iLength }); } //create the root node (contains the grand total sum) this._oRootNode = this._createNode({ context: oRootContext, parent: null, level: 0, nodeState: oRootNodeState, isLeaf: false, autoExpand: iNumberOfExpandedLevels, absoluteNodeIndex: -1 //-1 because the root should not be counted as the first node in the tree (first node is 0) }); this._oRootNode.isArtificial = true; //The root context is always automatically expanded this._loadChildContexts(this._oRootNode, {absoluteNodeIndex: -1}); }; /* * (Re-)Loads the child contexts for the given node "oNode". * This happens recursively for all expanded/autoExpanded child nodes -> the whole subtree starting at "oNode" is created. * All subsequently loaded child nodes will be added to the children collection of oNode. * Reuses the tree node objects (if any). * Since object references are used, all changes to subtree are directly reflected inside the main tree structure. */ AnalyticalTreeBindingAdapter.prototype._loadChildContexts = function (oNode, oRecursionDetails) { var oNodeState = oNode.nodeState; const bBundledMode = this._isRunningInAutoExpand(TreeAutoExpandMode.Bundled); // make sure the children array gets at least the requested length var iMaxGroupSize = this.getGroupSize(oNode.context, oNode.level); if (iMaxGroupSize >= 0) { if (!oNode.children[iMaxGroupSize - 1]) { oNode.children[iMaxGroupSize - 1] = undefined; } if (oNode.level === this.aAggregationLevel.length) { // One level above leafs oNodeState.leafCount = iMaxGroupSize; } oNode.sumNode = this._createSumNode(oNode); } //iterate all loaded (known) sections for (var i = 0; i < oNodeState.sections.length; i++) { var oCurrentSection = oNodeState.sections[i]; // check if section is still relevant // a section may vanish if the startIndex is now outside the known groupsize if (oCurrentSection.startIndex > oNode.children.length) { continue; } var iRequestedLength; if (iMaxGroupSize === -1) { iRequestedLength = oCurrentSection.length; } else { //the maximum entries we can request is the groupSize iRequestedLength = Math.min(oCurrentSection.length, iMaxGroupSize - oCurrentSection.startIndex); } //if we are in the autoexpand mode "bundled", suppress additional requests during the tree traversal //paging is handled differently var bSupressRequest = false; if (oNode.autoExpand >= 0 && bBundledMode) { bSupressRequest = true; iRequestedLength = Math.max(0, iMaxGroupSize); } //try to load the contexts for this sections (may be []) var aChildContexts = this.getNodeContexts(oNode.context, { startIndex: oCurrentSection.startIndex, length: iRequestedLength, //if we only retrieve he known data from the AnalyticalBinding we need no Threshold threshold: bSupressRequest ? 0 : this._iThreshold, level: oNode.level, supressRequest: bSupressRequest }); //for each child context we create a new node for (var j = 0; j < aChildContexts.length; j++) { var oChildContext = aChildContexts[j]; // calculate the index of the child node in the children array // the offset in the children array is the section start index var iChildIndex = j + oCurrentSection.startIndex; var oChildNode = oNode.children[iChildIndex]; //the updated node data after this tree building cycle var oUpdatedNodeData = { context: aChildContexts[j], parent: oNode, level: oNode.level + 1, positionInParent: iChildIndex, autoExpand: Math.max(oNode.autoExpand - 1, -1), absoluteNodeIndex: (++oRecursionDetails.absoluteNodeIndex) }; // if we already have a child node reuse it, otherwise create a new one // Using an object reference allows us to automatically update our "snapshot" of the tree, we retrieve in getContexts if (oChildNode) { oChildNode.context = oUpdatedNodeData.context; oChildNode.parent = oUpdatedNodeData.parent; oChildNode.level = oUpdatedNodeData.level; oChildNode.positionInParent = oUpdatedNodeData.positionInParent; oChildNode.magnitude = 0; oChildNode.numberOfTotals = 0; oChildNode.totalNumberOfLeafs = 0; oChildNode.autoExpand = oUpdatedNodeData.autoExpand; oChildNode.absoluteNodeIndex = oUpdatedNodeData.absoluteNodeIndex; //calculate the group id for the given context //if we reach this point, the binding returned a context from which we can calculate the group id var sGroupIDForChild; if (oChildContext) { sGroupIDForChild = this._calculateGroupID(oChildNode); } oChildNode.groupID = sGroupIDForChild; } else { //create a node one level deeper (missing a group ID and a context) oChildNode = this._createNode(oUpdatedNodeData); } //retrieve the node state OR create one if necessary oChildNode.nodeState = this._getNodeState(oChildNode.groupID); if (!oChildNode.nodeState) { oChildNode.nodeState = this._createNodeState({ groupID: oChildNode.groupID, expanded: false // a new node state is never expanded (EXCEPT during auto expand!) }); } oChildNode.nodeState.parentGroupID = oNode.groupID; oChildNode.isLeaf = !this.nodeHasChildren(oChildNode); oNode.children[iChildIndex] = oChildNode; if (oChildNode.isLeaf) { oNode.numberOfLeafs += 1; } //if the parent node is in selectAllMode, select this child node if (oChildNode.parent.nodeState.selectAllMode && !this._mTreeState.deselected[oChildNode.groupID] && oChildNode.isLeaf) { this.setNodeSelection(oChildNode.nodeState, true); } // if the child node was previously expanded, it has to be expanded again after we rebuilt our tree // --> recursion // but only if we have at least 1 group (otherwise we have a flat list and not a tree) if ((oChildNode.autoExpand >= 0 || oChildNode.nodeState.expanded) && this.isGrouped()) { if (!this._mTreeState.collapsed[oChildNode.groupID]) { // propagate the selectAllMode to the childNode, but only if the parent node is flagged and we are still autoexpanding if (oChildNode.autoExpand >= 0 && oChildNode.parent.nodeState.selectAllMode && !this._mTreeState.deselected[oChildNode.groupID]) { if (oChildNode.nodeState.selectAllMode === undefined) { oChildNode.nodeState.selectAllMode = true; } } this._updateTreeState({groupID: oChildNode.nodeState.groupID, fallbackNodeState: oChildNode.nodeState , expanded: true}); this._loadChildContexts(oChildNode, oRecursionDetails); } // sum up the magnitude/sumRows when moving up in the recursiom oNode.magnitude += oChildNode.magnitude; oNode.numberOfTotals += oChildNode.numberOfTotals; oNode.numberOfLeafs += oChildNode.numberOfLeafs; } if (oChildNode && oChildNode.isLeaf) { oNode.totalNumberOfLeafs = iMaxGroupSize; } else { oNode.totalNumberOfLeafs += oChildNode.totalNumberOfLeafs; } } } // add up the sum of all sub-tree magnitudes // if we run in autoexpand mode, we need to take the full length of the children array iMaxGroupSize = bBundledMode ? oNode.children.length : iMaxGroupSize; oNode.magnitude += Math.max(iMaxGroupSize || 0, 0); if (!iMaxGroupSize && !bBundledMode) { Log.warning("AnalyticalTreeBindingAdapter: iMaxGroupSize(" + iMaxGroupSize + ") is undefined for node '" + oNode.groupID + "'!"); } // add up the total number of sum rows (expanded nodes with at least one child) // the number of totals for the root node is always 1 except in case the grand totals were not requested if (oNode.sumNode || (oNode === this._oRootNode && this.providesGrandTotal() && this.hasTotaledMeasures())) { oNode.numberOfTotals += 1; } // Determine a Watermark node for auto expand paging: // Find the first node for which the group length is not final. // ONLY consider nodes with an autoExpand level to become a watermark node (autoExpand != -1) if (bBundledMode && oNode.autoExpand != -1) { if (!this._oWatermark && !oNode.isLeaf && !this.mFinalLength[oNode.groupID]) { const iLastWatermarkNodeIndex = this._getIndexBeforeFirstMissingNode(oNode); let iStartIndex = oNode.children.length; let sNodeInfo = "WATERMARK: at '" + oNode.groupID + "' (" + oNode.absoluteNodeIndex + ") and start index " + iStartIndex + " with last complete node index " + iLastWatermarkNodeIndex; if (iStartIndex === 0) { // if there are no children loaded yet, move the watermark to the parent node to avoid // many requests, for this node and its following siblings, fetching the node's content with // single requests with $top > 10000 iStartIndex = oNode.positionInParent; oNode = oNode.parent; sNodeInfo += " --> moved to '" + oNode.groupID + "' (" + oNode.absoluteNodeIndex + ") and start index " + iStartIndex; } this._oWatermark = { groupID: oNode.groupID, context: oNode.context, absoluteNodeIndex: oNode.absoluteNodeIndex, startIndex: iStartIndex, level: oNode.level, autoExpand: oNode.autoExpand, lastWatermarkNodeIndex: iLastWatermarkNodeIndex }; Log.debug(sNodeInfo, undefined, sClassName); } } }; /** * Gets the absolute node index of the node in the subtree of the given node that is just before the first missing * node. * * @param {Object} oNode The current node * @returns {int} The absolute node index of the node just before the first missing node * @private */ AnalyticalTreeBindingAdapter.prototype._getIndexBeforeFirstMissingNode = function (oNode) { let iIndex = oNode.absoluteNodeIndex; oNode.children.some((oChildNode) => { iIndex = oChildNode.isLeaf ? oChildNode.absoluteNodeIndex : this._getIndexBeforeFirstMissingNode(oChildNode); return !oChildNode.isLeaf && !this.mFinalLength[oChildNode.groupID]; }); return iIndex; }; /* * Retrieves the group ID for the given node. * The implementation differs from the ODataTreeBindingAdapter, since the AnalyticalBinding already exposes * certain convenience functions. */ AnalyticalTreeBindingAdapter.prototype._calculateGroupID = function (oNode) { var sGroupID; var iMaxLevel = this.aAggregationLevel.length; // in case we have no grouping at all, the "groupID" for each node is based on its position as the roots child if (!this.isGrouped() && oNode && oNode.positionInParent) { sGroupID = "/" + oNode.positionInParent + "/"; } else if (oNode.level > iMaxLevel) { // if the level of the node exceeds the maximum level (in the analytical case, this is the aggregation level), // the group id is also appended with the relative parent position sGroupID = this._getGroupIdFromContext(oNode.context, iMaxLevel); assert(oNode.positionInParent != undefined, "If the node level is greater than the number of grouped columns, the position of the node to its parent must be defined!"); sGroupID += oNode.positionInParent + "/"; } else { //this is the best case, the node sits on a higher level than the aggregation level sGroupID = this._getGroupIdFromContext(oNode.context, oNode.level); } return sGroupID; }; /* * Collapses the given node, either a node instance or an index. * Overwritten from the ODataTreeBindingAdapter, because of additional analytical logic. */ AnalyticalTreeBindingAdapter.prototype.collapse = function(vParam) { var oNodeStateForCollapsingNode, oNode; //check if the Parameter is a node state object if (typeof vParam === "object") { oNodeStateForCollapsingNode = vParam; } else if (typeof vParam === "number") { oNode = this.findNode(vParam); assert(oNode && oNode.nodeState, "AnalyticalTreeBindingAdapter.collapse(" + vParam + "): No node found!"); //jump out if no node was found if (!oNode) { return; } oNodeStateForCollapsingNode = oNode.nodeState; } //mark the node as collapsed this._updateTreeState({groupID: oNodeStateForCollapsingNode.groupID, expanded: false}); // remove the select-all state oNodeStateForCollapsingNode.selectAllMode = false; var bAutoExpandRequestTriggered = false; if (this.bCollapseRecursive || this._isRunningInAutoExpand(TreeAutoExpandMode.Bundled)) { var sGroupIDforCollapsingNode = oNodeStateForCollapsingNode.groupID; // in case we collapsed a parent(chain) of the watermark node, we have to page, because we can not know how many // nodes will move up after collapsing the subtree. This is important because the watermark node will never be visible if (this._isRunningInAutoExpand(TreeAutoExpandMode.Bundled) && this._oWatermark && (typeof sGroupIDforCollapsingNode == "string" && sGroupIDforCollapsingNode.length > 0 && this._oWatermark.groupID.startsWith(sGroupIDforCollapsingNode))) { // move the watermark node to the parent of the collapsed node, because the auto-expand paging starting point needs to be moved if (oNode && oNode.parent) { const oSiblingNode = oNode.parent.children[oNode.positionInParent + 1]; // may not yet be available this._oWatermark = { groupID: oNode.parent.groupID, context: oNode.parent.context, absoluteNodeIndex: oNode.parent.absoluteNodeIndex, startIndex: oNode.positionInParent + 1, //+1 is the next (right) sibling of the collapsed node level: oNode.parent.level, autoExpand: oNode.parent.autoExpand, lastWatermarkNodeIndex: this._getIndexBeforeFirstMissingNode(oSiblingNode ?? oNode) }; } this._autoExpandPaging(); bAutoExpandRequestTriggered = true; } // Collapse all subsequent child nodes, this is determined by a common groupID prefix, e.g.: "/A100-50/" is the parent of "/A100-50/Finance/" // All expanded nodes which start with 'sGroupIDforCollapsingNode', are basically children of it and also need to be collapsed var that = this; each(this._mTreeState.expanded, function (sGroupID, oNodeState) { if (typeof sGroupIDforCollapsingNode == "string" && sGroupIDforCollapsingNode.length > 0 && sGroupID.startsWith(sGroupIDforCollapsingNode)) { that._updateTreeState({groupID: sGroupID, expanded: false}); } }); var aDeselectedNodeIds = []; // also remove selections from child nodes of the collapsed node each(this._mTreeState.selected, function (sGroupID, oNodeState) { if (typeof sGroupIDforCollapsingNode == "string" && sGroupIDforCollapsingNode.length > 0 && sGroupID.startsWith(sGroupIDforCollapsingNode)) { oNodeState.selectAllMode = false; that.setNodeSelection(oNodeState, false); aDeselectedNodeIds.push(sGroupID); } }); if (aDeselectedNodeIds.length) { var selectionChangeParams = { rowIndices: [] }; // Collect the changed indices var iNodeCounter = 0; this._map(this._oRootNode, function (oNode) { if (!oNode || !oNode.isArtificial) { iNodeCounter++; } if (oNode && aDeselectedNodeIds.indexOf(oNode.groupID) !== -1) { if (oNode.groupID === this._sLeadSelectionGroupID) { // Lead selection got deselected selectionChangeParams.oldIndex = iNodeCounter; selectionChangeParams.leadIndex = -1; } selectionChangeParams.rowIndices.push(iNodeCounter); } }); this._publishSelectionChanges(selectionChangeParams); } } // only fire change if no autoexpand request is triggered: // this prevents the table from rerendering because, the autoExpand logic // uses the same collapse/expand functions as the user interactions if (!bAutoExpandRequestTriggered) { this._fireChange({reason: ChangeReason.Collapse}); } }; AnalyticalTreeBindingAdapter.prototype.collapseToLevel = function(iLevel) { // reconfigure the auto expand level this.setNumberOfExpandedLevels(iLevel, true); TreeBindingAdapter.prototype.collapseToLevel.call(this, iLevel); }; /** * Returns if a node hase children or not. * @param {object} oNode tree node * @return {boolean} true if the node has children, false otherwise */ AnalyticalTreeBindingAdapter.prototype.nodeHasChildren = function(oNode) { assert(oNode, "AnalyticalTreeBindingAdapter.nodeHasChildren: No node given!"); //check if the node has children if (!oNode || !oNode.parent || oNode.nodeState.sum) { return false; } else if (oNode.isArtificial) { return true; } else { return AnalyticalBinding.prototype.hasChildren.call(this, oNode.context, { level: oNode.level }); } }; /* * Resets the data. * If mParameters.reason is set to ChangeReason.Sort: * Expand/Collapse states and the selection is kept in case the reset was triggered from within a sort() call. * @param {object} oContext the context which will be reset (if undefined, the whole binding is reset) * @param {object} mParameters.reason the reason for the reset */ AnalyticalTreeBindingAdapter.prototype.resetData = function(oContext, mParameters) { var vReturn = AnalyticalBinding.prototype.resetData.call(this, oContext, mParameters); // clear the mapping table this._aRowIndexMap = []; // and the root node this._oRootNode = undefined; this._oWatermark = undefined; // clear page size this._iPageSize = 0; this._iThreshold = 0; if (!mParameters || mParameters.reason !== ChangeReason.Sort) { //remove the selection/reset lead selection index this.clearSelection(); // clear the tree state this._createTreeState(true); } return vReturn; }; /** * Checks if the AnalyticalBinding has totaled measures available. * Used for rendering sum rows. * * @public * @returns {boolean} Whether the binding has totaled measures or not */ AnalyticalTreeBindingAdapter.prototype.hasTotaledMeasures = function() { var bHasMeasures = false; each(this.getMeasureDetails() || [], function(iIndex, oMeasure) { if (oMeasure.analyticalInfo.total) { bHasMeasures = true; return false; } return true; }); return bHasMeasures; }; /* * Returns if the Binding is grouped. This depends on the aggregation level. * If the aggregation level is 0, the binding essentially holds a list. */ AnalyticalTreeBindingAdapter.prototype.isGrouped = function () { return (this.aAggregationLevel.length > 0); }; /* * Returns if the binding is in auto expand mode. Used to branch paging behavior. */ AnalyticalTreeBindingAdapter.prototype._isRunningInAutoExpand = function (sAutoExpandMode) { if (this.getNumberOfExpandedLevels() > 0 && this._autoExpandMode === sAutoExpandMode) { return true; } else { return false; } }; /** * Sets the number of expanded levels on the TreeBinding (commonly an AnalyticalBinding). * This is NOT the same as AnalyticalTreeBindingAdapter#collapse or AnalyticalTreeBindingAdapter#expand. * Setting the number of expanded levels leads to different requests. * This function is used by the AnalyticalTable for the ungroup/ungroup-all feature. * @param {int} iLevels the number of levels which should be expanded, minimum is 0 * @protected * @name sap.ui.model.analytics.AnalyticalTreeBindingAdapter#setNumberOfExpandedLevels * @function */ // @see sap.ui.table.AnalyticalTable#_getGroupHeaderMenu AnalyticalTreeBindingAdapter.prototype.setNumberOfExpandedLevels = function(iLevels, bSupressResetData) { var iNumberOfAggregationLevels; iLevels = iLevels || 0; if (iLevels < 0) { Log.warning("Number of expanded levels was set to 0. Negative values are prohibited", this, sClassName); iLevels = 0; } iNumberOfAggregationLevels = this.aAggregationLevel.length; if (iLevels > iNumberOfAggregationLevels) { Log.warning("Number of expanded levels was reduced from " + iLevels + " to " + iNumberOfAggregationLevels + " which is the number of grouped dimensions", this, sClassName); iLevels = iNumberOfAggregationLevels; } if (!bSupressResetData) { this.resetData(); } // set the numberOfExpandedLevels on the binding directly // this.mParameters is inherited from the Binding super class this.mParameters.numberOfExpandedLevels = iLevels; }; /** * Retrieves the currently set number of expanded levels from the Binding (commonly an AnalyticalBinding). * @protected * @name sap.ui.model.analytics.AnalyticalTreeBindingAdapter#getNumberOfExpandedLevels * @function * @returns {int} the number of expanded levels */ AnalyticalTreeBindingAdapter.prototype.getNumberOfExpandedLevels = function() { return this.mParameters.numberOfExpandedLevels; }; /* * Overrides the default from the TBA. * Returns the maximum number of currently selectable nodes, in this case the total number of leaves. * @returns {int} Maximum number of currently selectable node */ AnalyticalTreeBindingAdapter.prototype._getSelectableNodesCount = function (oNode) { if (oNode) { return oNode.totalNumberOfLeafs; } else { return 0; } }; return AnalyticalTreeBindingAdapter; }, /* bExport= */ true);