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:
#!js{ 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:
#!xml<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.
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.
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:
#!jsvar 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:
#!jsvar 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:
#!jsvar oComboBox.bindItems("/companies", oItemTemplate);
To remove an aggregation binding, you can use the unbindAggregation method:
#!jsoComboBox.unbindAggregation("items");
Controls with typed binding methods also provide a typed unbind:
#!jsoComboBox.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:
#!jsoComboBox.unbindAggregation("items", true);
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:
#!xml<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:
#!jssap.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:
#!jsoVerticalLayout.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" } }); } } });
To provide initial sorting and grouping in an XML view, proceed as follows:
#!xml<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:
#!jssap.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:
#!xml<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:
#!js//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:
#!js 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.
You can sort or filter data manually after the aggregation binding is complete by getting the corresponding binding and calling the sort/filter function:
#!js// manual sorting oList.getBinding("items").sort(oSorter); // manual filtering oList.getBinding("items").filter([oFilterCity, oFilterRevenue]);
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.
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"
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.
A new object with the same ID is created.
The component that owns the objects is destroyed.
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'.
To leave the parameter undefined is very error-prone, therefore we don't recommend this! Always set the parameter explicitly to true or false.