Show TOC

Aggregation BindingLocate this document in the navigation structure

Aggregation binding is used to automatically create child controls according to model data.

Let’s say we would like to display the following JSON model data in a sap.m.List:

{
	companies : [
		{
			name : "Acme Inc.",
			city: "Belmont",
			state: "NH",
			county: "Belknap",
			revenue : "123214125.34"  
		},{
			name : "Beam Hdg.",
			city: "Hancock",
			state: "NH",
			county: "Belknap"
			revenue : "3235235235.23"  
		},{
			name : "Carot Ltd.",
			city: "Cheshire",
			state: "NH",
			county: "Sullivan",
			revenue : "Not Disclosed"  
		}]
}

Here’s how to display the company list in an XML view:

<mvc:View
	controllerName="sap.ui.sample.App"
	xmlns="sap.m"
	xmlns:mvc="sap.ui.core.mvc">
	<List id=”companyList” items="{/companies}">
		<items>
			<StandardListItem
				title="{name}"
				description="{city}"
			/>
		</items>
	</List>
</mvc:View>

The List element has both an items attribute and a nested items element:

  • The attribute items="{/companies}" binds the children of our json model’s companies array to the list. This by itself is not enough to display the companies, instead it sets the parent path for the binding of all contained list items and their descendants. In addition you need to declare a nested element.

  • The nested items element in our case contains a StandardListItem. This serves as a template for creating the individual list rows.

Note

The binding paths of StandardListItem for properties title and description are relative to companies. This means that instead of having to write the whole binding path title={/companies/name}, you can simply write title={name}. By omitting the slash ‘/’ at the beginning, {name} is marked as a relative binding path.

Note

The model has a default size limit to avoid too much data being rendered on the UI. This size limit determines the number of entries used for the aggregation bindings. The default size limit is 100 entries.

This means that controls that don't support paging or don't request data in chunks (e.g. sap.m.ComboBox) only show 100 entries even though the model contains more items.

To change this behavior, you can set a size limit in the model by using oModel.setSizeLimit.

Instead of using a StandardListItem as a list row template, you can also use any other sap.m. list item, such as:

  • ActionListItem

  • DisplayListItem

  • CustomListItem

  • ObjectListItem

For more examples and details on when to use which list item control, see the various list items in the Explored app in the Demo Kit.

If you declare your list directly in JavaScript, you can define aggregation binding either in the settings object in the constructor or by calling the bindAggregation method. Aggregation binding requires the definition of a template, which is cloned for each bound entry of the list. For each clone that is created, the binding context is set to the respective list entry, so that all bindings of the template are resolved relative to the entry. The aggregated elements are destroyed and recreated whenever the bound list in the data model is changed.

To bind an aggregation, you create a template or provide a factory function, which is then passed when defining the aggregation binding itself. In the settings object, this looks as follows:

var oItemTemplate = new sap.ui.core.ListItem({text:"{name}"});
oComboBox = new sap.m.ComboBox({
	items: {
		path: "/companies",      //no curly brackets here!
		template: oItemTemplate
	}
});

A template is not necessarily a single control as shown in the example above, but can also be a tree of controls. For each list entry, a deep clone of the template is created and added to the bound aggregation.

You can also define the aggregation binding by using the bindAggregation method of a control:

var oItemTemplate = new sap.ui.core.ListItem({text:"{name}"});
var oComboBox.bindAggregation("items", "/companies", oItemTemplate)

In addition, some controls have a typed binding method for aggregations that are likely to be bound by the application:

var oComboBox.bindItems("/companies", oItemTemplate);

To remove an aggregation binding, you can use the unbindAggregation method:

oComboBox.unbindAggregation("items");

Controls with typed binding methods also provide a typed unbind:

oComboBox.unbindItems();

When an aggregation is unbound, its aggregated controls are removed and destroyed by default. If you would like to keep the items in your ComboBox, for example, you can do so by using:

oComboBox.unbindAggregation("items", true);
Using Factory Functions

The factory function is a more powerful approach for creating controls from model data. The factory function is called for each entry of a control’s aggregation, and the developer can decide whether each entry shall be represented by the same control with different properties or even by a completely different control for each entry.

The factory function comes with the parameters sId, which should be used as an ID for the new control, and oContext, which is for accessing the model data of the entry. The returned object must be of type sap.ui.core.Element. Here’s how this scenario can be realized in an XML view and a controller using our JSON model data:

