Generally, it's not a good practice to declare a template in XML without predefining the corresponding list binding in the parent control. Otherwise, it's just adding a plain single item which UI5 renders together with the List initially. That single item gets then cloned and replaced in the 2nd rendering cycle after the list binding from the controller is complete. I guess this is where the grouping has issues; the first item is no longer sap.m.CustomListItem
but sap.m.GroupHeaderListItem
causing side effects when rendering the rest of the items.
Better create the template in the controller entirely:
<List id="idListCart" noDataText="{i18n>CartListNoData}">
<!-- Template control is created in the controller. Nothing to define here. -->
</List>
{ // Items binding info
path: "/CartSet",
templateShareable: false,
template: new CustomListItem({ // required from "sap/m/CustomListItem"
// ...
}),
// ...
}
This does add quite a few more LOC to the controller but avoids unexpected side effects.
We could keep the template definition in XML by wrapping it with <dependents>
but that practice caused other issues in my experience with some outdated SAPUI5 controls. But you might want to give it a try:
<List id="idListCart" noDataText="{i18n>CartListNoData}">
<dependents> <!-- Controls that belong to parent but aren't supposed to be rendered -->
<CustomListItem id="listItemTemplate">
<!-- ... -->
</CustomListItem>
</dependents>
</List><!-- No change in the controller -->
Alternative
If possible, define the list binding directly in XML where Sorter
can be created declaratively, with its grouper and group header factory assigned using the '.<fnName>'
syntax. Dynamic filters would still need to be created and added to the list binding separately in the controller.
<List id="idListCart"
noDataText="{i18n>CartListNoData}"
items="{
path: '/CartSet',
sorter: {
path: 'Startdatum',
descending: true,
group: '.formatDate'
},
groupHeaderFactory: '.createGroupHeader',
suspended: true
}"
><!-- Template as usual: -->
<CustomListItem>
<!-- ... -->
</CustomListItem>
</List>
The list binding is suspended
first in order to prevent sending a request prematurely without the $filter query. Once the binding resumes, a single request will be sent instead of two.
{ // Controller
onInit: async function() {
const myFilters = new Filter({/* ... */}); // required from "sap/ui/model/Filter"
await this.getOwnerComponent().getModel().metadataLoaded(); // preventing race condition
this.byId("idListCart").getBinding("items") // access ODataListBinding
.filter(myFilters, "Application")
.resume(); // starts sending the request from here.
},
formatDate: function(oContext) {
return /* formatted date */;
},
createGroupHeader: function(oGroup) {
return new GroupHeaderListItem({ // required from "sap/m/GroupHeaderListItem"
title: /* formatted group header using oGroup.key */
});
},
// No list binding from controller
}