Show TOC

Composite ControlsLocate this document in the navigation structure

Composite controls are implemented by reusing other controls.

For application developers, the composite control is a black box, therefore, an application developer cannot distinguish a composite control from native (non-composite) controls. For control developers, however, composite controls are a means to save time and effort by reusing existing controls for the implementation. As the application developer can not distinguish the controls, the control developer can change the implementation later and avoid composition (or vice versa). For existing uses of the respective control, this change is fully compatible.

Note

If you do not intend to re-use a control in several places, a composite control may not be your best choice. Composite controls are best suited for (massive) re-use and for a public API which shields the application developer from its inner workings. If these are not your requirements, consider to use other techniques of factoring out common parts within your application. You can, for example simply write a function returning the root of some control tree, as in the following example:

//Creates a MatrixLayout
    function createMatrixLayout() {
      return new sap.ui.commons.layout.MatrixLayout().createRow(
          new sap.ui.commons.layout.MatrixLayoutCell({
            backgroundDesign : "Header", 
            content : new sap.ui.commons.Label({design : "Bold", text : "Row 1"})
        })).createRow(
          new sap.ui.commons.layout.MatrixLayoutCell({
            content : new sap.ui.commons.Label({text : "Row 2"})
        }));
    }
Simple Example: Search Field

To create a composite control, you start with crafting its API including properties, events, aggregations, and so on as you do it for any other control. Choose either element or control as base type. The following simple example combines a text field with a button that we call "search field". To the outside world, it offers an editable value and can fire a search event.

API

As any other control, you can describe composite controls either via SAPUI5 metamodel files using the SAPUI5 control development tools, or you can use the JavaScript control definition API, see Developing SAPUI5 Controls.

The first XML snippet shows a metamodel file for the search field. The second snippet shows the same in JavaScript code.



<?xml version="1.0" ?>
<control xmlns="http://www.sap.com/sap.ui.library.xsd" >
  <name>SearchField</name>
  <baseType>sap.ui.core/Control</baseType>
  <properties>
    <property name="value" type="string" defaultValue="">
      <documentation>The search field's value, editable by the end user.</documentation>
    </property>
  </properties>
  <aggregations>
    <aggregation name="_tf" cardinality="0..1" type="sap.ui.commons/TextField" visibility="hidden">
      <documentation>Internal aggregation to hold the inner TextField.</documentation>
    </aggregation>
    <aggregation name="_btn" cardinality="0..1" type="sap.ui.commons/Button" visibility="hidden">
      <documentation>Internal aggregation to hold the inner Button.</documentation>
    </aggregation>
  </aggregations>
  <events>
    <event name="search">
      <documentation>Event fired when the end user activates the search, e.g. by pressing the search field's button.</documentation>
    </event>
  </events>
</control>

Same control definition as JavaScript API call:

sap.ui.core.Control.extend("SearchField", {
  metadata : {
    properties : {
       "value" : "string"
    },
    aggregations: {
       "_tf" : {type : "sap.ui.commons.TextField", multiple : false, visibility: "hidden"},
       "_btn" : {type : "sap.ui.commons.Button", multiple : false, visibility: "hidden"}
    },
    events: {
       "search" : {}
    }
  }
});

The two aggregations with visibility set to hidden are defined in the code snippets above. These aggregations are used to hold the inner controls. Aggregations are used to define a parent-child relationship between a parent control and its children (controls or elements). The knowledge about this relationship is, for example, relevant for the SAPUI5 core to dispatch events properly, or to cleanup the children when the parent is destroyed. Hidden aggregations are control internal and are used especially to register the inner controls within the control hierarchy without making them publicly available. Because hidden aggregations are only used internally within a composite control for hidden aggregations no typed accessor functions are generated, they are not cloned, and data binding is not enabled.

Behavior

The control implementation, that is, its behavior, contains the code for initialization and clean-up hooks as well as glue code for properties and events.

Init

The init function contains the composite's parts and stores references to them. We strongly recommend that you do not assign an ID to those parts according to some naming scheme, but rather let the framework compute the IDs automatically. This reduces the possibility that a composite's parts are accessed from outside via the sap.ui.getCore().byId(...) function.

During the init function, the settings of the composite only have their default values. If the application developer has provided some values to the constructor, these values will only be set later on. It is, therefore, crucial for the correct behavior of your composite control that you implement one of the synchronization mechanisms described below.

/**
 * Initialization hook... creating composite parts
 */
SearchField.prototype.init = function(){
  var that = this;
  this.setAggregation("_tf", new sap.ui.commons.TextField({
    change: function(oEvent){
      that.setProperty("value", oEvent.getParameter("newValue"), true /*no re-rendering needed, change originates in HTML*/); //see section Properties for explanation
    }
  }));
  this.setAggregation("_btn", new sap.ui.commons.Button({
    text: "Search",
    press: function(){
      that.fireSearch();
    }
  }));
};

Exit

You can use the exit function to clean up your control when it is destroyed. You do not need to destroy the inner controls. This is done automatically by the framework because the inner controls are kept in hidden aggregations.

/**
 * Clean-up hook... destroying composite parts.
 */
SearchField.prototype.exit = function() {
  //nothing to do here
};

Properties

Changes to settings in the API of a composite control are usually reflected in its parts. In the following example, the value property is propagated to the text field part. To do so, the generated setter for that property is overwritten. Make sure that you include the proper implementation which generically sets a property inside the element base class, else you would have to override the getter also.

Note how the text field's change event is used to update the composite's value property. Because the change originated in the HTML input field, no re-rendering is needed. This is expressed by the third parameter of the setProperty call. This trick is applicable whenever a property change does not require a re-rendering on this control level.

Note

Changing the text field part's value triggers a re-rendering of the text field.

/**
 * Propagate value to text field.
 */
SearchField.prototype.setValue = function(sValue){
    this.setProperty("value", sValue, true /*no re-rendering of whole search field needed*/);
    this.getAggregation("_tf").setValue(sValue); // Note: this triggers re-rendering of text field!
};

Propagating the API settings to the parts is usually not as straightforward as shown in the example above. If intercepting the changes by overriding the setters is not sufficient or too complicated, an alternative approach might be to implement a single updateAllParts method and call it at the beginning of the renderer of the composite control.

Renderer

You can use markup for layouting in the renderer implementation. But at the heart of it, you simply delegate (via the render manager) to the composite parts' renderers. This is where you really benefit from re-using other controls with non-trivial renderers. If you have chosen the updateAllParts approach to keep the composite API settings and the settings of the parts in sync, make sure that you call updateAllParts before the real rendering starts.

SearchFieldRenderer.render = function(oRenderManager, oSearchField) {
  // oSearchField.updateAllParts(); // called depending on your 'sync' approach
  oRenderManager.write("<div"); 
  oRenderManager.writeControlData(oSearchField);
  oRenderManager.addClass("SearchField"); 
  oRenderManager.writeClasses();
  oRenderManager.write(">");
  oRenderManager.renderControl(oSearchField.getAggregation("_tf"));
  oRenderManager.renderControl(oSearchField.getAggregation("_btn"));
  oRenderManager.write("</div>");
};