<mvc:View
	controllerName="sap.ui.sample.App"
	xmlns="sap.m"
	xmlns:l="sap.ui.layout"
	xmlns:mvc="sap.ui.core.mvc">
	<l:VerticalLayout
		content="{ path: '/companies', factory: '.createContent'}"
		class="sapUiContentPadding"
		width="100%"/>
</mvc:View>

Please note the '.' in factory: '.createContent'. The class App.controller.js contains the implementation of our factory method:

sap.ui.define([
	"sap/ui/core/mvc/Controller",
	"sap/ui/model/json/JSONModel",
	"sap/ui/model/type/String",
	"sap/ui/model/type/Float",
	"sap/m/Input",
	"sap/m/Text",
	"sap/m/CheckBox"
], function (Controller, JSONModel, StringType, Float, Input, Text, CheckBox ) {
	"use strict";
	return Controller.extend("sap.ui.sample.App", {
		onInit : function () {
		…
		},
		createContent: function (sId, oContext) {
		var oRevenue = oContext.getProperty("revenue");
			switch(typeof oRevenue) {
				case "string":
					return new Text(sId, {
						text: {
							path: "revenue",
							type: new StringType()
						}
					});
  
				case "number":
					return new Input(sId, {
						value: {
							path: "revenue",
							type: new Float()
						}
					});
				
				case "boolean":
					return new Checkbox(sId, {
						checked: {
							path: "revenue"
						}
					});
			}
		},
	});
});

If you would like to avoid using the XML view, you would proceed as follows:

oVerticalLayout.bindAggregation("content", "/companies", function (sId, oContext) {
	var oRevenue = oContext.getProperty("revenue");
	switch(typeof oRevenue) {
			case "string":
				return new sap.m.Text(sId, {
					text: {
						path: "revenue",
						type: new sap.ui.model.type.String()
					}
				});
  
			case "number":
				return new sap.m.Input(sId, {
					value: {
						path: "revenue",
						type: new sap.ui.model.type.Float()
					}
				});
				
			case "boolean":
				return new sap.m.Checkbox(sId, {
					checked: {
						path: "revenue"
					}
				});
			}
		}
});
Initial Sorting, Grouping and Filtering for Aggregation Binding

To provide initial sorting and grouping in an XML view, proceed as follows:

<mvc:View
	controllerName="sap.ui.sample.App"
	xmlns="sap.m"
	xmlns:l="sap.ui.layout"
	xmlns:mvc="sap.ui.core.mvc">		
	<List items="{ path: '/companies', 
		sorter: { path: 'county', descending: false, group: '.getCounty'}, 
		groupHeaderFactory: '.getGroupHeader'}">
		<items>
			<StandardListItem
				title="{name}"
				description="{city}"
			/>
		</items>
	</List>
	
</mvc:View>

The this context of a group header factory function is generally set to the control (or managed object) that owns the binding. However, in XML views, the reference to the group header factory is done in the view controller by putting a dot (.) in front of the name of the group header factory function ({ groupHeaderFactory:'.myGroupHeader' }). In this case, the group header factory's this context is bound to the controller.

The list uses a sorter which sorts the list of companies in ascending order by the county column. It also groups its rows using the App.controller’s getCounty method to provide the captions and the getGroupHeader function to provide non-standard group header controls, as shown here:

