11

I am trying to bind items in a sap.m.Table (preventing usage of a Factory function) and I am getting this error:

Missing template or factory function for aggregation items of Element

My views looks as follows:

<Table id="favTable">
  <headerToolbar>
    <Toolbar>
      <Title id="tableHeader" text="{i18n>tableHeader}"/>
    </Toolbar>
  </headerToolbar>
  <columns>
    <Column>
      <Label text="{i18n>serviceNameColText}" />
    </Column>
    <Column>
      <Label text="{i18n>serviceTechNameColText}"/>
    </Column>
    <Column width="50px"/>
  </columns>
  <ColumnListItem>
    <Text text="{Text}" />
    <Text text="{Service}" />
    <Button icon="sap-icon://delete" press="onDeleteRow" />
  </ColumnListItem>
</Table>

According controller code (using a bound OData service) tries to bind items into the view after the route has been hit:

_onPatternMatched: function(oEvent) {
  let oTable = this.getView().byId(sIdTable);
  // bind items dynamically with attributes
  const sGroupId = oEvent.getParameter("arguments").Group;
  let sBindingPath = "/DataSet(SetId='" + sSetId + "')"
  oTable.bindItems({
    path: sBindingPath,
    parameters: {
      navigation: {
        FavoriteGroupSet: "ToFavorites"
      }
    },
    filters: [
      // new Filter("InstitutionId", "EQ", oEvent.getParameter("arguments").Institution),
      new Filter("SetId", "EQ", sSetId)
    ]
  })
},

What do I need to do to make the correct binding to show up the correct data?

Boghyon Hoffmann
  • 17,103
  • 12
  • 72
  • 170
Pille
  • 1,123
  • 4
  • 16
  • 38

1 Answers1

17

In UI5's concept of aggregation binding, you can use two mechanisms to build the aggregation items:

  • Template elements, which are cloned for each individual item from the model collection.
  • Factory functions, which are called for each individual item from the model and are expected to build a new control / element for each call.

I would assume that you want to use the ColumnListItem from your XML as a template. The problem is that when building XML views, controls are used as templates for the parent aggregation only when you also bind the parent aggregation in XML as well. Otherwise, they are interpreted as simple children.

In a nutshell, UI5 interprets your view as if you have a sap.m.Table with a single, static item. When you try to bind the items aggregation later, it destroys this item (well, actually it just throws an error in your case, as you must specify either a template or a factory when you use the bindAggregation method (bindItems is just a wrapper for this method).

One option to correct this would be to use relative binding and then use the bindElement method instead to change the Table's binding. In your case, it is not really clear what you want to do, as the sBindingPath seems to have a value like /DataSet(SetId='ABC'), which actually will not point towards a collection, but towards a single DataSet entity.

If you actually change the way in which your OData service is used and you have a navigation (e.g. the path would look something like /DataSet('ABC')/MyNavigationSet), then you can do the following:

View:

<!-- note that the items binding path should not start with / (to be relative) -->
<Table id="favTable" items={MyNavigationSet}>
    <columns>
        <!-- your columns... -->
    </columns>
    <items>
        <ColumnListItem id="favTableItemTemplate">
            <cells>
                <Text text="{Text}" />
                <Text text="{Service}" />
                <Button icon="sap-icon://delete" press="onDeleteRow" />
            </cells>
        </ColumnListItem>
    </items>
</Table>

Controller:

function(oEvent) {
    // the rest of your code
    this.byId("favTable").bindElement(sBindingPath);
}

Another option which works with your current OData service is to declare your template as a dependent and then use it for the binding. The templateShareable flag should be set such that the template is not destroyed on re-binding the aggregation.

View:

<Table id="favTable">
    <columns>
        <!-- your columns... -->
    </columns>
    <dependents>
        <ColumnListItem id="favTableItemTemplate">
            <cells>
                <Text text="{Text}" />
                <Text text="{Service}" />
                <Button icon="sap-icon://delete" press="onDeleteRow" />
            </cells>
        </ColumnListItem>
    </dependents>
</Table>

Controller:

function(oEvent) {
    // the rest of your code
    this.byId("favTable").bindItems({
        path: sBindingPath,
        template: this.byId("favTableItemTemplate"),
        templateShareable: true,
        parameters: {
            navigation: {FavoriteGroupSet: "ToFavorites"}
        },
        filters: [new Filter("SetId", "EQ", sSetId)]
    })
}
Serban Petrescu
  • 5,127
  • 2
  • 17
  • 34
  • Thank you again for the solution! If I were about to use solution 1 (change element binding), how can I pass filters and the like to the "bindElement" function to the table? In API documentation, I couldn't find that I can do so there. – Pille May 29 '18 at 10:52
  • 1
    Happy to help! Well, the important point to understand is that you have two bindings actually: the element binding and the relative aggregation binding on the table items. The filtering and sorting needs to be done at the aggregation binding level. The reason is that the element binding can actually affect several sub-ordinate bindings (e.g. you can element-bind a view, which can contain two separate tables with relative bindings). You can do that by as usual, by doing `this.byId("favTable").getBinding("items").filter(...)` in the controller code. – Serban Petrescu May 29 '18 at 12:21