The $$aggregation binding parameter at sap.ui.model.odata.v4.ODataModel#bindList holds the information needed for data aggregation. It may be
changed by sap.ui.model.odata.v4.ODataListBinding#setAggregation. It cannot be combined
with an explicit system query option $apply, because it implicitly derives $apply. For more information,
see the OData Extension for Data Aggregation V4.0 specification.
Since 1.117.0, either a read-only recursive hierarchy (see below) or (pure) data aggregation is supported, but no mix;
hierarchyQualifier is the leading property that decides between these two use cases. Since 1.125.0, maintenance of a
recursive hierarchy is supported.
Data aggregation or a recursive hierarchy cannot be combined with grouping via a list binding's first sorter. For more information, see the vGroup
parameter of sap.ui.model.Sorter.
Data aggregation or a recursive hierarchy do not support the creation, deletion, or refreshing of data. Additional property requests for an entity that already has been requested (see Data Reuse) as well as updating of data including invocation of bound actions and side effects are only supported for a recursive hierarchy.
For every aggregatable property, you can provide the name of the custom aggregate for a corresponding currency or unit of measure. That custom
aggregate must return the single value of a unit in case there is only one, or null otherwise ("multi-unit
situation"). In the special case that the single value is null, an empty string "" has to be
returned.
Normally, there is also a structural property of the same name as the custom aggregate, providing type information, etc. In case of a multi-unit
situation,
v4.Context#getFilter may be helpful to send a request for more details.
The following client-side instance annotations can be used to access a node level or expansion state. For property bindings, a syntax
like {= %{@$ui5.node.level} } is usually helpful, because automatic type determination is not available.
@$ui5.node.level – A non-negative integer which describes the node level; "0" is the single root node
which corresponds to the grand total row, "1" are the top-level group nodes, etc.
@$ui5.node.isExpanded – A boolean which determines whether this node is currently expanded.
true means yes, false means no, undefined means that (the state is
undefined because) this node is a leaf. As an implementation detail, the annotation might simply be missing for leaves.@$ui5.node.groupLevelCount – An integer value which determines the count of the direct children of a group
node. As an implementation detail, the annotation is only available if the corresponding node is expanded.
Two scenarios are supported:
You can provide properties for grouping and aggregation. An appropriate system query option $apply is derived
from those. The list binding then still provides a flat list of contexts ("rows"), but with additional aggregated properties
("columns"). In addition, you can request grand total values for aggregatable properties. In this case, an extra row appears
at the beginning of the flat list of contexts that contains the grand total values, as well as empty values for all other
properties.
Example XML View With Grand Total
<table:Table fixedRowCount="1"
rows="{
path : '/BusinessPartners',
parameters : {
$$aggregation : {
aggregate : {
SalesAmount : {
grandTotal : true,
unit : 'Currency'
}
},
group : {
Country : {additionally : ['Texts/Country']}
}
},
$filter : 'SalesAmount gt 1000000',
$orderby : 'SalesAmount desc'
}
}">
<table:Column template="Texts/Country">
<Label text="Country"/>
</table:Column>
<table:Column hAlign="End" template="SalesAmount">
<Label text="Sales Amount"/>
</table:Column>
<table:Column template="Currency">
<Label text="Currency"/>
</table:Column>
</table:Table>You can provide group levels to determine a hierarchy of expandable group levels in addition to the leaf nodes determined by the groupable and
aggregatable properties. To achieve this, specify the names of the group levels in the groupLevels property
of $$aggregation. If no other groupable properties are given except those named as levels, the last group
level determines the leaf nodes and is not expandable.
Group levels can be combined with the system query option $count : true; for more information, see Binding Collection Inline Count. Group levels can only be combined with filtering before the aggregation
(see below). Note how an $orderby option can address groups across all levels. For every aggregatable
property, you can request subtotals and a grand total individually.
Example XML View With Hierarchy
<table:Table fixedRowCount="1"
rows="{
path : '/BusinessPartners',
parameters : {
$$aggregation : {
aggregate : {
SalesAmount : {
grandTotal : true,
subtotals : true,
unit : 'Currency'
}
},
group : {
Country : {additionally : ['CountryText']},
Region : {additionally : ['RegionText']}
},
groupLevels : ['Country','Region','Segment']
},
$count : false,
$orderby : 'Country,Region desc,Segment',
filters : {path : \'Region\', operator : \'GE\', value1 : \'Mid\'}
}
}">
<table:Column template="CountryText">
<Label text="Country"/>
</table:Column>
<table:Column template="RegionText">
<Label text="Region"/>
</table:Column>
<table:Column template="Segment">
<Label text="Segment"/>
</table:Column>
<table:Column hAlign="End" template="SalesAmount">
<Label text="Sales Amount"/>
</table:Column>
<table:Column template="Currency">
<Label text="Currency"/>
</table:Column>
</table:Table>For aggregatable properties where grand total or subtotal values are requested, you can globally choose where these should be displayed:
Use the grandTotalAtBottomOnly or subtotalsAtBottomOnly property with values true or
false, respectively, or simply omit it. For more information, see the API Reference in the Demo Kit.
Filters are provided to the list binding as described in Filtering. The Filter
objects are analyzed automatically to perform the filtering before the aggregation where possible using the filter()
transformation. The remaining filters, including the provided $filter parameter of the binding, are applied after the
aggregation either via the system query option $filter or within the system query option $apply,
using again the filter() transformation.
Note that Filter objects are not supported for aggregatable properties with an alias. For more information, see the name property
of the aggregate map of the oAggregation parameter of v4.ODataListBinding#setAggregation.
You can provide a search string to be applied before data aggregation via the oAggregation.search parameter of ODataListBinding#setAggregation. It works like the "5.1.7 System Query Option $search", but is applied before data aggregation, not after it. Note that
certain content will break the syntax of the $apply system query option when embedded into a
search() transformation and thus result in an invalid request. If the OData service supports the ODATA-1452 proposal, then the command
system query option when embedded into a ODataUtils.formatLiteral(sSearch, "Edm.String"); should be used to
encapsulate the whole search string beforehand (see sap.ui.model.odata.v4.ODataUtils.formatLiteral). Otherwise, it might be wise to restrict your search input
accordingly.
For each groupable property, you can define an optional list of strings that provides the paths to properties (like texts or
attributes) related to this groupable property in a 1:1 relation. They are requested additionally via groupby and
must not change the actual grouping; a unit for an aggregatable property must not be repeated there.
You can display hierarchical data (a "tree") inside a table using a list binding. Read-only hierarchies are supported since 1.117.0 while maintenance is supported since 1.125.0. Such a recursive hierarchy is described by a pair of "Org.OData.Aggregation.V1.RecursiveHierarchy" and "com.sap.vocabularies.Hierarchy.v1.RecursiveHierarchy" annotations at the list binding's entity type. You need to use the same qualifier for both of these annotations, which is known as the hierarchy qualifier.
If the hierarchyQualifier property of $$aggregation is present, a recursive hierarchy without data aggregation
is defined. The only other supported properties are expandTo, which optionally specifies the number of initially
expanded levels as a positive integer, and search (see Search Before Data Aggregation above). Sorting and filtering can
be done as usual (both as system query options and as SAPUI5 objects),
but $search is not supported (use search instead, see above).
Note how this influences v4.ODataListBinding#getCount. You can use the v4.ODataListBinding#getAggregation method with the new bVerbose parameter to
access some details from the above-mentioned annotations. The v4.ODataListBinding#setAggregation method can be used to change $$aggregation.
The following properties are required from a "com.sap.vocabularies.Hierarchy.v1.RecursiveHierarchy" annotation:
DistanceFromRootDrillStateLimitedDescendantCountLimitedRankActions and functions can be invoked as usual. Side effects are supported both for single rows and the entire list ("side-effects refresh"; see v4.Context#requestSideEffects for details), even if they affect the hierarchy (node IDs,
parent/child relations, or sibling order) itself. The current tree state with respect to expanded and collapsed nodes (see v4.Context#isExpanded) is kept even in case of such a side-effects refresh.
The @$ui5.node.level and @$ui5.node.isExpanded client-side instance annotations can be used as described above
to access a node level or expansion state. A context's index refers to its position in the list binding's "flat" collection. You can use v4.Context#getParent to access a node's parent. If the parent is not yet known, v4.Context#requestParent can be used to request it from the server. The v4.Context#isAncestorOf API also helps to inspect the parent/child relationship (note
that v4.ODataListBinding#getRootBinding is unrelated).
Since 1.125.0, a recursive hierarchy need not be read-only, but maintenance is supported, namely:
v4.ODataListBinding#create and "@$ui5.node.parent" therein. Since
1.130.0, the createInPlace option is supported for the $$aggregation binding parameter and the
v4.ODataListBinding#setAggregation method. When set, newly created nodes are shown "in place", i.e. at
the position specified by the service . Otherwise, created nodes are displayed out of place as the first children of their parent
or as the first roots, but not in their usual position as defined by the service and the current sort order.v4.Context#delete. Note that the deletion is first done on the server and only later
shown on the client. Thus, the group ID must not have submit mode "API".v4.Context#move. Note that nextSibling requires a "com.sap.vocabularies.Hierarchy.v1.RecursiveHierarchyActions" annotation with at least a
ChangeNextSiblingAction. An out-of-place node has no preceding sibling and thus cannot be moved up. Each
out-of-place node with the same parent has the same following sibling, namely that parent's first in-place (that is, not
out-of-place) node. That following sibling is null if the parent has no in-place children. A similar
consideration applies for out-of-place root nodes. This way, you can move out-of-place nodes down so that they become in-place
nodes. Thus, you actively determine their position among their siblings. A first in-place node has no preceding sibling even if
out-of-place nodes are present. Thus, an in-place node cannot be moved up in order to become out-of-place (again).copy parameter of v4.Context#move. Note that the copying of nodes requires a "com.sap.vocabularies.Hierarchy.v1.RecursiveHierarchyActions" annotation with the
CopyAction.Note that only one such change must be pending at any point in time. That is, you must wait for one change to be completed before starting the next change. The only exception is property updates, for which multiple properties can be combined as usual.
The TopLevels function is fundamental for recursive hierarchies. It describes the input set underlying the
hierarchy (see the list binding's path) and specifies which recursive hierarchy is built on top (see
hierarchyQualifier above). It takes care to initially expand a certain number of levels (see
expandTo above) and later to expand or collapse certain nodes in order to keep the tree state during a
side-effects refresh.
A typical request to read the first page of a hierarchical table may look like this:
GET
/sap/opu/odata4/IWBEP/TEA/default/IWBEP/TEA_BUSI/0001/EMPLOYEES?$apply=orderby(AGE)/com.sap.vocabularies.Hierarchy.v1.TopLevels(HierarchyNodes=$root/EMPLOYEES,HierarchyQualifier='OrgChart',NodeProperty='ID',Levels=2)&$select=AGE,DescendantCount,DistanceFromRoot,DrillState,ID,MANAGER_ID,Name&$count=true&$skip=0&$top=115
Note how the sibling(!) order is specified via an orderby transformation (see the list binding's sorters as well as
$orderby). The list binding's path would be "/EMPLOYEES", and $$aggregation
looks as follows. The model's autoExpandSelect parameter does its magic, and
$count&$skip&$top is taken care of automatically by the list binding.
<Table rows="{
path: '/EMPLOYEES',
parameters: {
$count: false,
$orderby: 'AGE',
$$aggregation: {
expandTo: 2,
hierarchyQualifier: 'OrgChart'
},
$$patchWithoutSideEffects: true
},
...
}">If the list binding uses $count: true, for example, to show this count as part of a title, an extra request is sent once (not
each time when scrolling, but of course again after a (side-effects) refresh). It includes any custom query options as well as
filter and search criteria: GET
/sap/opu/odata4/IWBEP/TEA/default/IWBEP/TEA_BUSI/0001/EMPLOYEES/$count?sap-client=123&$filter=AGE ge 0 and
(Is_Manager)&$search=developer
With filter and search, the main request looks a bit more complicated and includes an ancestors transformation beforehand: GET
/sap/opu/odata4/IWBEP/TEA/default/IWBEP/TEA_BUSI/0001/EMPLOYEES?$apply=ancestors($root/EMPLOYEES,OrgChart,ID,filter(AGE
ge 0 and (Is_Manager))/search(developer),keep
start)/orderby(AGE)/com.sap.vocabularies.Hierarchy.v1.TopLevels(HierarchyNodes=$root/EMPLOYEES,HierarchyQualifier='OrgChart',NodeProperty='ID',Levels=2)
&$select=AGE,DescendantCount,DistanceFromRoot,DrillState,ID,MANAGER_ID,Name&$count=true&$skip=0&$top=115
When a node is expanded individually, a request for its children is sent using a descendants transformation, for example: GET
/sap/opu/odata4/IWBEP/TEA/default/IWBEP/TEA_BUSI/0001/EMPLOYEES?$apply=descendants($root/EMPLOYEES,OrgChart,ID,filter(ID
eq
'0'),1)/orderby(AGE)&$select=AGE,DrillState,ID,MANAGER_ID,Name&$count=true&$skip=0&$top=6
When keeping the expand/collapsed state of nodes, the TopLevels function's ExpandLevels parameter is needed,
for example: GET
/sap/opu/odata4/IWBEP/TEA/default/IWBEP/TEA_BUSI/0001/EMPLOYEES?$apply=com.sap.vocabularies.Hierarchy.v1.TopLevels(HierarchyNodes=$root/EMPLOYEES,HierarchyQualifier='OrgChart',NodeProperty='ID',Levels=2,ExpandLevels=[{NodeID
: "8", Levels : 0}, {NodeID : "1", Levels : 1}])&...
When moving a node, it is PATCHed with a payload like "EMPLOYEE_2_MANAGER@odata.bind" : "EMPLOYEES('9')", which points to the
new parent. When creating a new node, a similar payload is used to point to the new parent as part of the POST. For root nodes, a
null value is sent. To determine a node's sibling position, the "com.sap.vocabularies.Hierarchy.v1.RecursiveHierarchyActions" annotation's
ChangeNextSiblingAction is invoked with a payload like NextSibling : {ID : "3" }. A
null value is used to make a node the last sibling.