sap.ui.define([
	"sap/ui/core/mvc/Controller",
	"sap/ui/model/json/JSONModel",
	"sap/m/GroupHeaderListItem "
], function (Controller, JSONModel, GroupHeaderListItem) {
	"use strict";
	return Controller.extend("sap.ui.sample.App", {
		onInit : function () {
			…
		},	

		getCounty: function(oContext) {
			return oContext.getProperty('county');
		},
		
		getGroupHeader: function(oGroup) {
			return new GroupHeaderListItem({
				title : oGroup.key,
				upperCase : false
			}
		);
	},   
});

As you can see, getCounty generates the group caption, which in this case is the county of the current companies. getGroupHeader serves as a group header factory function: use this if you would like to use non-standard group headers. In our example, we would like the group header caption not to be upper case (which is the default). After sorting and grouping, the company list looks like this:

The following XML snippet provides initial filtering:

<mvc:View
	controllerName="sap.ui.sample.App"
	xmlns="sap.m"
	xmlns:l="sap.ui.layout"
	xmlns:mvc="sap.ui.core.mvc">		
	<List items="{ path: '/companies', 
		filters: [{path: 'city', operator: 'StartsWith', value1: 'B'},
			{path: 'revenue', operator: 'LT', value1: 150000000}]}">		
		<items>
			<StandardListItem
				title="{name}"
				description="{city}"
			/>
		</items>
	</List>	  
</mvc:View>

The example shown here will only display companies whose city name begins with a ‘b’ and whose revenue is less than 150 million. As you can see, you can provide more than one filter, each of which may refer to different columns using different filter operators. For a complete list of permitted filter operators, see sap.ui.model.FilterOperator in the API Reference part of the Demo Kit.

As shown below, initial sorting, grouping and filtering can of course also be provided using JavaScript.

You can define a sorter and/or filters:

//returns group header captions
var fnGetCounty = function(oContext) {
	return oContext.getProperty('county');
}

var oSorter = new Sorter({
	path: 'county', 
	descending: false, 
	group: fnGetCounty});
  
var oFilterCity = new sap.ui.model.Filter("city",
	sap.ui.model.FilterOperator.StartsWith, "B"),
	oFilterRevenue = new sap.ui.model.Filter("revenue",
		sap.ui.model.FilterOperator.LT, 150000000);

You can pass sorters and filters to the aggregation binding:

var oList = new sap.m.List({
	items: {path: "/companies", template: oItemTemplate, 
		sorter: oSorter, filters:[oFilterCity, oFilterRevenue]  
	}
});

You can also use the other aggregation binding possibilities (for example bindAggregation or bindItems) and provide the sorter and filters as parameters.

Manual Sorting and Filtering for Aggregation Binding

You can sort or filter data manually after the aggregation binding is complete by getting the corresponding binding and calling the sort/filter function:

// manual sorting
oList.getBinding("items").sort(oSorter);

// manual filtering
oList.getBinding("items").filter([oFilterCity, oFilterRevenue]);
Note

getBinding requires the name of the bound aggregation. In this example, we are looking at the items of the sap.m.List control.

For more information about the various sorting and filter methods and operators, see the documentation for Filter, Sorter, and Filter operations under sap.ui.model in the API Reference part of the Demo Kit.

Lifecycle of Binding Templates
The lifecycle of the binding templates differs from the lifecycle of controls that are contained in an aggregation. Whenever a control object is destroyed, any aggregating object is destroyed as well. For list binding templates, you specify the behavior by using the additional parameter templateShareable for the bindAggregation method of class sap.ui.base.ManagedObject.
  • templateShareable = "false" (preferred setting)

    If you set the parameter to false the lifecycle is controlled by the framework. It will destroy the template when the binding is removed (unbindAggregation, unbindItems)

  • templateShareable = "true"

    If you set the parameter to true the template is not destroyed when (the binding of) the aggregated object is destroyed. Use this option in the following cases only:
    • The template is reused in your app to define an additional list binding.

      Since the template is not destroyed, this could also affect some other aggregation that uses the same template at a later point in time.

    • The template is cloned.

    In these cases, the app has to make sure that the templates are properly cleaned up at some point in time - at the latest when the corresponding controller or component is destroyed. An error message is logged to make sure that this is not forgotten.
  • If the parameter is undefined, (neither true nor false), the framework checks at several points in time whether all list bindings are removed. If there are no bindings, the templates is marked as candidate for destroy(), but it is not immediately destroyed. The candidate is destroyed in the following cases:
    • A new object with the same ID is created.

    • The component that owns the objects is destroyed.

    If the framework determines that a "candidate for destroy" is still in use in another binding or in a clone operation, the framework makes sure that the candidate is not destroyed by implicitly setting templateShareable to true (as this best reflects how the app deals with the template). But now the template is not destroyed at all (an error messages is issued), and the app implementation needs to make sure that the binding template is destroyed as soon as it is no longer needed.
    Note
    The error messages are:
    • A binding template that is marked as 'candidate for destroy' is reused in a binding.

    • During a clone operation, a template was found that neither was marked with 'templateShareable:true' nor 'templateShareable:false'.

    Caution

    To leave the parameter undefined is very error-prone, therefore we don't recommend this! Always set the parameter explicitly to true or false.