src/sap.ui.table/src/sap/ui/table/TableGrouping.js
function(oTable, vRowIndex, bExpand) {
			var aIndices = [];
			var oBinding = oTable ? oTable.getBinding("rows") : null;

			if (!oTable || !oBinding || !oBinding.expand || vRowIndex == null) {
				return null;
			}

			if (typeof vRowIndex === "number") {
				aIndices = [vRowIndex];
			} else if (Array.isArray(vRowIndex)) {
				if (bExpand == null && vRowIndex.length > 1) {
					// Toggling the expanded state of multiple rows seems to be an absurd task. Therefore we assume this is unintentional and
					// prevent the execution.
					return null;
				}
				aIndices = vRowIndex;
			}

			// The cached binding length cannot be used here. In the synchronous execution after re-binding the rows, the cached binding length is
			// invalid. The table will validate it in its next update cycle, which happens asynchronously.
			// As of now, this is the required behavior for some features, but leads to failure here. Therefore, the length is requested from the
			// binding directly.
			var iTotalRowCount = oTable._getTotalRowCount(true);

			var aValidSortedIndices = aIndices.filter(function(iIndex) {
				// Only indices of existing, expandable/collapsible nodes must be considered. Otherwise there might be no change event on the final
				// expand/collapse.
				var bIsExpanded = oBinding.isExpanded(iIndex);
				var bIsLeaf = true; // If the node state cannot be determined, we assume it is a leaf.

				if (oBinding.nodeHasChildren) {
					if (oBinding.getNodeByIndex) {
						bIsLeaf = !oBinding.nodeHasChildren(oBinding.getNodeByIndex(iIndex));
					} else {
						// The sap.ui.model.TreeBindingCompatibilityAdapter has no #getNodeByIndex function and #nodeHasChildren always returns true.
						bIsLeaf = false;
					}
				}

				return iIndex >= 0 && iIndex < iTotalRowCount
					   && !bIsLeaf
					   && bExpand !== bIsExpanded;
			}).sort();

			if (aValidSortedIndices.length === 0) {
				return null;
			}

			// Operations need to be performed from the highest index to the lowest. This ensures correct results with OData bindings. The indices
			// are sorted ascending, so the array is iterated backwards.

			// Expand/Collapse all nodes except the first, and suppress the change event.
			for (var i = aValidSortedIndices.length - 1; i > 0; i--) {
				if (bExpand) {
					oBinding.expand(aValidSortedIndices[i], true);
				} else {
					oBinding.collapse(aValidSortedIndices[i], true);
				}
			}

			// Expand/Collapse the first node without suppressing the change event.
			if (bExpand === true) {
				oBinding.expand(aValidSortedIndices[0], false);
			} else if (bExpand === false) {
				oBinding.collapse(aValidSortedIndices[0], false);
			} else {
				oBinding.toggleIndex(aValidSortedIndices[0]);
			}

			return oBinding.isExpanded(aValidSortedIndices[0]);
		}
function(oTable, vRowIndex, bExpand) {
			var aIndices = [];
			var oBinding = oTable ? oTable.getBinding("rows") : null;

			if (!oTable || !oBinding || !oBinding.expand || vRowIndex == null) {
				return null;
			}

			if (typeof vRowIndex === "number") {
				aIndices = [vRowIndex];
			} else if (Array.isArray(vRowIndex)) {
				if (bExpand == null && vRowIndex.length > 1) {
					// Toggling the expanded state of multiple rows seems to be an absurd task. Therefore we assume this is unintentional and
					// prevent the execution.
					return null;
				}
				aIndices = vRowIndex;
			}

			// The cached binding length cannot be used here. In the synchronous execution after re-binding the rows, the cached binding length is
			// invalid. The table will validate it in its next update cycle, which happens asynchronously.
			// As of now, this is the required behavior for some features, but leads to failure here. Therefore, the length is requested from the
			// binding directly.
			var iTotalRowCount = oTable._getTotalRowCount(true);

			var aValidSortedIndices = aIndices.filter(function(iIndex) {
				// Only indices of existing, expandable/collapsible nodes must be considered. Otherwise there might be no change event on the final
				// expand/collapse.
				var bIsExpanded = oBinding.isExpanded(iIndex);
				var bIsLeaf = true; // If the node state cannot be determined, we assume it is a leaf.

				if (oBinding.nodeHasChildren) {
					if (oBinding.getNodeByIndex) {
						bIsLeaf = !oBinding.nodeHasChildren(oBinding.getNodeByIndex(iIndex));
					} else {
						// The sap.ui.model.TreeBindingCompatibilityAdapter has no #getNodeByIndex function and #nodeHasChildren always returns true.
						bIsLeaf = false;
					}
				}

				return iIndex >= 0 && iIndex < iTotalRowCount
					   && !bIsLeaf
					   && bExpand !== bIsExpanded;
			}).sort();

			if (aValidSortedIndices.length === 0) {
				return null;
			}

			// Operations need to be performed from the highest index to the lowest. This ensures correct results with OData bindings. The indices
			// are sorted ascending, so the array is iterated backwards.

			// Expand/Collapse all nodes except the first, and suppress the change event.
			for (var i = aValidSortedIndices.length - 1; i > 0; i--) {
				if (bExpand) {
					oBinding.expand(aValidSortedIndices[i], true);
				} else {
					oBinding.collapse(aValidSortedIndices[i], true);
				}
			}

			// Expand/Collapse the first node without suppressing the change event.
			if (bExpand === true) {
				oBinding.expand(aValidSortedIndices[0], false);
			} else if (bExpand === false) {
				oBinding.collapse(aValidSortedIndices[0], false);
			} else {
				oBinding.toggleIndex(aValidSortedIndices[0]);
			}

			return oBinding.isExpanded(aValidSortedIndices[0]);
		}
