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:
DistanceFromRoot
DrillState
LimitedDescendantCount
LimitedRank
Actions 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).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.