function ( oTable , vRowIndex , bExpand ) { var aIndices = [ ] ; var oBinding = oTable ? oTable . getBinding ( "rows" ) : null ; if ( ! oTable || ! oBinding || ! oBinding . expand || vRowIndex == null ) { return null ; } if ( typeof vRowIndex === "number" ) { aIndices = [ vRowIndex ] ; } else if ( Array . isArray ( vRowIndex ) ) { if ( bExpand == null && vRowIndex . length > 1 ) { // Toggling the expanded state of multiple rows seems to be an absurd task. Therefore we assume this is unintentional and // prevent the execution. return null ; } aIndices = vRowIndex ; } // The cached binding length cannot be used here. In the synchronous execution after re-binding the rows, the cached binding length is // invalid. The table will validate it in its next update cycle, which happens asynchronously. // As of now, this is the required behavior for some features, but leads to failure here. Therefore, the length is requested from the // binding directly. var iTotalRowCount = oTable . _getTotalRowCount ( true ) ; var aValidSortedIndices = aIndices . filter ( function ( iIndex ) { // Only indices of existing, expandable/collapsible nodes must be considered. Otherwise there might be no change event on the final // expand/collapse. var bIsExpanded = oBinding . isExpanded ( iIndex ) ; var bIsLeaf = true ; // If the node state cannot be determined, we assume it is a leaf. if ( oBinding . nodeHasChildren ) { if ( oBinding . getNodeByIndex ) { bIsLeaf = ! oBinding . nodeHasChildren ( oBinding . getNodeByIndex ( iIndex ) ) ; } else { // The sap.ui.model.TreeBindingCompatibilityAdapter has no #getNodeByIndex function and #nodeHasChildren always returns true. bIsLeaf = false ; } } return iIndex >= 0 && iIndex < iTotalRowCount && ! bIsLeaf && bExpand !== bIsExpanded ; } ) . sort ( ) ; if ( aValidSortedIndices . length === 0 ) { return null ; } // Operations need to be performed from the highest index to the lowest. This ensures correct results with OData bindings. The indices // are sorted ascending, so the array is iterated backwards. // Expand/Collapse all nodes except the first, and suppress the change event. for ( var i = aValidSortedIndices . length - 1 ; i > 0 ; i -- ) { if ( bExpand ) { oBinding . expand ( aValidSortedIndices [ i ] , true ) ; } else { oBinding . collapse ( aValidSortedIndices [ i ] , true ) ; } } // Expand/Collapse the first node without suppressing the change event. if ( bExpand === true ) { oBinding . expand ( aValidSortedIndices [ 0 ] , false ) ; } else if ( bExpand === false ) { oBinding . collapse ( aValidSortedIndices [ 0 ] , false ) ; } else { oBinding . toggleIndex ( aValidSortedIndices [ 0 ] ) ; } return oBinding . isExpanded ( aValidSortedIndices [ 0 ] ) ; }
Toggles or sets the expanded state of a single or multiple rows. Toggling only works for a single row.

@param {sap.ui.table.Table} oTable Instance of the table.
@param {int | int[]} vRowIndex A single index, or an array of indices of the rows to expand or collapse.
@param {boolean} [bExpand] If defined, instead of toggling the desired state is set.
@returns {boolean | null} The new expanded state in case an action was performed, otherwise <code>null</code>.
Toggles or sets the expanded state of a single or multiple rows . Toggling only works for a single row .